天天看点

Spring(SpringBoot)--解决@Configuration的循环依赖问题

简介

问题来源

        前几天遇到一个循环依赖问题,是RedissonClient这个bean引起的。RedissonClient是由一个配置类(@Configuration注解的类)提供的,这配置类在初始化时(@PostConstruct注解的方法中)去获取RedissonClient这个bean。我在自动注入(@Autowired)RedissonClient时发现报循环依赖异常,应用无法启动。

        为什么配置类要在实例化时获取自己管理的bean呢?因为代码的作者是要把这个配置类作为工具类,这个工具类要用到这个bean。这个工具类是作为Redis分布式锁用的,用到RedissonClient锁相关方法,而我想注入RedissonClient,使用它读写Redis的方法。

        本文介绍这个问题是如何引起的,以及如何解决。

题外话

        刚遇到这个问题时,我其实有点窃喜的。原因是:这个问题我知道快速解决的方案,也想通过这个问题来深入了解循环依赖的原理。

        快速解决的方案是:不进行自动注入,使用时再使用ApplicationContext来获取​

        我之前写过关于循环依赖的博客,对循环依赖也算是比较熟悉了,我感觉我能以比较优雅的方式来解决这个问题。​

        这也说明了写博客的重要性,有问题直接找就行,自己实测过的代码,用起来绝对放心。

问题复现

本处为了简单起见不引用RedisClient依赖,自己写一个简单的类进行示例。

代码

RedissonClient配置类(工具类)

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        RedisLockUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
        // System.out.println("This is AUtil#init");
    }

    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

RedissonClient类

package com.example.entity;

public class RedissonClient {
    public void getLock(String string) {
        System.out.println("RedissonClient#getLock:" + string);
    }

    public void getMap() {
        System.out.println("RedissonClient#getMap");
    }
}      

我这里为了示例 ,直接打印参数,返回值也直接写为void。

启动类

package com.example;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    @Autowired
    private RedissonClient redissonClient;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}      

        如果在其他地方注入RedissonClient,有可能不会循环依赖,有可能会循环依赖,这是与bean的扫描顺序有关系的。本处为了百分百复现,直接在启动类注入。

启动时的报错打印

        这个打印太长了,但都比较重要。所以我将它单独作为一个二级标题,可以快速在其他内容与这个错误打印之间跳转。

Connected to the target VM, address: '127.0.0.1:54749', transport: 'socket'

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2021-09-24 23:30:07.025  INFO 13560 --- [           main] com.example.DemoApplication              : Starting DemoApplication on DESKTOP-QI6B9ME with PID 13560 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot)
2021-09-24 23:30:07.027  INFO 13560 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2021-09-24 23:30:08.312  INFO 13560 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-09-24 23:30:08.321  INFO 13560 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-09-24 23:30:08.321  INFO 13560 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.35]
2021-09-24 23:30:08.430  INFO 13560 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-09-24 23:30:08.430  INFO 13560 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1365 ms
2021-09-24 23:30:08.481  WARN 13560 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through field 'redissonClient'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisLockUtil': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getRedissonClient': Requested bean is currently in creation: Is there an unresolvable circular reference?
2021-09-24 23:30:08.483  INFO 13560 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2021-09-24 23:30:08.493  INFO 13560 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2021-09-24 23:30:08.506 ERROR 13560 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through field 'redissonClient'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisLockUtil': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getRedissonClient': Requested bean is currently in creation: Is there an unresolvable circular reference?
  at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:594) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:895) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143) ~[spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at com.example.DemoApplication.main(DemoApplication.java:14) [classes/:na]
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisLockUtil': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getRedissonClient': Requested bean is currently in creation: Is there an unresolvable circular reference?
  at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:160) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:416) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1788) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:595) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:409) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1306) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  ... 20 common frames omitted
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getRedissonClient': Requested bean is currently in creation: Is there an unresolvable circular reference?
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:219) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1174) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:422) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:352) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:345) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1126) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at com.example.config.RedisLockUtil.init(RedisLockUtil.java:21) ~[classes/:na]
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_201]
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_201]
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_201]
  at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_201]
  at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  ... 41 common frames omitted

