@Scope annotation is invalid? What’s going on

I believe everyone knows that there are six types of scope attributes:

Value Meaning Effective condition
singleton means that this Bean is a singleton. In the Spring container, only one instance will exist.
prototype Multiple instance mode, each time a Bean is obtained from the Spring container, an instance of the Bean will be created. come out.
request When a new request arrives, an instance of the Bean will be created for processing. Effective in web environment
session When there is a new session, a Bean instance will be created. Effective in web environment
application This means that there is only one Bean in the entire life cycle of the project. Effective in web environment
gloablsession It is somewhat similar to application, but this is used in portlet environment. Effective in web environment

This usage is also very simple. You can set whether a Bean is in singleton mode through configuration.

1. Problem presentation

What I want to talk about today is not basic usage, but another question. Suppose I now have the following two beans:

@Service
public class UserService {<!-- -->
    @Autowired
    UserDao userDao;
}
@Repository
public class UserDao {<!-- -->
}

Inject UserDao into UserService. Since neither scope is declared, both are singletons by default.

Now, if I set the Scope for UserDao as follows:

@Repository
@Scope(value = "prototype")
public class UserDao {<!-- -->
}

This prototype means that if we get the UserDao instance multiple times from the Spring container, we will get the same instance.

but! ! !

I am now injecting UserDao into UserService. UserService is a singleton, that is, UserService is only initialized once. It stands to reason that UserService only asks the Spring container for UserDao once, which leads to the fact that the UserDao we finally get from UserService is always It’s the same one.

The test method is as follows:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService us = ctx.getBean(UserService.class);
UserService us2 = ctx.getBean(UserService.class);
System.out.println(us.userDao == us2.userDao);

The final print result is true.

In fact, there is no problem with this, because you only need UserDao once with the Spring container. But now what if my requirement is that UserService is a singleton and UserDao gets a different instance every time? How should you respond?

2. Solution

Spring has already considered this problem, and the solution is to implement it through proxies.

When we use the @Scope annotation, the annotation also has another attribute proxyMode. This attribute has four values, as follows:

public enum ScopedProxyMode {<!-- -->
DEFAULT,
NO,
INTERFACES,
TARGET_CLASS
}
  • DEFAULT: This is the default value. The default value is NO, which means no proxy is used.
  • NO: Do not use a proxy.
  • INTERFACES: Using JDK dynamic proxy requires the current bean to have an interface.
  • TARGET_CLASS: Use CGLIB dynamic proxy.

You can generate a dynamic proxy object for a Bean by setting the proxyMode attribute to implement multiple instances of the Bean.

Now I modify the annotations on UserDao as follows:

@Repository
@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserDao {<!-- -->
}

At this point, execute the test again:

AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService us = ctx.getBean(UserService.class);
UserService us2 = ctx.getBean(UserService.class);
System.out.println(us==us2);
System.out.println("us.userDao = " + us.userDao);
System.out.println("us2.userDao = " + us2.userDao);
System.out.println("us.userDao.getClass() = " + us.userDao.getClass());

The final print result is as follows:

As you can see, UserService is a singleton, userDao is indeed a different instance, and userDao is a CGLIB dynamic proxy object.

So, how to configure it if it is XML configuration?

<bean class="org.javaboy.demo.p2.UserDao" id="userDao" scope="prototype">
    <aop:scoped-proxy/>
</bean>
<bean class="org.javaboy.demo.p2.UserService">
    <property name="userDao" ref="userDao"/>
</bean>

This is different from the ordinary AOP configuration method, but it is easy to understand. Just compare it with the annotation configuration above to understand.

3. Source code analysis

So how does this happen?

Spring provides a special tool method AnnotationConfigUtils#applyScopedProxyMode to handle this matter:

static BeanDefinitionHolder applyScopedProxyMode(
ScopeMetadata metadata, BeanDefinitionHolder definition, BeanDefinitionRegistry registry) {<!-- -->
ScopedProxyMode scopedProxyMode = metadata.getScopedProxyMode();
if (scopedProxyMode.equals(ScopedProxyMode.NO)) {<!-- -->
return definition;
}
boolean proxyTargetClass = scopedProxyMode.equals(ScopedProxyMode.TARGET_CLASS);
return ScopedProxyCreator.createScopedProxy(definition, registry, proxyTargetClass);
}

From here we can see that if the proxy mode is NO/Default, then the original definition will be returned directly, otherwise the ScopedProxyCreator.createScopedProxy method will be called to generate the proxy object, which also involves a proxyTargetClass Parameter, this parameter is used to determine whether it is a JDK dynamic proxy or a CGLIB dynamic proxy. If proxyMode = ScopedProxyMode.TARGET_CLASS is set, then the proxyTargetClass variable is true, indicating a CGLIB dynamic proxy, otherwise it is a JDK dynamic proxy. .

Let’s continue to look at the ScopedProxyCreator.createScopedProxy method, which internally calls the ScopedProxyUtils#createScopedProxy method:

public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {<!-- -->
String originalBeanName = definition.getBeanName();
BeanDefinition targetDefinition = definition.getBeanDefinition();
String targetBeanName = getTargetBeanName(originalBeanName);
// Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class);
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName));
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) {<!-- -->
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
}
else {<!-- -->
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition abd) {<!-- -->
proxyDefinition.copyQualifiersFrom(abd);
}
// The target bean should be ignored in favor of the scoped proxy.
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases());
}

The code inside this is actually not easy to explain. It just creates a new RootBeanDefinition object. The variable name is proxyDefinition. It can also be seen from here that this is used to create a proxy object, and then combines the various attributes of the old BeanDefinition object. All values are copied in, and finally the proxyDefinition of the new proxy is returned.

One point worthy of attention here is that when creating a proxyDefinition, the parameter passed in by the constructor is ScopedProxyFactoryBean, which means that the object that this BeanDefinition will generate in the future is an object of ScopedProxyFactoryBean. Then let’s continue to look at ScopedProxyFactoryBean. You can see this from the name. Is a FactoryBean:

public class ScopedProxyFactoryBean extends ProxyConfig
implements FactoryBean<Object>, BeanFactoryAware, AopInfrastructureBean {<!-- -->
private final SimpleBeanTargetSource scopedTargetSource = new SimpleBeanTargetSource();
@Nullable
private String targetBeanName;
@Nullable
private Object proxy;
public ScopedProxyFactoryBean() {<!-- -->
setProxyTargetClass(true);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {<!-- -->
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory();
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Class<?> beanType = beanFactory.getType(this.targetBeanName);
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {<!-- -->
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader()));
}
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());
}
@Override
public Object getObject() {<!-- -->
return this.proxy;
}
@Override
public Class<?> getObjectType() {<!-- -->
if (this.proxy != null) {<!-- -->
return this.proxy.getClass();
}
return this.scopedTargetSource.getTargetClass();
}
@Override
public boolean isSingleton() {<!-- -->
return true;
}
}

The getObject method here returns the proxy object, and the proxy object is initialized in the setBeanFactory method (the setBeanFactory method is triggered after the Bean is initialized and the attributes are filled).

The setBeanFactory method is to create a proxy object. The set targetSource is scopedTargetSource, which encapsulates the proxy object. scopedTargetSource is a Bean of type SimpleBeanTargetSource. The characteristic of SimpleBeanTargetSource is that every time the proxy object is obtained, the getTarget method will be called again. , and in the getTarget method of SimpleBeanTargetSource, it searches for the Bean in the Spring container based on the original Bean name and returns it. That is to say, in the proxy object here, the proxied object is actually the original Bean, corresponding to the above case. , the proxy object is userDao.

