简介
问题来源
前几天遇到一个循环依赖问题,是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的实例化之后导致的。打个断点验证一下: 启动后发现,果然,先到达21行那个断点。
RedissonClient何时会实例化?
什么时候会去实例化RedissonClient这个bean呢,整个流程如何?(这里有助于理解bean实例化)。本处进行分析。
为了代码能走到RedissonClient实例化的地方,我把上边会报错的地方换掉:
打两个断点: 第1个断点到达: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); } }
解释:先实例化RedisLockUtil,再调用到期init方法,就到了我们的第1个断点。
第2个断点到达:
解释:通过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容器的管理。老方法,加断点:
首先到达第22行的断点:
然后一直点击箭头所指的位置(单步),一直找到它加入Spring容器的地方:
看到红框中的代码,也就是说,它会加入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