2021-09-24 23:30:08.510  WARN 13560 --- [           main] o.s.boot.SpringApplication               : Unable to close ApplicationContext

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'springApplicationAdminRegistrar' defined in class path resource [org/springframework/boot/autoconfigure/admin/SpringApplicationAdminJmxAutoConfiguration.class]: Unsatisfied dependency expressed through method 'springApplicationAdminRegistrar' parameter 1; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:539) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.event.AbstractApplicationEventMulticaster.retrieveApplicationListeners(AbstractApplicationEventMulticaster.java:245) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners(AbstractApplicationEventMulticaster.java:197) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:134) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:403) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:360) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.boot.availability.AvailabilityChangeEvent.publish(AvailabilityChangeEvent.java:81) ~[spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.availability.AvailabilityChangeEvent.publish(AvailabilityChangeEvent.java:67) ~[spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.doClose(ServletWebServerApplicationContext.java:167) ~[spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:978) ~[spring-context-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.boot.SpringApplication.handleRunFailure(SpringApplication.java:814) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:325) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) [spring-boot-2.3.0.RELEASE.jar:2.3.0.RELEASE]
  at com.example.DemoApplication.main(DemoApplication.java:14) [classes/:na]
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.core.env.Environment' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1716) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1272) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1226) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:885) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:789) ~[spring-beans-5.2.6.RELEASE.jar:5.2.6.RELEASE]
  ... 23 common frames omitted

Disconnected from the target VM, address: '127.0.0.1:54749', transport: 'socket'      

原因分析

简要分析

我摘抄上边“启动时的报错打印”的最重要的部分:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoApplication': Unsatisfied dependency expressed through field 'redissonClient'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'redisLockUtil': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'getRedissonClient': Requested bean is currently in creation: Is there an unresolvable circular reference?

        错误大概是这样的:DemoApplication=> RedissonClient=> RedisLockUtil的初始化方法=> getRedissonClient(也就是获取RedissonClient这个bean)。

        用人话来表述:创建DemoApplication这个bean时,依赖了RedissonClient,所以去创建RedissonClient,RedissonClient由RedisLockUtil管理的,所以先创建RedisLockUtil,创建RedisLockUtil时的初始化方法又依赖了RedissonClient,导致循环依赖。

详细分析

实例化顺序

        RedisLockUtil提供RedissonClient这个bean,初始化时却获取不到,这应该是RedissonClient这个bean的实例化在RedisLockUtil的实例化之后导致的。打个断点验证一下:
Spring(SpringBoot)--解决@Configuration的循环依赖问题
启动后发现,果然,先到达21行那个断点。

RedissonClient何时会实例化?

        什么时候会去实例化RedissonClient这个bean呢,整个流程如何?(这里有助于理解bean实例化)。本处进行分析。

        为了代码能走到RedissonClient实例化的地方,我把上边会报错的地方换掉:

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        // RedisLockUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
        System.out.println("This is RedisLockUtil#init");
    }

    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      
打两个断点:
Spring(SpringBoot)--解决@Configuration的循环依赖问题
第1个断点到达:
Spring(SpringBoot)--解决@Configuration的循环依赖问题

解释:先实例化RedisLockUtil,再调用到期init方法,就到了我们的第1个断点。

第2个断点到达:

Spring(SpringBoot)--解决@Configuration的循环依赖问题

 解释:通过factoryBean(即RedisLockUtil)和beanName(即getRedissonClient)来实例化获取RedissonClient这个bean。

        可以发现,两个断点,红框中是最靠近的一个共同的方法。通过它的名字instantiateUsingFactoryMethod可以知道,是通过工厂方法来实例化RedissonClient,这个工厂就是RedisLockUtil。

        已经够清晰了。我注入一个RedissonClient来使用,Spring去容器里查找,发现它受RedisLockUtil管理,于是先实例化它,实例化后调用其初始化方法(@PostConstruct注解的方法),初始化方法里也是想获取RedissonClient,而这时RedissonClient还没有呢!所以就报错!

