Are you having a headache due to circular dependencies between SpringBeans?

Circular dependency—circular dependency & amp; circular reference

Circular dependency between Java libraries, that is, Java package circular dependency, refers to when two or more jar files directly or indirectly depend on each other (circular dependency). Circular dependencies can cause problems during compilation or runtime of your application and can be difficult to resolve.

The circular dependency between classes means that A depends on B, and at the same time, B also depends on A. In the spring container, when two or more beans have mutual dependencies (circular references), a circular dependency occurs. For example, A→B and B→A at the same time, or indirect A→C→B and B→A at the same time. In both cases, A and B will be interdependent.

This article talks about the latter, that is, the circular dependency of java beans

Once a program has circular dependencies, an error will be reported probabilistically when the service is started: when Spring tries to create a bean, it will fall into infinite recursion, unable to complete the refresh of the application, and the service startup will fail. The error message is as follows:

– Exception encountered during context initialization – cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘payUserSignBizService’: Bean with name ‘payUserSignBizService’ has been injected into other beans [payMerchantAgreementApiImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching – consider using ‘getBeanNamesOfType\ ‘ with the ‘allowEagerInit’ flag turned off, for example.

Baidu Translation: – Exception encountered during context initialization – Cancel refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean named “payUserSignBizService”: The bean named “PayUserSignBizService” has been used as part of a circular reference to Its original version was injected into other beans [payMerchantAgreementApiImpl], but ended up being wrapped. This means that said other beans do not use the final version of the bean. This is often the result of an overeagerness for type matching – for example, consider using “getBeanNameOfType” with the “allowEagleInit” flag turned off.

The application startup log is as follows (large amount of information, with additional comments and folding):

 1 #Service startup log--->
 2 ...
 3 2023-07-20 22:04:57.427 [TID:N/A] [] [main] INFO c.c.f.a.spring.annotation.SpringValueProcessor:120 - Monitoring key: levy.public.key, beanName: levyCommunicationUtil, method: com. emax.channel.provider.modules.serviceprovider.util.LevyCommunicationUtil.setPlatPublicKey
 4 #The front is INFO, and WARN appears below (circular dependency detected)
 5 2023-07-20 22:04:58.023 [TID:N/A] [] [main] WARN o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext:557 - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'payUserSignBizService': Bean with name 'payUserSignBizService' has been injected into other beans [payMerchantAgreementApiImpl] in its raw version aspart of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
 6 #Various components related to the program start shutdown/destroy, such as: nacos/dubbo/thread pool/hikari database connection
 7 2023-07-20 22:04:58.028 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:147 - com.alibaba.nacos.client.naming.beat.BeatReactor do shutdown begin
 8 2023-07-20 22:04:58.030 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:149 - com.alibaba.nacos.client.naming.beat.BeatReactor do shutdown stop
 9 2023-07-20 22:04:58.030 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:149 - com.alibaba.nacos.client.naming.core.EventDispatcher do shutdown begin
10 2023-07-20 22:05:01.031 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:152 - com.alibaba.nacos.client.naming.core.EventDispatcher do shutdown stop
11 2023-07-20 22:05:01.032 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:370 - com.alibaba.nacos.client.naming.core.HostReactor do shutdown begin
12 2023-07-20 22:05:01.032 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:118 - com.alibaba.nacos.client.naming.core.PushReceiver do shutdownbegin
13 2023-07-20 22:05:04.033 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:122 - com.alibaba.nacos.client.naming.core.PushReceiver do shutdownstop
14 2023-07-20 22:05:04.033 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:132 - com.alibaba.nacos.client.naming.backups.FailoverReactor do shutdown begin
15 2023-07-20 22:05:04.033 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:134 - com.alibaba.nacos.client.naming.backups.FailoverReactor do shutdown stop
16 2023-07-20 22:05:04.034 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:374 - com.alibaba.nacos.client.naming.core.HostReactor do shutdown stop
17 2023-07-20 22:05:04.034 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:715 - com.alibaba.nacos.client.naming.net.NamingProxy do shutdown begin
18 2023-07-20 22:05:04.034 [TID:N/A] [] [main] WARN com.alibaba.nacos.client.naming:72 - [NamingHttpClientManager] Start destroying NacosRestTemplate
19 2023-07-20 22:05:04.035 [TID:N/A] [] [main] WARN com.alibaba.nacos.client.naming:79 - [NamingHttpClientManager] Destruction of the end
20 2023-07-20 22:05:04.035 [TID:N/A] [] [main] INFO com.alibaba.nacos.client.naming:718 - com.alibaba.nacos.client.naming.net.NamingProxy do shutdown stop
21 2023-07-20 22:05:04.062 [TID:N/A] [] [main] INFO o.s.scheduling.concurrent.ThreadPoolTaskExecutor:208 - Shutting down ExecutorService
22 2023-07-20 22:05:04.072 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy17@157ff8f8 was destroying!
23 2023-07-20 22:05:04.072 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy20@4a1abba1 was destroying!
24 2023-07-20 22:05:04.072 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy26@67263db7 was destroying!
25 2023-07-20 22:05:04.072 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy27@4a2bc71f was destroying!
26 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy10@20f94e9a was destroying!
27 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy12@3f213e97 was destroying!
28 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy18@693c7741 was destroying!
29 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy2@7d3b4646 was destroying!
30 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy1@175c4ae5 was destroying!
31 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy14@21bf1b1f was destroying!
32 2023-07-20 22:05:04.073 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy6@2b33e616 was destroying!
33 2023-07-20 22:05:04.074 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy22@c1d9c40 was destroying!
34 2023-07-20 22:05:04.075 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy28@79e3f444 was destroying!
35 2023-07-20 22:05:04.075 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy8@205d6f84 was destroying!
36 2023-07-20 22:05:04.075 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy6@2b33e616 was destroying!
37 2023-07-20 22:05:04.076 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy25@74f54f8e was destroying!
38 2023-07-20 22:05:04.076 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy3@1252d480 was destroying!
39 2023-07-20 22:05:04.076 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy5@59b8a801 was destroying!
40 2023-07-20 22:05:04.076 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy21@1c61f9bf was destroying!
41 2023-07-20 22:05:04.076 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy8@205d6f84 was destroying!
42 2023-07-20 22:05:04.076 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy5@59b8a801 was destroying!
43 2023-07-20 22:05:04.110 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy29@173c0722 was destroying!
44 2023-07-20 22:05:04.110 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy4@12e3cf1c was destroying!
45 2023-07-20 22:05:04.131 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy30@7c1adb8a was destroying!
46 2023-07-20 22:05:04.131 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:293 - org.apache.dubbo.common.bytecode.proxy0@72364a40 was destroying!
47 2023-07-20 22:05:04.178 [TID:N/A] [] [main] INFO o.a.d.c.s.b.f.a.ReferenceAnnotationBeanPostProcessor:305 - class org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor was destroying!
48 2023-07-20 22:05:04.178 [TID:N/A] [] [main] INFO com.zaxxer.hikari.HikariDataSource:350 - HikariPool-1 - Shutdown initiated...
49 2023-07-20 22:05:04.201 [TID:N/A] [] [main] INFO com.zaxxer.hikari.HikariDataSource:352 - HikariPool-1 - Shutdown completed.
50 2023-07-20 22:05:04.204 [TID:N/A] [] [main] INFO org.apache.catalina.core.StandardService:173 - Stopping service [Tomcat]
51 2023-07-20 22:05:04.230 [TID:N/A] [] [main] INFO o.s.b.a.l.ConditionEvaluationReportLoggingListener:142 -
52
53 Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
54 #Stop dubbo service
55 2023-07-20 22:05:04.231 [TID:N/A] [] [main] INFO o.a.d.s.b.c.e.AwaitingNonWebApplicationListener:162 - [Dubbo] Current Spring Boot Application is about to shutdown...
56 #Application service terminated. It is shown that it is caused by circular dependency, and the exception detailed stacktrace is printed.
57 2023-07-20 22:05:04.262 [TID:N/A] [] [main] ERROR org.springframework.boot.SpringApplication:858 - Application run failedorg.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'payUserSignBizService': Bean with name 'payUserSignBizService' has been injected into other beans [payMerchantAgreementApiImpl] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
58 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:622)
59 at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
60 at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
61 at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
62 at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
63 at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
64 at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:849)
65 at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
66 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
67 at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142)
68 at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
69 at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
70 at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
71 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
72 at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
73 at com.emax.channel.provider.ChannelServerApplication.main(ChannelServerApplication.java:38)
74 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
75 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
76 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
77 at java.lang.reflect.Method.invoke(Method.java:498)
78 at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
79 at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
80 at org.springframework.boot.loader.Launcher.launch(Launcher.java:50)
81 at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
82 #At this point, it’s cool. 

