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@RefreshScope
After annotation, where@Value
Injected 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@RefreshScope
Bean, by means of the@Autowired
The 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.RefreshClazz
The 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
、@Cacheable
Scope 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
@RefreshScope
Define the code:
@Target({ , })
@Retention()
@Scope("refresh")
@Documented
public @interface RefreshScope {
@AliasFor(annotation = )
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
according to@RefreshScope
definition 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:
-
DEFAULT
: default value, equivalent toNO
; -
NO
: No scoped proxy is created, and this pattern is usually not useful for beans that are not a singleton scope; -
INTERFACES
: Use the JDK Dynamic Proxy to create a dynamic proxy object based on an interface implementation; -
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: - Both interface and type injection can be used when using party injection. If the
proxyMode =
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; - 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@Scope
The 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 a
FactoryBean
, 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:
- (indicates passive-voice clauses)
@Autowired
The injection is a scoped proxy whose BeanDefinition's scope is actually a singleton; - 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; - 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#doGetBean
The 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
)。