解决方案

下边5种解决方案。第5种为最好的方案。

公共代码

package com.example.entity;

public class RedissonClient {
    public void getLock(String string) {
        System.out.println("RedissonClient#getLock:" + string);
    }

    public void getMap() {
        System.out.println("RedissonClient#getMap");
    }
}      

这个类都会用到,所以我放到这个公共位置。 

方案1:不自动注入

        SpringBoot启动时会扫描所有被@Component注解的bean,然后去扫描这个bean里边的字段,如果它有@Autowired注解,那就从Spring容器中找它然后将引用地址赋值给这个字段,如果找不到则去实例化它。

        本文的使用@Autowired注入RedissonClient就是这样,启动时去实例化它导致的失败。

        所以思路很简单,我不自动注入,我使用的时候再获取,这样启动时就不会去实例化它。获取的方法为:使用ApplicationContext。想详细了解ApplicationContext的可以见​​此文​​。

写法如下:

ApplicationContext工具类

package com.example.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
 
    @Override
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        ApplicationContextHolder.context = context;
    }
 
    public static ApplicationContext getContext() {
        return context;
    }
}      

启动类

package com.example;

import com.example.entity.RedissonClient;
import com.example.util.ApplicationContextHolder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    private static RedissonClient redissonClient;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        DemoApplication.redissonClient = ApplicationContextHolder.getContext().getBean(RedissonClient.class);
        DemoApplication.redissonClient.getMap();
    }

}      

原来的配置类不变

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        RedisLockUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
        // System.out.println("This is RedisLockUtil#init");
    }

    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

启动测试

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2021-09-25 10:10:23.936  INFO 49756 --- [           main] com.example.DemoApplication              : Starting DemoApplication on DESKTOP-QI6B9ME with PID 49756 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot)
2021-09-25 10:10:23.936  INFO 49756 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2021-09-25 10:10:24.896  INFO 49756 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-09-25 10:10:24.896  INFO 49756 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-09-25 10:10:24.896  INFO 49756 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.35]
2021-09-25 10:10:25.001  INFO 49756 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-09-25 10:10:25.001  INFO 49756 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1028 ms
2021-09-25 10:10:25.316  INFO 49756 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-09-25 10:10:25.431  INFO 49756 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-09-25 10:10:25.586  INFO 49756 --- [           main] com.example.DemoApplication              : Started DemoApplication in 1.944 seconds (JVM running for 2.964)
RedissonClient#getMap      

 可见:正常启动了。

方案2:延迟注入

        Spring有延迟注入的方法,使用的时候再去实例化这个bean,而不是启动时就实例化

package com.example;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Lazy;

@SpringBootApplication
public class DemoApplication {
    @Lazy
    @Autowired
    private RedissonClient redissonClient;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}      

这样就可以启动成功了。 

方案3:用方法获得bean

此方法更简单了,对代码的改动最小。

原来配置类是这样的:

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        RedisLockUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
    }

    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

修改之后: 

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @PostConstruct
    public void init() {
        RedisLockUtil.redissonClient = getRedissonClient();
    }

    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

        可以看到,我直接调用本类里获取bean的方法来拿的bean。这样是可以的,也是可以保证RedissonClient的单例模式,没有任何影响。项目也可以启动成功。

        看到这里,应该要有疑问了。

疑问一:我是怎么知道可以通过这种方法来获得本类中的bean的?

这种用法是我自己在使用shiro框架时,看到其他人这样写的,就知道了这种用法。可见,技术是相通的,了解的多了,思路就会更多。

疑问2:这样为什么仍然可以保证单例模式,这样获取的bean会加入Spring容器的管理吗?

答案是:会加入Spring容器的管理。

接下来,我就分析一下,为什么它可以加入Spring容器的管理。老方法,加断点:

Spring(SpringBoot)--解决@Configuration的循环依赖问题

首先到达第22行的断点:

Spring(SpringBoot)--解决@Configuration的循环依赖问题

然后一直点击箭头所指的位置(单步),一直找到它加入Spring容器的地方:

Spring(SpringBoot)--解决@Configuration的循环依赖问题

        看到红框中的代码,也就是说,它会加入Spring容器,下次有其他地方需要这个bean的时候,会直接获取这个bean,而不是新构造一个了。

        有朋友可能会问,我怎么知道红框中的代码是加入Spring容器的代码。我之前分析过循环依赖的详细原理,所以我一眼就能看出这是第三级缓存。​

方案4:bean放到外部管理

        对于这个RedisLockUtil配置类,它同时也是工具类,它用到了RedissonClient的锁功能,其实RedissonClient的其他读写Redis的方法也巨好用,就像操作JDK中的List、Set、Map一样,RedissonClient实际是实现了JDK中的List、Set、Map,所以使用RedissonClient操作Redis就像我们操作JDK的List、Set、Map一样,超级便利。基于此,我把RedissonClient的配置单独拿出来。

修改之前

配置类(工具类)

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        RedisLockUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
    }

    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

启动类 

package com.example;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    @Autowired
    private RedissonClient redissonClient;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}      

修改之后

配置类(工具类) 

package com.example.util;

import com.example.entity.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

@Configuration
public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    @Autowired
    private ApplicationContext applicationContext;

    @PostConstruct
    public void init() {
        RedisLockUtil.redissonClient = applicationContext.getBean(RedissonClient.class);
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

配置类 

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }
}      

 启动类不变

测试修改后的结果

可以启动成功。

方案5:工具类不加入容器(推荐)

        思考一下:RedisLockUtil是个工具类,我们想用的是它的静态方法,所以,最好不将其加入容器。本方案为最优方案,个人感觉是最优雅的方案。

工具类

package com.example.util;

import com.example.entity.RedissonClient;

public class RedisLockUtil {
    private static RedissonClient redissonClient;
    private static final String PREFIX = "rbac:user:" ;

    static {
        System.out.println("RedisLockUtil.static initializer");
        RedisLockUtil.redissonClient = ApplicationContextHolder.getContext().getBean(RedissonClient.class);
    }

    public static void lock(String name) {
        RedisLockUtil.redissonClient.getLock(PREFIX + name);
    }
}      

RedissonClient

package com.example.entity;

public class RedissonClient {
    public void getLock(String string) {
        System.out.println("RedissonClient#getLock:" + string);
    }

    public void getMap() {
        System.out.println("RedissonClient#getMap");
    }
}      

Redisson配置类

package com.example.config;

import com.example.entity.RedissonClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient getRedissonClient() {
        return new RedissonClient();
    }
}      

ApplicationContext工具类

package com.example.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
    private static ApplicationContext context;
 
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        ApplicationContextHolder.context = context;
    }
 
    public static ApplicationContext getContext() {
        return context;
    }
}      
package com.example;

import com.example.entity.RedissonClient;
import com.example.util.RedisLockUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    @Autowired
    private RedissonClient redissonClient;

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
        RedisLockUtil.lock("Tony");
    }

}      
.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.3.0.RELEASE)

2021-09-25 12:18:03.232  INFO 5528 --- [           main] com.example.DemoApplication              : Starting DemoApplication on DESKTOP-QI6B9ME with PID 5528 (E:\work\Idea_proj\demo_JAVA\demo_SpringBoot\target\classes started by Liu in E:\work\Idea_proj\demo_JAVA\demo_SpringBoot)
2021-09-25 12:18:03.234  INFO 5528 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2021-09-25 12:18:04.380  INFO 5528 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-09-25 12:18:04.388  INFO 5528 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-09-25 12:18:04.388  INFO 5528 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.35]
2021-09-25 12:18:04.499  INFO 5528 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-09-25 12:18:04.499  INFO 5528 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1226 ms
2021-09-25 12:18:04.896  INFO 5528 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2021-09-25 12:18:05.024  INFO 5528 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-09-25 12:18:05.202  INFO 5528 --- [           main] com.example.DemoApplication              : Started DemoApplication in 2.299 seconds (JVM running for 3.382)
RedisLockUtil.static initializer
RedissonClient#getLock:rbac:user:Tony