Probably the most voluminous Spring source code series: circular dependency

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

Keywords: Java Spring Cache

Added by jpr on Fri, 24 Dec 2021 09:11:17 +0200