Another point that needs attention is the added interceptor DelegatingIntroductionInterceptor, which is enhanced content for the proxy object (other content in the setBeanFactory method is conventional AOP code, so I won’t go into details. Friends who are not familiar with it can take a look. The Spring source code video recently recorded by Brother Song (How should I learn Spring source code?).

The DelegatingIntroductionInterceptor interceptor passes in scopedObject as a parameter. This parameter actually represents the proxied object, that is, the proxied object is a ScopedObject.

public class DelegatingIntroductionInterceptor extends IntroductionInfoSupport
implements IntroductionInterceptor {<!-- -->
@Nullable
private Object delegate;
public DelegatingIntroductionInterceptor(Object delegate) {<!-- -->
init(delegate);
}
protected DelegatingIntroductionInterceptor() {<!-- -->
init(this);
}
private void init(Object delegate) {<!-- -->
this.delegate = delegate;
implementInterfacesOnObject(delegate);
suppressInterface(IntroductionInterceptor.class);
suppressInterface(DynamicIntroductionAdvice.class);
}
@Override
@Nullable
public Object invoke(MethodInvocation mi) throws Throwable {<!-- -->
if (isMethodOnIntroducedInterface(mi)) {<!-- -->
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate, mi.getMethod(), mi.getArguments());
if (retVal == this.delegate & amp; & mi instanceof ProxyMethodInvocation pmi) {<!-- -->
Object proxy = pmi.getProxy();
if (mi.getMethod().getReturnType().isInstance(proxy)) {<!-- -->
retVal = proxy;
}
}
return retVal;
}
return doProceed(mi);
}
@Nullable
protected Object doProceed(MethodInvocation mi) throws Throwable {<!-- -->
return mi.proceed();
}
}

DelegatingIntroductionInterceptor implements the IntroductionInterceptor interface, which is a typical introduction enhancement. This brother Song has written an article before to tell you about it: IntroductionAdvisor, a rare introduction enhancement in Spring, you should be able to understand the content here after reading the previous article. Since it is an introduction enhancement, the final generated proxy object is both an instance of UserDao and an instance of ScopedObject.

4. Summary

After the above analysis, we can draw the following conclusions:

  1. The UserDao obtained multiple times from UserService is actually a ScopedObject object.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService us1 = ctx.getBean(UserService.class);
UserService us2 = ctx.getBean(UserService.class);
UserDao userDao1 = us1.getUserDao();
UserDao userDao2 = us2.getUserDao();
ScopedObject scopedObject1 = (ScopedObject) userDao1;
ScopedObject scopedObject2 = (ScopedObject) userDao2;
System.out.println("userDao1 = " + userDao1);
System.out.println("userDao2 = " + userDao2);
System.out.println("scopedObject1 = " + scopedObject1);
System.out.println("scopedObject2 = " + scopedObject2);

The above code will not report an error, this is the introduction enhancement.

  1. The generated proxy object itself is actually the same, because UserService is a singleton. After all, UserDao is only injected once, but the proxied Bean in the proxy object will change.

The phenomenon manifested is the four objects in the first point. If you compare their memory addresses, userDao1, userDao2, scopedObject1 and scopedObject2 are the same memory address because they are the same proxy object.

But the proxied object is different. After DEBUG, you can see that the first four addresses representing the proxy objects are all the same, while the UserDao being proxied later is a different object.

The reason for this phenomenon is that in the setBeanFactory method of ScopedProxyFactoryBean, the TargetSource we set is a SimpleBeanTargetSource. The characteristic of this TargetSource is that every time it is proxied, it will search for the Bean in the Spring container. Since UserDao has many objects in the Spring container, Example, so the UserDao returned by Spring is not the same every time, thus realizing multiple instances of UserDao:

public class SimpleBeanTargetSource extends AbstractBeanFactoryBasedTargetSource {<!-- -->
@Override
public Object getTarget() throws Exception {<!-- -->
return getBeanFactory().getBean(getTargetBeanName());
}

}

For the second point, if you don’t understand it yet, you can read Brother Song’s previous article: Are the proxied objects in AOP singletons? .

Okay, now you guys understand what’s going on~