在网上如何找做网站的人,做网站如何兼职,离莞来莞最新政策,上海网页设计多少钱问题呈现
项目性能优化#xff0c;需要将本地内存#xff08;JVM内存#xff09;替换为本地Redis#xff08;同一个Pod中的Container#xff09;#xff0c;降低JVM内存和GC的压力#xff0c;同时引入了JetCache简化和统一使用#xff08;对JetCache也做了扩展#x…问题呈现
项目性能优化需要将本地内存JVM内存替换为本地Redis同一个Pod中的Container降低JVM内存和GC的压力同时引入了JetCache简化和统一使用对JetCache也做了扩展支持了本地Redis。引入JetCache后项目启动就会报循环依赖的错误错误入口则是项目中使用PostConstruct调用JetCache加载预热缓存的地方错误堆栈如下
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name globalCacheConfig defined in class path resource [cn/jojo/edu/jetcache/autoconfigure/JetCacheAutoConfiguration.class]: Circular depends-on relationship between globalCacheConfig and redissonAutoInitat org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:305) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1155) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveBean(DefaultListableBeanFactory.java:416) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:349) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBean(DefaultListableBeanFactory.java:342) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at cn.jojo.edu.jetcache.anno.field.LazyInitCache.init(LazyInitCache.java:83) ~[jetcache-anno-ss2.6.9-20241025.105436-2.jar:?]at cn.jojo.edu.jetcache.anno.field.LazyInitCache.checkInit(LazyInitCache.java:66) ~[jetcache-anno-ss2.6.9-20241025.105436-2.jar:?]at cn.jojo.edu.jetcache.anno.field.LazyInitCache.put(LazyInitCache.java:159) ~[jetcache-anno-ss2.6.9-20241025.105436-2.jar:?]at cn.jojo.edu.malacca.integration.cache.AbstractLocalRedisCacheService.put2Cache(AbstractLocalRedisCacheService.java:92) ~[classes/:?]at cn.jojo.edu.malacca.integration.cache.AbstractLocalRedisCacheService.forceRefreshCache(AbstractLocalRedisCacheService.java:140) ~[classes/:?]at cn.jojo.edu.malacca.api.server.config.PreheatInit.execute(PreheatInit.java:96) ~[classes/:?]at cn.jojo.edu.malacca.api.server.config.PreheatInit.lambda$init$0(PreheatInit.java:73) ~[classes/:?]at cn.jojo.infra.sdk.context.request.RequestContextHolder.setContext(RequestContextHolder.java:162) ~[microservice-sdk-1.7.29.jar:?]at cn.jojo.edu.malacca.api.server.config.PreheatInit.init(PreheatInit.java:71) ~[classes/:?]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_65]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_65]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_65]at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_65]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleElement.invoke(InitDestroyAnnotationBeanPostProcessor.java:389) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:333) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:157) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]... 18 more看错误日志问题是出在预热调用LazyInitCache.put方法时该方法内部首先会获取GlobalCacheConfig对象让其优先初始化就是在初始化该对象时报的循环依赖错误 存在以下几个奇怪的问题
错误提示的是globalCacheConfig和redissonAutoInit产生了循环依赖但是看GlobalCacheConfig本身并没有依赖其它复杂的对象只是在初始化时依赖了SpringConfigProvider、JetCacheProperties和AutoConfigureBeansglobalCacheConfig和redissonAutoInit的依赖是如何产生的呢 为什么在PostConstruct中预热会报错但在EventListener(ApplicationReadyEvent.class)中预热却不会报错为什么配置启用了多个缓存组件才会报错只启用一个缓存组件却不会Spring通过三级缓存解决了非构造函数注入产生的循环依赖那为什么这个循环依赖没有被解决呢
分析过程
依赖关系分析
RedissonAutoInit对GobalCacheConfig的依赖比较好发现从图中可以看到是父类中引用了ConfigProvider在ConfigProvider中又引用了GlobalCacheConfig不过从图中也无法找出GobalCacheConfig对RedissonAutoInit的依赖从何而来。 于是查看报循环依赖错误的源码 这部分源码位于AbstractBeanFactory.doGetBean方法中在创建Bean之前会对有设置dependsOn属性的BeanDefinition以下简称BD校验是否有循环依赖但是GlobalCacheConfig类中并没有标记DependsOn注解还有哪里可以设置呢不难想到Spring本身提供的一些扩展点是可以修改BD属性的比如BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor最终找到一个BeanFactoryPostProcessor的实现类BeanDependencyManager
public class BeanDependencyManager implements BeanFactoryPostProcessor {Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {String[] autoInitBeanNames beanFactory.getBeanNamesForType(AbstractCacheAutoInit.class, false, false);if (autoInitBeanNames ! null) {BeanDefinition bd beanFactory.getBeanDefinition(JetCacheAutoConfiguration.GLOBAL_CACHE_CONFIG_NAME);String[] dependsOn bd.getDependsOn();if (dependsOn null) {dependsOn new String[0];}int oldLen dependsOn.length;dependsOn Arrays.copyOf(dependsOn, dependsOn.length autoInitBeanNames.length);System.arraycopy(autoInitBeanNames,0, dependsOn, oldLen, autoInitBeanNames.length);bd.setDependsOn(dependsOn);}}}在这里将获取到的AbstractCacheAutoInit的子类都设置到了GlobalCacheConfig的dependsOn属性中题外话这里并不是所有实现了AbstractCacheAutoInit的子类都会设置进去还要看是否满足条件Jetache提供了JetCacheCondition只有配置启用了的缓存组件对应的Init才会初始化并设置到dependsOn中。 至此循环依赖就产生了。
循环依赖错误根因分析
有了循环依赖但为什么Spring的三级缓存没有解决该问题以及为什么是在某些条件下才会报错呢本节就对剩余几个问题进行分析。 上面的时序图是在启用了Caffeine和Redisson缓存且使用PostConstruct预热缓存的前提下的启动初始化过程首先BeanDependencyManager获取到CaffeineAutoInit和RedissonAutoInit对象并设置到GlobalCacheConfig BD的dependsOn属性中。接着在PostConstruct标记的方法执行调用put预热缓存在put中会优先调用getBean初始化GlobalCacheConfig。 GlobalCacheConfig初始化时就会进入到这段代码循环依次判断依赖的对象是否有循环依赖以及调用getBean对其进行初始化。 首先是CaffeineAutoInit在判断无循环依赖后会注册CaffeineAutoInit - GlobalCacheConfig的依赖关系表示CaffeineAutoInit依赖GlobalCacheConfig对象下文同理注册依赖关系后调用getBean初始化CaffeineAutoInit时其实也会进入到这个方法只不过该对象没有dependsOn所以直接跳过了这段代码进入到实例化的逻辑 熟悉Spring初始化逻辑的就知道该方法中创建完Bean就会对其进行依赖注入而CaffeineAutoInit的父类中依赖了SpringConfigProvider对象 因此又会实例化SpringConfigProvider对象同样的该对象中又依赖了GlobalCacheConfig所以又再次触发GlobalCacheConfig的创建所以会再一次判断GlobalCacheConfig depnedsOn的对象和自己是否有循环依赖。 因为CaffeineAutoInit在前面已经初始化完成了所以这次只是简单判断一下接着判断并初始化RedissonAutoInit这里又会注册RedissonAutoInit - GlobalCacheConfig的依赖关系然后又依赖注入SpringConfigProvider依赖注入完成后会注册SpringConfigProvider - RedissAutoInit的依赖关系这里就是导致报错的关键步骤。 到这里GlobalCacheConfig注入到SpringConfigProvider完成进入到图中第六步操作注册GlobalCacheConfig - SpringConfigProvider依赖关系紧接着SpringConfigProvider注入到CaffeineAutoInit完成注册SpringConfigProvider - CaffeineAutoInit依赖关系。然后又一次判断并初始化RedissonAutoInit
private boolean isDependent(String beanName, String dependentBeanName, Nullable SetString alreadySeen) {if (alreadySeen ! null alreadySeen.contains(beanName)) {return false;}String canonicalName canonicalName(beanName);SetString dependentBeans this.dependentBeanMap.get(canonicalName);if (dependentBeans null) {return false;}if (dependentBeans.contains(dependentBeanName)) {return true;}for (String transitiveDependency : dependentBeans) {if (alreadySeen null) {alreadySeen new HashSet();}alreadySeen.add(beanName);if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {return true;}}return false;}dependentBeanMap就是存储依赖关系的容器canonicalName则是globalCacheConfigdependentBeanName是redissonAutoInit所以dependentBeans就是springConfigProvider递归再次判断就有了R - S - G - R的循环依赖。 看到这对于剩余几个问题就很容易理解了下面总结概括下 当在PostConstruct中调用put方法保存缓存时会优先创建初始化GlobalCacheConfig对象该对象存在dependsOn依赖的对象Spring会对有dependsOn的BD在实例化之前判断是否有循环依赖当dependsOn只有一个时三级缓存帮我们解决了循环依赖的问题不会报错而当有多个时实例化第一个依赖的对象CaffeineAutoInit时会导致GlobalCacheConfig的嵌套创建在第二次创建时第二个依赖对象RedissonAutoInit会注册依赖关系返回到首次创建的地方再次判断RedissonAutoInit的依赖关系就得到循环依赖的结果。而当GlobalCacheConfig延迟主动实例化时只要在它之前随便先创建一个Init对象直接就从单例缓存中就获取到了对象依赖注入会创建该对象并放入缓存进入不到这个判断所以就不会报错依赖注入触发的GlobalCacheConfig实例化虽然会进入这个判断但不会导致嵌套创建感兴趣的可以自行画一下创建过程。 最后还有个问题为什么Spring要在createBean之前对有dependsOn属性的BD判断是否有循环依赖是为了应对什么场景没有这个判断针对这个场景三级缓存是否也能解决循环依赖的问题呢