View Code

How to resolve circular dependencies?

First of all, we need to clarify the various responsibilities, especially the responsibilities of each level in our layered architecture – do not call each other at the same level, call lower-level APIs.

The following is a circular dependency caused by unclear responsibilities. The solution is to eliminate the interdependence of the two services at the same level, UserService and LoginAccountService. When they call each other’s data, they should call their underlying methods. For example: When UserService#selectUserInfo obtains LoginAccount data, it calls loginAccountMapper#selectByUserId instead.

// ------------------------------- UserService ------------------ ------------------
@Service
public class UserService {
    @Resource
    private UserMapper userMapper;
    @Resource
    private LoginAccountService loginAccountService;


    public UserVO selectUserInfo(int userId) {
        User user = userMapper.selectByUserId(userId);
        if (user == null) {
            throw new ResponseException("User does not exist");
        }
        UserVO userVO = BeanMapper.map(user, UserVO.class);
        LoginAccount loginAccount= loginAccountService.selectByUserId(userId);
        if (loginAccount!=null){
            userVO.setLoginAccount(loginAccount.getLoginAccount());
        }
        return userVO;
    }

    public void validUser(int userId) {
        User user = userMapper.selectByUserId(userId);
        if (user == null) {
            throw new ResponseException("User does not exist");
        }
        if (!"NORMAL".equalsIgnoreCase(user.getStatus())) {
            throw new ResponseException("The user has been disabled");
        }
    }
}

