Few people talk about circular dependencies in Spring, let's learn together today!

There are too many blogs about Spring cyclic dependencies on the Internet. Many of them are analyzed in depth and written very carefully. They even drew sequence diagrams and flowcharts to help readers understand. After reading it, I felt that I understood, but closed When I put my eyes on it, I always feel that I haven't fully understood it, and I still feel that there are still one or two hurdles to pass. It is really difficult for someone like me who is a little stupid. At that time, I was thinking, if one day, I understand Spring cyclic dependencies, I must write a blog in my own way to help everyone understand better. After I understand, I have been thinking about how to write and how to write. It's easier to understand. Just a few days ago, I figured it out. It should be easier to understand. Before writing this blog, I read a lot of blogs about Spring circular dependencies. There should be no such explanations on the Internet, so let's start now.

What is circular dependency

In a nutshell: the two depend on each other.

In development, this situation may often occur, but we usually do not notice that the two classes or even multiple classes we wrote are dependent on each other, why not notice? Of course it is because there is no error, and there is no problem at all. If an error is reported or a problem occurs, will we still not notice it? All this is the credit of Spring, which silently solves the circular dependency problem for us later.

As follows:

@Configuration
@ComponentScan
public class AppConfig {
}
@Service
public class AuthorService {
    @Autowired
    BookService bookService;
}
@Service
public class BookService {
    @Autowired
    AuthorService authorService;
}
public class Main {
    public static void main(String[] args) {
        ApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(AppConfig.class);

        BookService bookService = (BookService) annotationConfigApplicationContext.getBean("bookService");
        System.out.println(bookService.authorService);

        AuthorService authorService = (AuthorService) annotationConfigApplicationContext.getBean("authorService");
        System.out.println(authorService.bookService);
    }
}

operation result:

com.codebear.springcycle.AuthorService@63376bed
com.codebear.springcycle.BookService@4145bad8

You can see that AuthorService is required in BookService, and BookService is required in AuthorService. Similar to this is called circular dependency, but the magic is that there is no problem at all.

Of course, some friends may not get the magic of it. As for the magic of it, we will talk about it later.

Can Spring solve any circular dependencies?

No way.

If it is a circular dependency of the prototype bean, Spring cannot solve it:

@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class BookService {
    @Autowired
    AuthorService authorService;
}
@Service
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class AuthorService {
    @Autowired
    BookService bookService;
}

After starting, the scary red font appeared on the console:

image.png

If it is a circular dependency injected by construction parameters, Spring cannot resolve:

@Service
public class AuthorService {
    BookService bookService;

    public AuthorService(BookService bookService) {
        this.bookService = bookService;
    }
}
@Service
public class BookService {

    AuthorService authorService;

    public BookService(AuthorService authorService) {
        this.authorService = authorService;
    }
}

Still annoying red font:

image.png

Can the circular dependency be closed? Yes, Spring provides this feature, we need to write:

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.setAllowCircularReferences(false);
        applicationContext.register(AppConfig.class);
        applicationContext.refresh();
    }
}

Run it again, and an error is reported:

image.png

It should be noted that we cannot write:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); applicationContext.setAllowCircularReferences(false);

If you write this way, the program executes the first line of code, and the entire Spring container has been initialized. If you don't allow circular dependencies, it won't help.

What is the magic of circular dependence

There are many small partners who may not think how amazing it is to be able to rely on circularly. That is because they don’t know where the contradictions are. Next, let’s talk about this problem: When beanA and beanB are circularly dependent:

Create beanA and find dependency on beanB; create beanB and find dependency on beanA; create beanA and find dependency on beanB; create beanB and find dependency on beanA. ... Okay, it's an endless loop. The contradiction of circular dependency is that to create beanA, it needs beanB, and creating beanB needs beanA, and then neither bean can be created.

How to easily solve circular dependencies

If you have ever read Spring's blog on resolving circular dependencies, you should know that there are several Maps. A Map contains the most complete objects called singletonObjects, and a Map contains objects exposed in advance, called earlySingletonObjects.

Here, we must first explain these two things:

singletonObjects: Singleton pool, which stores beans that have experienced the full life cycle of Spring, and the dependencies of the beans have been filled. earlySingletonObjects: A map of objects exposed in advance, which stores objects that have just been created, beans that have not experienced the full life cycle of Spring, and the dependencies of the beans have not yet been filled. We can do this:

When we finish creating beanA, we put ourselves in earlySingletonObjects, find that we need beanB, and then create beanB; when we create beanB, we put ourselves in earlySingletonObjects, and find that we need beanA, and then we go to fart Create beanA; before creating beanA, go to earlySingletonObjects to check and find that you have been created, and return yourself; beanB gets beanA, beanB is created, put yourself into singletonObjects; beanA can go to singletonObjects to get it BeanB, beanA is also created, put yourself in singletonObjects. The whole process is over. Let's implement this function below: First, define a custom annotation. If the annotation is marked on the field, it means that it needs to be Autowired:

@Retention(RetentionPolicy.RUNTIME)
public @interface CodeBearAutowired {
}

Create two more circularly dependent classes:

public class OrderService {
    @CodeBearAutowired
    public UserService userService;
}
public class UserService {
    @CodeBearAutowired
    public OrderService orderService;
}

Then there is the core, creating objects, filling in properties, and solving the problem of Spring circular dependencies:

public class Cycle {
    // 单例池,里面放的是完整的bean,已完成填充属性
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 存放的是提前暴露出来的bean,没有经历过spring完整的生命周期,没有填充属性
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    // 在Spring中,这个map存放的是beanNam和beanDefinition的映射关系
    static Map<String, Class<?>> map = new HashMap<>();
    static {
        map.put("orderService", OrderService.class);
        map.put("userService", UserService.class);
    }
    // 如果先调用init方法,就是预加载,如果直接调用getBean就是懒加载,两者的循环依赖问题都解决了
    public void init() {
        for (Map.Entry<String, Class<?>> stringClassEntry : map.entrySet()) {
            createBean(stringClassEntry.getKey());
        }
    }

    public Object getBean(String beanName) {
        // 尝试从singletonObjects中取,
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 尝试从earlySingletonObjects取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        return createBean(beanName);
    }

    private Object createBean(String beanName) {
        Object singletonObject;

        try {
            // 创建对象
            singletonObject = map.get(beanName).getConstructor().newInstance();

            // 把没有完成填充属性的半成品 bean 放入earlySingletonObjects
            earlySingletonObjects.put(beanName, singletonObject);

            // 填充属性
            populateBean(singletonObject);

            // bean创建成功,放入singletonObjects
            this.singletonObjects.put(beanName, singletonObject);

            return singletonObject;
        } catch (Exception ignore) {
        }
        return null;
    }

    private void populateBean(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(CodeBearAutowired.class) != null) {
                Object value = getBean(field.getName());
                try {
                    field.setAccessible(true);
                    field.set(object, value);
                } catch (IllegalAccessException ignored) {
                }
            }
        }
    }
}

Preload call:

public class Main {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        cycle.init();
        UserService userService = (UserService) cycle.getBean("userService");
        OrderService orderService = (OrderService) cycle.getBean("orderService");
        System.out.println(userService.orderService);
        System.out.println(orderService.userService);
    }
}

operation result:

com.codebear.cycleeasy.OrderService@61baa894
com.codebear.cycleeasy.UserService@b065c63

Lazy loading call:

public class Main {
    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        UserService userService = (UserService) cycle.getBean("userService");
        OrderService orderService = (OrderService) cycle.getBean("orderService");
        System.out.println(userService.orderService);
        System.out.println(orderService.userService);
    }
}

operation result:

com.codebear.cycleeasy.OrderService@61baa894
com.codebear.cycleeasy.UserService@b065c63

Why can't solve the circular dependency injected by prototype and construction method

In the above, we wrote the code to solve the circular dependency by ourselves. We can see that the core is to use a map to solve this problem. This map is equivalent to a cache.

Why can we do this, because our bean is singleton, and field injection (setter injection), singleton means that the object only needs to be created once, which can be taken out of the cache later, field injection means that we do not need Call the constructor to inject.

If it is a prototype bean, it means that you have to create an object every time and you cannot use the cache; if it is a constructor injection, it means you need to call the constructor injection, and you cannot use the cache.

What if I need aop?

