Spring source code - IOC part - circular dependency - use an example to prove what problems will occur if the L2 cache is removed [7]

Experimental environment: spring-framework-5.0.2,jdk8,gradle4.3.1

 

Above Spring source code - IOC part - how spring solves Bean circular dependency [6] As mentioned, if you do not use the three-level cache (singletonObjects, earlySingletonObjects, singletonFactories) and only use the two-level cache (singletonObjects, singletonFactories), it will have no impact on ordinary beans, but it will lead to repeated creation of bean instances for AOP proxy beans, which violates the singleton principle.

This paper uses a practical example to prove it.

1. Define an annotation

package beans;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAopAnnotation {
    String value();
}

 

2. Define AOP and cut the section to @ myaopinnotation. In this way, the proxy object is who @ myaopinnotation is marked on. It is more flexible

package beans;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;

@Aspect
@Component
@EnableAspectJAutoProxy
public class MyAop {

    @Pointcut("@annotation(beans.MyAopAnnotation)")
    public void pointCat() {
    }

    @Before("pointCat()")
    public void before(JoinPoint joinPoint) {
        System.out.println("implement AOP before method");
    }
}

 

3. To define beans, I configure @ myaopinnotation on TestBean, that is, TestBean needs to create AOP proxy. In addition, TestBean, User and User2 are injected into each other

TestBean
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TestBean {

    @Autowired
    private User user;

    @Autowired
    private User2 user2;

    @Autowired
    private TestBean testBean;

    public User getUser() {
        return user;
    }

    public User2 getUser2() {
        return user2;
    }

    public TestBean getTestBean() {
        return testBean;
    }

    @MyAopAnnotation("")
    public void hello() {
        System.out.println("TestBean implement hello method ");
    }
}
User
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class User {

    @Autowired
    private User2 user2;

    @Autowired
    private TestBean testBean;

    public TestBean getTestBean() {
        return testBean;
    }

    public User2 getUser2() {
        return user2;
    }


}
User2
package beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

@Component
@DependsOn({"user"})
public class User2 {

    @Autowired
    private User user;

    @Autowired
    private TestBean testBean;

    public User getUser() {
        return user;
    }

    public TestBean getTestBean() {
        return testBean;
    }

}

 

4. Run code

public class Main {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.scan("beans");
		context.refresh();

		TestBean testBean = (TestBean) context.getBean("testBean");
		testBean.hello();

		User user = context.getBean(User.class);
		User2 user2 = context.getBean(User2.class);
		System.out.println("user == user2.getUser() : " + (user == user2.getUser()));

		System.out.println("testBean == user.getTestBean() : " + (testBean == user.getTestBean()));
		System.out.println("testBean == user2.getTestBean() : " + (testBean == user2.getTestBean()));
		System.out.println("user.getTestBean() == user2.getTestBean() : " + (user.getTestBean() == user2.getTestBean()));

		context.close();
	}

 

The running results are as follows: you can see that the AOP has taken effect. In addition, the injected properties of each bean are the same bean, which proves that both proxy bean (TestBean) and ordinary bean (User, User2) are singletons.

 

Then, I'll modify the getSingleton method to mask the earlySingletonObjects, that is, the bean will be from singletonfactory until it becomes a complete object Getobject() gets the early bean instance. As mentioned above, singletonfactory GetObject () is a factory method. Each call will execute the getEarlyBeanReference method, and the getEarlyBeanReference method will create a new proxy object for AOP proxy beans, which will be returned directly by ordinary beans.

Modified getSingleton
@Nullable
	protected Object getSingleton(String beanName, boolean allowEarlyReference) {

		// 1. [get from L1 cache] get the singleton object corresponding to beanName from singleton object cache
		Object singletonObject = this.singletonObjects.get(beanName);

		if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			// 3. Lock for operation
			synchronized (this.singletonObjects) {

				// 4. [get from L2 cache] get the singleton object from the early singleton object cache
				// It is called an early singleton object because the objects in earlySingletonObjects are created through the ObjectFactory exposed in advance, and no operations such as attribute filling have been carried out
				// TODO blocks earlySingletonObjects
				//singletonObject = this.earlySingletonObjects.get(beanName);

				// 5. If it is not in the early singleton object cache, it is allowed to create early singleton object references
				if (singletonObject == null && allowEarlyReference) {

					// 6. [get from L3 cache] get the single instance factory of beanName from the single instance factory cache
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {

						// 7. If there is a singleton factory, create a singleton through the factory
						singletonObject = singletonFactory.getObject();

						// TODO blocks earlySingletonObjects
						// 8. Put the singleton object created through the singleton object factory into the early singleton object cache
						//this.earlySingletonObjects.put(beanName, singletonObject);
						// 9. Remove the singleton object factory corresponding to the beanName, because the singleton factory has created an instance object and put it into the earlySingletonObjects cache,
						// Therefore, the singleton object of beanName can be obtained through the earlySingletonObjects cache later, and the singleton factory does not need to be used
						//this.singletonFactories.remove(beanName);
					}
				}
			}
		}

		return singletonObject;
	}

 

Let's run again and find user and user2 Getuser () is the same object, because user is just an ordinary bean, and it is itself no matter how many times it is obtained. Testbeans are different because they are proxy objects. Without the secondary cache earlySingletonObjects, they are taken directly from the tertiary cache singletonFactories, and each time they are taken, a new proxy object is returned. Therefore, testbeans are no longer a single instance at this time.

 

Keywords: Spring

Added by SieRobin on Sun, 06 Feb 2022 09:43:05 +0200