Experimental environment: spring-framework-5.0.2,jdk8,gradle4.3.1
- Spring source code - IOC part - container introduction [1]
- Spring source code - IOC part - container initialization process [2]
- Spring source code - IOC part - Xml Bean parsing and registration process [3]
- Spring source code - IOC part - Custom IOC container and Bean parsing registration [4]
- Spring source code - IOC part - Bean instantiation process [5]
- Spring source code - IOC part - how spring solves Bean circular dependency [6]
- Spring source code - IOC part - circular dependency - use an example to prove what problems will occur if the L2 cache is removed [7]
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
TestBeanUserpackage 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 "); } }
User2package 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; } }
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.