Location>code7788 >text

Spring source code analysis] Spring Scope features in the dynamic proxy - Scoped Proxy

Popularity:117 ℃/2024-08-06 18:28:37

This article is based on Springboot 3.3.2 and Springcloud version 2023.0.1.

What is Spring Scoped Proxy

When using the Spring cloud configuration center's dynamic configuration update feature, I found that when adding a class to the@RefreshScopeAfter annotation, where@ValueInjected fields are automatically updated. At first I thought Spring would automatically set the field values of the bean after receiving the configuration update event, but after testing, I found that the configuration update is achieved by rebuilding the entire bean. Experimental code is as follows:

@RestController
public class TestController {
    @Autowired
    private RefreshClazz refreshClazz;

    @GetMapping("/test/url")
    public String getCount() {
        return "count=" + ();
    }

    @Component
    @RefreshScope
    public static class RefreshClazz {
        @Value("${}")
        private String configStr;

        private int count = 0;

        @PostConstruct
        public void postConstruct() {
            ("POST_CONSTRUCT");
        }

        @PreDestroy
        public void preDestroy() {
            ("PRE_DESTROY");
        }

        public int getCount() {
            return count++;
        }
    }
}

In the code, RefreshClazz is a tagged@RefreshScopeBean, by means of the@AutowiredThe RefreshClazz is injected into the Controller in the same way as the POST_CONSTRUCTOR. Running the code above, you'll see that when the configuration is updated, the RefreshClazz internal field count is reset to 0, and PRE_DESTROY and POST_CONSTRUCT are output, indicating that the old bean is deleted and a new one is created.

So here's the question.RefreshClazzThe controller is statically injected into the controller, so how can it be automatically refreshed? The reason is that it is injected with a dynamic proxy object.@Lazy@Transactional@CacheableScope is one of these dynamic proxies, and the dynamic proxy used by the Scope function is called a Scoped Proxy.

How to use Scoped Proxy

@RefreshScopeDefine the code:

@Target({ ,  })
@Retention()
@Scope("refresh")
@Documented
public @interface RefreshScope {

	@AliasFor(annotation = )
	ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}

according to@RefreshScopedefinition we can find that it is equivalent to the@Scope(value = "refresh", proxyMode = ScopedProxyMode.TARGET_CLASS). The key to this isproxyMode = ScopedProxyMode.TARGET_CLASS. This parameter has a total of four values:

  1. DEFAULT: default value, equivalent toNO
  2. NO: No scoped proxy is created, and this pattern is usually not useful for beans that are not a singleton scope;
  3. INTERFACES: Use the JDK Dynamic Proxy to create a dynamic proxy object based on an interface implementation;
  4. TARGET_CLASS: Use CGLIB to create a dynamic proxy object based on inheritance.
    It can be seen that@RefreshScope Dynamic proxy objects implemented based on inheritance are used by default. This has several advantages:
  5. Both interface and type injection can be used when using party injection. If theproxyMode = The type of scoped proxy created is not the type of the original bean, but simply implements the interface it implements. Since the@Autowired The real thing to inject is the scoped proxy, and if the variable is defined as the type of the bean, Spring will report the followingNo qualifying bean of type '...' available Error;
  6. The JDK implementation of dynamic proxies is slightly larger than the CGLIB dynamic proxy in terms of performance and memory overhead.

How a Scoped Proxy is Created

Spring Bean Registration

The logic for registering the scoped proxy in the Spring container comes from theScopedProxyUtils#createScopedProxy method, which has the following code:

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
		BeanDefinitionRegistry registry, boolean proxyTargetClass) {

        // Getting the originalbeanNamecap (a poem)beandefine
	String originalBeanName = ();
	BeanDefinition targetDefinition = ();
        // Generation of proxiesbean(used form a nominal expression)beanName
	String targetBeanName = getTargetBeanName(originalBeanName);

	// Creating Dynamic AgentsBean(used form a nominal expression)BeanDefinition
	RootBeanDefinition proxyDefinition = new RootBeanDefinition();
	(new BeanDefinitionHolder(targetDefinition, targetBeanName));
	(targetDefinition);
	(());
	(());

        // would be proxiedbean(used form a nominal expression)beanNamepass to a dynamic agentBean
	().add("targetBeanName", targetBeanName);
	if (proxyTargetClass) {
		(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, );
		// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
	}
	else {
		().add("proxyTargetClass", );
	}

	// general term "will" or "original" in the sense that it refers to a combination of the followingbean(used form a nominal expression)属性复制到动态代理beandefine中.
	(());
	(());
	if (targetDefinition instanceof AbstractBeanDefinition abd) {
		(abd);
	}

	// low-levelbeanconceal,Not involved in injection.
	(false);
	(false);

	// low-levelbean(used form a nominal expression)beanNameset totargetBeanName,Register to Container.
	(targetBeanName, targetDefinition);

	// 返回刚生成(used form a nominal expression)动态代理bean(used form a nominal expression)BeanDefinition,beanNameset to原bean(used form a nominal expression)名字.
	return new BeanDefinitionHolder(proxyDefinition, originalBeanName, ());
}

