簡介
問題來源
前幾天遇到一個循環依賴問題,是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