// --------------------------- LoginAccountService -------------------- ---------------
@Service
public class LoginAccountService {
    @Resource
    private UserService userService;
    @Resource
    private LoginAccountMapper loginAccountMapper;

    public LoginAccount login(String loginAccount, String password) {
        // Get login account
        LoginAccount entity = loginAccountMapper.selectByAccount(loginAccount);
        if (entity == null) {
            throw new ResponseException("Account does not exist");
        }
        if (!password.equals(entity.getPassword())) {
            throw new ResponseException("User password is wrong");
        }
        userService.validUser(entity.getUserId());
        return entity;

    }

    public LoginAccount selectByUserId(int userId) {
        return loginAccountMapper.selectByUserId(userId);
    }
}

View Code

Of course, the above circular dependency can be solved from a technical perspective by adding @Lasy and other methods, but the correct approach is to redesign your program based on class responsibilities. You must know that unclear responsibilities will cause many problems, not just the problem of repeated dependence.

Circular dependencies in business callback scenarios

In our enterprise application development, there is a scenario where after one business is completed, a callback notifies another business. This “callback” business scenario may also cause circular dependencies when the program is implemented. We happened to encounter this relatively typical situation and would like to share it with you.

Our middle office channel service is connected to several bank channels. One of the YiLian channels has special requirements. The payee needs to be registered before the payment is made to the individual. Payment can only be made after the registration is completed. See the schematic diagram (design document) below. YiLianPayStrategy is injected into the upper PayUserSignService, PayUserSignService is injected into YiLianSignStrategy, and YiLianSignStrategy is injected into YiLianPayStrategy, resulting in mutual cyclic dependence between YiLianPayStrategy and YiLianSignStrategy.

So, what if we solve the circular dependency in the “callback” business scenario?

There are two solutions:

1. Instead of using bean injection, obtain beans only when a callback is required.

Implementation method 1) The business bean injects the ApplicationContext object, uses ApplicationContext#getBean in the callback code to obtain the target object, and then calls the callback processing method of the target object.

Implementation method 2) Our system encapsulates a SpringContextUtils tool class that implements the ApplicationContextAware interface. This class provides the static method getBean based on ApplicationContext#getBean. Call SpringContextUtils#getBean in the callback code of the business bean to obtain the target object, and then call the callback processing method of the target object.

In the above case, the callback method of YiLianSignStrategy can be changed to this using SpringContextUtils

 /**
     * Encapsulates the relevant processing logic after successful reporting by settlement personnel
     *
     * @param batchNo batch number
     * @param details batch details
     * @return
     */
    @VisibleForTesting
    public int batchUpdateQueryResult(String batchNo, List<PayUserSign> details) {
        if (CollectionUtils.isEmpty(details)) return 0;
        // Persistently update the reporting data record with the results returned by the bank
        int count = payUserSignManager.batchUpdateQueryResult(details);
        if (count > 0 & amp; & amp; details.stream().anyMatch(p -> PayUserSignStatusEnum.isFinalState(p.getStatus()))) {
            // After the report is completed, the payment issuance service will be called back and the issuance will continue.
            details.removeIf(p -> PayUserSignStatusEnum.SUCCESS != p.getStatus());
            if (CollectionUtils.isNotEmpty(details)) {
                ThreadPoolUtil.execute(() -> SpringContextUtils.getBean(YiLianPaymentStrategy.class).signOkCallBack(details));
            }
        }
        return count;
    }

View Code

2. Use spring’s event listener and its publish-subscribe model to achieve decoupling between classes.

In the callback code of the business bean, use ApplicationContext#publishEvent to publish an application event; add @EventListener to the callback processing method of the target object to receive event notifications. Note that event publishing and monitoring processing are synchronous, and asynchronous implementation needs to be considered.

In the above case, I defined a Holder to encapsulate the publishing and subscribing of events.

import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;

/**
 * Use event listeners to eliminate circular dependencies between classes
 * @author zhangguozhan
 * 2023-7-24 18:46
 */
@Slf4j
@Component
public class PayUserSignCallbackEventHolder {
    @Resource
    private ApplicationContext applicationContext;

    @Getter
    private static final class MyEvent extends ApplicationEvent {
        private final List<PayUserSignDTO> userSignList;

        /**
         * Create a new ApplicationEvent.
         *
         * @param source the object on which the event initially occurred (never {<!-- -->@code null})
         */
        public MyEvent(List<PayUserSignDTO> source) {
            super(source);
            this.userSignList = source;
        }
    }

    public void publish(List<PayUserSignDTO> payUserSignList) {
        applicationContext.publishEvent(new MyEvent(payUserSignList));
    }

    @EventListener(classes = MyEvent.class)
    public void consume(MyEvent event) {
        ThreadPoolUtil.getThreadPoolExecutor().execute(() -> applicationContext.getBean(YiLianPaymentStrategy.class).signOkCallBack(event.getUserSignList()));
    }

}

View Code

This article mentions spring’s interface ApplicationContext many times. We have a glimpse of the diagram.

Thanks for reading! The writing and compilation of this article took nearly 4 hours over two nights.