In his previous article “Spring Source code – Loop dependencies (with 25 debug screenshots)”, Grass also looked into loop dependencies. But today or in the cycle of dependence on the pit, is really arranged clearly. Here I will describe the process of stomping, mainly covering three topics: template methods, Bean load order, and loop dependencies.

The cause of this pit starts from the template method. Recently, I wrote A requirement that the Manager needs to process three types of data: A, B and C. The processing process is similar and more, but there are some differences in data types and details. In order to reuse, I naturally thought of using the template method to rewrite, which was my first attempt to use the template method in Spring, and I stumbled.

Let me briefly recreate the scenario where there is a fun method in Manager that processes the data based on the type passed in using the corresponding utility class, UtilA, UtilB, and UtilC, which are injected through attributes. There is also a preHandle method in the Manager that does some data preprocessing and will be used later, but not now.

@Component
public class Manager {

	@Autowired
	private UtilA utilA;

	@Autowired
	private UtilB utilB;

	@Autowired
	private UtilC utilC;

	public void fun(String type, String data) {
		switch (type) {
			case "A" :
				utilA.process(data);
				break;
			case "B" :
				utilB.process(data);
				break;
			case "C":
				utilC.process(data);
				break;
			default: utilA.doProcess(data); }}public String preHandle(String data) {
		// I am a fake preprocessor... I didn't do anything, huh
		returndata; }}Copy the code

UtilA, UtilB and UtilC all inherit from a Template class, Template. The Process method is a template method that processes data and invokes the doProcess abstract method, whose logic will be implemented by UtilA, UtilB, and UtilC.

public abstract class Template {

	public void process(String data) {
        // I am a template method... I can do a lot of work without my sons having to write it all
        // Special work is done by doProcess sons
		doProcess(data);
	}

	protected abstract void doProcess(String data);

}
Copy the code

Take UtilA, as follows:

@Component
public class UtilA extends Template {
	@Override
	protected void doProcess(String data) {
		System.out.println("This is A, processing data:"+ data); }}Copy the code

We’ve written the template method, no problem. But now I have a requirement that I call the preHandle method of the Manager in the process method (don’t ask me why I don’t just copy it, it’s more complicated and there are many other methods and dependencies used in preHandle, so it’s best to reuse them), So you need to get an instance of the Manager in Template, but Template is an abstract class that can’t even be instantiated into a Bean, let alone dependency injection. My solution here is to introduce a SpringContextHolder, which is a wrapper class for ApplicationContext, to get the Manager instance, defined as follows:

@Component
public class SpringContextHolder implements ApplicationContextAware {

	private static ApplicationContext applicationContext;

	@Override
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		applicationContext = context;
	}

	public static <T> T getBean(String name) {
		return(T) applicationContext.getBean(name); }}Copy the code

The Template class is then overridden to get the Manager instance in the constructor, and the preHandle method can be called in the Process method.

public abstract class Template {

	private Manager manager;

	public Template(a) {
		manager = SpringContextHolder.getBean("manager");
	}

	public void process(String data) {
		manager.preHandle(data);
		doProcess(data);
	}

	protected abstract void doProcess(String data);

}
Copy the code

Here is the main function, now running:

public class Main {
	public static void main(String[] args) {
		ApplicationContext context = new ClassPathXmlApplicationContext("spring-context.xml");
		Manager manager = (Manager) context.getBean("manager");
		manager.fun("A"."123"); }}Copy the code

Call fun on Manager, and since we pass in an argument of “A”, utilA will be used to process the data. All looks fine, but then comes the first problem. When the container is started, UtilA is loaded and instantiated by calling the constructor, where we specify to get the Manager via SpringContextHolder’s getBean method, Since SpringContextHolder has not yet been loaded, applicationContext is null and therefore a null pointer is reported. Make sure SpringContextHolder is loaded before UtilA is loaded. That is, control the loading order of beans. A ** @dependson ** annotation on UtilA with the parameter “springContextHolder” will be loaded when UtilA is loaded.

@Component
@DependsOn("springContextHolder")
public class UtilA extends Template {
	@Override
	protected void doProcess(String data) {
		System.out.println("This is A, processing data:"+ data); }}Copy the code

That’s it. We’re good to go. When I uploaded the code to the test environment, the application wouldn’t start. When you look at the log, there is a cyclic dependency and the Spring container can’t get up. On closer inspection, circular dependencies do occur. UtilA is injected through an attribute in Manager, while UtilA’s parent, Template, gets Manger through getBean in its constructor. The question is, why can I run it locally, but the test environment is reporting an error? The finer point is why cyclic dependencies do not occur locally but in the test environment. If you’ve seen the Spring source – circular dependencies (25 debugging screenshot attached) or with circular dependencies, we must know if X and Y are attributes into circular dependencies, Spring will be resolved through the three-level cache, not an error, and for X and Y are constructor injection circular dependencies, Spring is unable to solve, Complains. The situation is that I have property injection in one place and constructor injection in the other. The assumption is that Manager is loaded first and property injection is done first, so no error is reported, whereas UtilA is loaded first and constructor injection is done first in the test environment, so a loop-dependent error occurs. ** Why are the loading orders different for the two environments? Spring automatically scans the load order for hashCode, which is operating system specific, so the load order may differ depending on the operating system of the two environments. This is why the local environment and the test environment run differently.

Here is how to solve this problem, there are two general ideas:

  1. Remove constructor dependencies;
  2. Control the loading sequence.

First, instead of getting the dependencies in the constructor, we can get them in the process method:

public abstract class Template {

	private Manager manager;

	public Template(a) {}public void process(String data) {
		manager = SpringContextHolder.getBean("manager");
		manager.preHandle(data);
		doProcess(data);
	}

	protected abstract void doProcess(String data);

}
Copy the code

Second, control Manager is always loaded before UtilA using the @dependson annotation:

@Component
@DependsOn({"springContextHolder", "manager"})
public class UtilA extends Template {
	@Override
	protected void doProcess(String data) {
		System.out.println("This is A, processing data:"+ data); }}Copy the code

I ended up with method 1, which I thought would only require one change, and method 2, which would require three subclasses and a lot of changes. If you encounter this kind of problem, or according to their own actual situation to solve.

Finally, there are two reasons for stepping on pits:

  1. When learning loop dependency, we only consider that both X and Y use attribute injection or constructor injection, and do not consider whether the problem of loop dependency will occur when X uses attribute injection and Y uses constructor injection.
  2. Lack of attention to the loading order of beans. In order for the program to run correctly, the loading sequence of beans needs to be correct.