Prior to entering this method, this method that is being@ScopeThe modified bean is no different than any other normal singleton bean. However, this method does a magic trick on it and ends up hiding the definition of the original bean under a new name inside the Spring container, exposing a newly generated scoped proxy bean. because the new bean has the same name as the original bean and is probably a Primary Bean, the@Autowired This dynamic proxy bean is injected by default.

What was the name of the original Bean changed to? You can refer to thegetTargetBeanName() Methods:

private static final String TARGET_NAME_PREFIX = "scopedTarget.";

public static String getTargetBeanName(String originalBeanName) {
	return TARGET_NAME_PREFIX + originalBeanName;
}

Therefore, the beanName of the underlying bean isscopedTarget.<originalBeanName>

Dynamic Proxy Object Generation

existScopedProxyUtils#createScopedProxy method, we notice that the newly generated dynamic proxy bean class is set to the. It's aFactoryBean, which is responsible for specifically generating dynamic proxy objects. The code is as follows:

public class ScopedProxyFactoryBean extends ProxyConfig
	implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {

  /** through (a gap)Springcontainer to get the underlying object'sTargetSource. */
  private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();

  /** lowest rung (of society)bean(used form a nominal expression)beanName. */
  @Nullable
  private String targetBeanName;

  /** 缓存(used form a nominal expression)单例 Scoped proxy. */
  @Nullable
  private Object proxy;

  /** constructor method. */
  public ScopedProxyFactoryBean() {
	setProxyTargetClass(true);
  }

  /** 设置lowest rung (of society)bean(used form a nominal expression)beanName. */
  public void setTargetBeanName(String targetBeanName) {
	 = targetBeanName;
	(targetBeanName);
  }

  /** Creating Dynamic Proxy Objects(used form a nominal expression)主方法. */
  @Override
  public void setBeanFactory(BeanFactory beanFactory) {
	if (!(beanFactory instanceof ConfigurableBeanFactory cbf)) {
		throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
	}
    // because oftargetSource设置使用(used form a nominal expression)beanFactory
	(beanFactory);

    // pass (a bill or inspection etc)ProxyFactoryCreating Dynamic Proxy Objects
	ProxyFactory pf = new ProxyFactory();
	(this);
    // 使用配置好(used form a nominal expression)targetSource
	();

	(, "Property 'targetBeanName' is required");
	Class<?> beanType = ();
	if (beanType == null) {
		throw new IllegalStateException("Cannot create scoped proxy for bean '" + +
				"': Target type could not be determined at the time of proxy creation.");
	}
	if (!isProxyTargetClass() || () || (())) {
		((beanType, ()));
	}

	// Add an introduction that implements only the methods on ScopedObject.
	ScopedObject scopedObject = new DefaultScopedObject(cbf, ());
	(new DelegatingIntroductionInterceptor(scopedObject));

	// Add the AopInfrastructureBean marker to indicate that the scoped proxy
	// itself is not subject to auto-proxying! Only its target bean is.
	();
    
    // Generate and cache dynamic proxy objects
	 = (());
  }


  /** 工厂类(used form a nominal expression)获取生成对象方法,获取生成(used form a nominal expression)动态代理对象. */
  @Override
  @Nullable
  public Object getObject() {
	if ( == null) {
		throw new FactoryBeanNotInitializedException();
	}
	return ;
  }
}

This class uses theProxyFactory creates dynamic proxy objects, which it generates through the interfaceTargetSource to get the underlying object of the proxy. The aboveScopedProxyFactoryBean UsedSimpleBeanTargetSource, it is coded as follows:

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {

  @Override
  public Object getTarget() throws Exception {
	return getBeanFactory().getBean(getTargetBeanName());
  }
}

The logic is clear, throughbeanFactory to get the name of thetargetBeanName bean object as the object being proxied. And thistargetBeanName It is generated when the scoped proxy is registered in the Spring container, and the logic for setting it up is:

().add("targetBeanName", targetBeanName);

Scoped Proxy overall working logic

Looking at the previous code, we can summarize the working logic of the scoped proxy:

  1. (indicates passive-voice clauses)@AutowiredThe injection is a scoped proxy whose BeanDefinition's scope is actually a singleton;
  2. When the bean method is called, the scoped proxy obtains the name of the bean in the Spring container.scopedTarget.<originalBeanName> The bean to be proxied is a bean whose scope is refresh;
  3. The scoped proxy calls the corresponding method of the bean being proxied.

The Life Cycle of a Scoped Bean

In the octet, Spring has five Scopes (singleton, prototype, request, session, globalSession) (this is actually outdated, the latest documentation is six: singleton, prototype, request, session, application, websocket). Then in this article therefresh What is scope?
In fact, it's only necessary to inject a realization of theScope interface, the user can add a customized scope.refresh The scope isspring-cloud-context When the Spring container fetches any bean that is not a singleton or prototype, it will first find the Scope object that corresponds to the scope name and use theScope#get Get the bean from this object, code referenceAbstractBeanFactory#doGetBeanThe life cycle of a scoped bean is managed by the specific scope class (see the implementation example). The lifecycle of a scoped bean is managed by the specific scope class (for an example implementation seeGenericScope)。