Our above scheme looks very good, but there is still a problem, if our bean is created, we need to do some processing, what should we do? Maybe, you didn’t understand the meaning of this sentence, let’s say it more clearly, if beanA and [beanB's proxy object] are circularly dependent, or [beanA's proxy object] and beanB are circularly dependent, or [beanA's proxy object] and [ Proxy object of beanB] cyclic dependency, what to do?

The creation of proxy objects mentioned here is only one possibility of "processing".

In this case, we can't throw the created object directly into the cache? If we do this, if [beanA's proxy object] and [beanB's proxy object] are cyclically dependent, the beanB in beanA we finally obtain is still beanB, not the proxy object of beanB.

Smart you, you must be thinking, isn't it simple: After we create an object, we judge whether the object needs a proxy, if we need a proxy, create a proxy object, and then put the proxy object in earlySingletonObjects, isn't it OJ8K? like this:

private Object createBean(String beanName) { Object singletonObject;

try {
    // 创建对象
    singletonObject = map.get(beanName).getConstructor().newInstance();

    // 创建bean的代理对象
    /**
     * if( 需要代理){
     *     singletonObject=创建代理对象;
     *
     * }
     */

    // 把没有完成填充属性的半成品 bean 放入earlySingletonObjects
    earlySingletonObjects.put(beanName, singletonObject);

    // 填充属性
    populateBean(singletonObject);

    // bean创建成功,放入singletonObjects
    this.singletonObjects.put(beanName, singletonObject);

    return singletonObject;
} catch (Exception ignore) {
}
return null;

}

This is indeed possible, but it violates Spring’s original intention. Spring’s original intention is to go to aop in the last few steps of the bean life cycle. If you do this as mentioned above, it means that once the object is created, Spring will go. aop, which violates Spring's original intention, so Spring did not do so.

However, if there is a circular dependency on aop bean, there is nothing to do. You can only go to aop first, but if there is no circular dependency, Spring does not want to perform aop here, so Spring introduces Map<String, ObjectFactory<? >>, ObjectFactory is a functional interface, which can be understood as a factory method. When the object is created, put the [factory method for obtaining this object] into this map, and when the circular dependency occurs, execute this [get this Object factory method], get the processed object.

The following code is released directly:

public class Cycle {
    // 单例池,里面放的是完整的bean,已完成填充属性
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    // 存放的是 加工bean的工厂方法
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();

    // 存放的是提前暴露出来的bean,没有经历过spring完整的生命周期,没有填充属性
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();

    private final Set<String> singletonsCurrentlyInCreation = new HashSet<>();

    static Map<String, Class<?>> map = new HashMap<>();

    static {
        map.put("orderService", OrderService.class);
        map.put("userService", UserService.class);
    }

    public void init() {
        for (Map.Entry<String, Class<?>> stringClassEntry : map.entrySet()) {
            createBean(stringClassEntry.getKey());
        }
    }

    private Object createBean(String beanName) {
        Object instance = null;
        try {
            instance = map.get(beanName).getConstructor().newInstance();
        } catch (Exception ex) {
        }


        Object finalInstance = instance;
        this.singletonFactories.put(beanName, () -> {
            // 创建代理对象
            return finalInstance;
        });

        populateBean(instance);

        this.singletonObjects.put(beanName, instance);
        return instance;
    }

    public Object getBean(String beanName) {
        // 尝试从singletonObjects中取,
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 尝试从earlySingletonObjects取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject != null) {
            return singletonObject;
        }

        // 尝试从singletonFactories取出工厂方法
        ObjectFactory<?> objectFactory = this.singletonFactories.get(beanName);
        if (objectFactory != null) {
            singletonObject = objectFactory.getObject();
            this.earlySingletonObjects.put(beanName, singletonObject);
            return singletonObject;
        }

        return createBean(beanName);
    }

    private void populateBean(Object object) {
        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.getAnnotation(CodeBearAutowired.class) != null) {
                Object value = getBean(field.getName());
                try {
                    field.setAccessible(true);
                    field.set(object, value);
                } catch (IllegalAccessException ignored) {
                }
            }
        }
    }
}

Calling method:

 public static void main(String[] args) {
        Cycle cycle = new Cycle();
        cycle.init();
        System.out.println(((UserService) cycle.getBean("userService")).orderService);
        System.out.println(((OrderService) cycle.getBean("orderService")).userService);
    }

operation result:

com.codebear.cycles.OrderService@49e4cb85
com.codebear.cycles.UserService@2133c8f8

Can the second-level cache solve the circular dependency, and what is the use of the third-level cycle?

My views may be very different from the mainstream views on the Internet. As to whether my views are right or wrong, please judge for yourself.

The second-level cache can solve the circular dependency, even if the aop bean is cyclically dependent, as we have mentioned above, we can create the object directly, create the proxy object directly, and put the proxy object into the second-level cache, so that what we get from the second-level cache must be aop bean, not the bean itself.

What is the use of the three-level cache? The mainstream view on the Internet is to solve the circular dependency, and also for the efficiency. In order to solve the circular dependency, we have discussed it above. My opinion is that the secondary cache can already solve the circular dependency. Let us think about it. Does efficiency matter?

My point of view is that it doesn’t matter. The reason is as follows: We put the [Factory Method of Obtaining Object] into the map

  • If there is no circular dependency, this map is not used at all, and it has nothing to do with efficiency;
  • If it is an ordinary bean cyclic dependency, the three-level cache directly returns the bean, which has nothing to do with efficiency;
  • If it is aop bean cyclic dependency, if there is no third-level cache, directly create a proxy object and put it into the second-level cache. If there is a third-level cache, you still need to create a proxy object, but the timing of the two is different, and it has nothing to do with efficiency.
  • With the foundation of this blog, when you read other blogs about Spring circular dependencies, it should be easier, because after all, we have solved circular dependencies by ourselves. Spring circular dependencies are just further encapsulation and Improve.

At last

Reply to the data by private message to receive a summary of Java interview questions from a major manufacturer + Alibaba Taishan manual + a learning guide for knowledge points + a summary of Java core knowledge points in a 300-page pdf document!

The content of these materials are all the knowledge points that the interviewer must ask during the interview. The chapter includes many knowledge points, including basic knowledge, Java collections, JVM, multi-threaded concurrency, spring principles, microservices, Netty and RPC, Kafka , Diary, design pattern, Java algorithm, database, Zookeeper, distributed cache, data structure, etc.

Author: CodeBear Original: https://0x9.me/EL7No

file

Guess you like

Origin blog.csdn.net/weixin_46577306/article/details/107450755