I've read too many articles about circular dependency. I feel that most of them only talk about how circular dependency is implemented. For the time being, I haven't seen any articles that can really explain why L3 cache is used. In order to better analyze the code received by spring, this article is arranged first.
Definition of cache level
Before analyzing spring annotations, we need to reach a consensus on the definition of three levels of cache. Therefore, in this article, we have the following definitions for three levels of cache:
// L1 cache singleton pool private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256); // L2 cache early singleton pool private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16); // L3 cache singleton factory pool private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
L1 cache is used to store the final results. Generally, we have no objection to L1 cache. The key is L2 and L3 cache.
Why do I define Level 2 and level 3 in this way? The cache is defined according to the order in which the cache is created. According to the usual cache definition, it will enter the low-level cache at the earliest, and then enter the high-level cache in turn.
Then why did someone define the early singleton pool as a three-level cache? First of all, there is no problem with this definition. This definition considers the role of cache. We know that once we enter the high-level cache (without updating), we don't need to go to the low-level cache
However, when the bean enters earlySingletonObjects, will it still go to singletonFactories to get it? As for why, we'll talk about it later.
So let's not worry about the definition of L2 and L3 cache. In my opinion, both definitions are correct, but neither is completely accurate. After reading this article, you will know why I say so.
Cyclic dependence
To get to the point, first clarify a few questions
1. What type of cache is required to achieve circular dependency?
Let's start with the answer: two levels of level 1 cache is enough, and level 1 cache and level 2 cache can be completed
2. What are the minimum levels of cache required to achieve circular dependency + aop?
The answer is the same as above: two levels of level 1 cache is enough, and level 1 cache and level 2 cache can be completed
(at this time, some friends will say that I'm too boastful, because most articles and videos are talking about the role of L3 cache in realizing AOP, so we should find out the relationship between AOP and L3 cache. spring does realize AOP through L3 cache, but it doesn't have to use L3 cache to realize AOP.)
3. Why does spring use L3 cache to implement aop?
Postpone the aop process
4. After using L3 cache, if L2 cache is removed, can L1 cache + L3 cache achieve circular dependency + aop latency?
sure
5. Next question, why does spring use L2 cache
For a little bit of performance
So the summary is:
1. If you only want to realize circular dependency, L1 + L2 cache is enough.
2. If you want to realize circular dependency + aop, L1 cache + L2 cache can also be completed.
3. If you want to delay the process of circular dependency + aop+aop, L1 cache + L3 cache can be completed. When introducing L3 cache, some performance will be sacrificed
4. If you want to realize circular dependency + aop+aop process delay + performance optimization, you need L1 cache + L2 cache + L3 cache
Next, I will realize the above requirements by simulating the process of spring, and then analyze the source code of spring
Two levels of cache implement circular dependency (Level 1 + level 2)
Next, I will write an example to prove that L1 + L2 cache can complete circular dependency, and I have also tried to delete the L3 cache in spring. L2 cache changes the factory to an early single instance, which can fully realize circular dependency. If you don't agree with the following example, you can privately trust the code I want me to transform.
Two circular dependent classes:
Handwriting circular dependency based on L1 + L2 cache:
public class SimpleContent { // L1 cache static Map<String,Object> m1 = new HashMap<>(); // L2 cache static Map<String,Object> m2 = new HashMap<>(); public static void main(String[] args) throws Exception { // Create dx DX dx = create(DX.class); DY dy = create(DY.class); System.out.println(); } public static <T> T create(Class<T> beanClass) throws Exception { // Returns directly if the L1 cache exists String beanName = beanClass.getSimpleName().toLowerCase(); Object cache = m1.get(beanName); if (cache != null) { return (T)cache; } T bean = beanClass.newInstance(); // Add to L2 cache m2.put(beanName, bean); injection(bean); // Delete L2 cache m2.remove(beanName, bean); m1.put(beanName, bean); return bean; } public static void injection(Object bean) throws Exception { Class<?> aClass = bean.getClass(); Field[] fields = aClass.getDeclaredFields(); for (Field field : fields) { String fieldName = field.getName(); String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); String fieldClass = field.getType().getName(); Class<?> aClass1 = Class.forName(fieldClass); Object cache = m1.get(field.getName()); if (cache == null) { cache = m2.get(field.getName()); if (cache == null) { cache = create(aClass1); } } Method method = aClass.getMethod(methodName, aClass1); method.invoke(bean, cache); } } }
It can be proved that problem 1, level 1 + Level 3 cache can realize circular dependency
Implement AOP with L1 + L2 cache
If you want to implement AOP in the above code, it is also very simple
public class AopContent { // L1 cache static Map<String,Object> m1 = new HashMap<>(); // L2 cache static Map<String,Object> m2 = new HashMap<>(); public static void main(String[] args) throws Exception { // Create dx AopContent aopContent = new AopContent(); DX dx = aopContent.create(DX.class); DY dy = aopContent.create(DY.class); dy.getDx().getDy().getDx().print(); } public <T> T create(Class<T> beanClass) throws Exception { // Returns directly if the L1 cache exists String beanName = beanClass.getSimpleName().toLowerCase(); Object cache = m1.get(beanName); if (cache != null) { return (T)cache; } T bean = beanClass.newInstance(); // Cache after aop bean = (T) aop(bean); // Add to L2 cache m2.put(beanName, bean); injection(bean); // Delete L2 cache m2.remove(beanName, bean); m1.put(beanName, bean); return bean; } public <T> void injection(T bean) throws Exception { Class<?> clazz = bean.getClass().getSuperclass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Autowired annotationsByType = field.getAnnotation(Autowired.class); if (annotationsByType == null) { continue; } String fieldName = field.getName(); String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); String fieldClass = field.getType().getName(); Class<?> aClass1 = Class.forName(fieldClass); Object cache = m1.get(field.getName()); if (cache == null) { cache = m2.get(field.getName()); if (cache == null) { cache = create(aClass1); } } Method method = clazz.getMethod(methodName, aClass1); method.invoke(bean, cache); } } public Object aop(Object bean) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(new MyMethodInterceptor(bean)); Object proxyObj = enhancer.create(); // Return proxy object return proxyObj; } class MyMethodInterceptor implements MethodInterceptor { Object obj; public MyMethodInterceptor(Object obj) { this.obj = obj; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if ("print".equals(method.getName())) { System.out.println("aop after"); } Object invoke = method.invoke(obj, objects); if ("print".equals(method.getName())) { System.out.println("aop before"); } return invoke; } } }
L1 + L3 cache to achieve AOP latency
public class StandardContent { // L1 cache static Map<String,Object> m1 = new HashMap<>(); // L3 cache static Map<String, ObjectFactory<Object>> m3 = new HashMap<>(); public static void main(String[] args) throws Exception { // Create dx StandardContent content = new StandardContent(); DX dx = content.create(DX.class); DY dy = content.create(DY.class); System.out.println(dx.getDy().getDx() == dy.getDx()); dy.getDx().getDy().getDx().print(); } public <T> T create(Class<T> beanClass) throws Exception { // Returns directly if the L1 cache exists String beanName = beanClass.getSimpleName().toLowerCase(); Object cache = getSingleton(beanName); if (cache != null) { return (T)cache; } T bean = beanClass.newInstance(); // Add to L3 cache m3.put(beanName, ()->{ return aop(bean); }); T exposedObject = bean; injection(bean); Object earlySingletonReference = getSingleton(beanName); if (exposedObject == bean) { exposedObject = (T) earlySingletonReference; } else { // If it is dependent on other bean s, an error is reported } // L3 cache if (m3.containsKey(beanName)) { m3.remove(beanName); } // Enter L1 cache m1.put(beanName, bean); return exposedObject; } public <T> void injection(T bean) throws Exception { Class<?> clazz = bean.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Autowired annotationsByType = field.getAnnotation(Autowired.class); if (annotationsByType == null) { continue; } String fieldName = field.getName(); String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); String fieldClass = field.getType().getName(); Class<?> aClass1 = Class.forName(fieldClass); Object cache = getSingleton(field.getName()); if (cache == null) { cache = create(aClass1); } Method method = clazz.getMethod(methodName, aClass1); method.invoke(bean, cache); } } public Object aop(Object bean) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(new MyMethodInterceptor(bean)); Object proxyObj = enhancer.create(); // Return proxy object return proxyObj; } class MyMethodInterceptor implements MethodInterceptor { Object obj; public MyMethodInterceptor(Object obj) { this.obj = obj; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if ("print".equals(method.getName())) { System.out.println("aop after"); } Object invoke = method.invoke(obj, objects); if ("print".equals(method.getName())) { System.out.println("aop before"); } return invoke; } } public Object getSingleton(String beanName) { Object bean = m1.get(beanName); if (bean != null) { return bean; } ObjectFactory<Object> objectObjectFactory = m3.get(beanName); if (objectObjectFactory != null) { bean = objectObjectFactory.getObject(); return bean; } return null; } }
aop + performance optimization with L1 + L2 + L3 cache
public class EnhancerContent { // L1 cache static Map<String,Object> m1 = new HashMap<>(); // L2 cache static Map<String,Object> m2 = new HashMap<>(); // L3 cache static Map<String, ObjectFactory<Object>> m3 = new HashMap<>(); public static void main(String[] args) throws Exception { // Create dx EnhancerContent content = new EnhancerContent(); DX dx = content.create(DX.class); DY dy = content.create(DY.class); System.out.println(dx.getDy().getDx() == dy.getDx()); dy.getDx().getDy().getDx().print(); } public <T> T create(Class<T> beanClass) throws Exception { // Returns directly if the L1 cache exists String beanName = beanClass.getSimpleName().toLowerCase(); Object cache = getSingleton(beanName); if (cache != null) { return (T)cache; } T bean = beanClass.newInstance(); // Add to L3 cache m3.put(beanName, ()->{ return aop(bean); }); T exposedObject = bean; injection(bean); exposedObject = initializeBean(bean); Object earlySingletonReference = getSingleton(beanName);; if (exposedObject == bean) { exposedObject = (T) earlySingletonReference; } else { // If it is dependent on other bean s, an error is reported } // Delete L2 cache and L3 cache if (m2.containsKey(beanName)) { m2.remove(beanName); } if (m3.containsKey(beanName)) { m3.remove(beanName); } // Enter L1 cache m1.put(beanName, bean); return exposedObject; } public <T> void injection(T bean) throws Exception { Class<?> clazz = bean.getClass(); Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { Autowired annotationsByType = field.getAnnotation(Autowired.class); if (annotationsByType == null) { continue; } String fieldName = field.getName(); String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); String fieldClass = field.getType().getName(); Class<?> aClass1 = Class.forName(fieldClass); Object cache = getSingleton(field.getName()); if (cache == null) { cache = create(aClass1); } Method method = clazz.getMethod(methodName, aClass1); method.invoke(bean, cache); } } /** * Simulate bean initialization process * @param bean * @param <T> * @return */ public <T> T initializeBean(T bean) { return bean; } public Object aop(Object bean) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(bean.getClass()); enhancer.setCallback(new MyMethodInterceptor(bean)); Object proxyObj = enhancer.create(); // Return proxy object return proxyObj; } class MyMethodInterceptor implements MethodInterceptor { Object obj; public MyMethodInterceptor(Object obj) { this.obj = obj; } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if ("print".equals(method.getName())) { System.out.println("aop after"); } Object invoke = method.invoke(obj, objects); if ("print".equals(method.getName())) { System.out.println("aop before"); } return invoke; } } public Object getSingleton(String beanName) { Object bean = m1.get(beanName); if (bean != null) { return bean; } bean = m2.get(beanName); if (bean != null) { return bean; } ObjectFactory<Object> objectObjectFactory = m3.get(beanName); if (objectObjectFactory != null) { bean = objectObjectFactory.getObject(); m3.remove(beanName); m2.put(beanName, bean); return bean; } return null; } }
I believe this article has solved many doubts about circular dependency for you. In the next article, we will analyze the implementation in spring in detail