@Configuration自身循环依赖及解决办法

前言

最近做项目,把项目旧的Springboot框架升级到比较新的一个版本2.7.x,是最后支持JDK8的版本,JDK8寿命真长,不过pagehelper报了自身循环依赖的问题,解决方式也很简单,升级pagehelper的版本即可,不过里面有一个@Configuration的自身循环依赖,比较有意思,就分享一下。Springboot从2.6.0开始就默认禁止使用循环依赖,即默认配置不解决循环依赖:Spring Boot 2.6.0 Configuration Changelog · spring-projects/spring-boot Wiki · GitHub

准备demo

依赖Springboot 2.6.0;pagehelper1.3.0(有深意)

XML 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>2.6.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>

然后写上配置和基础代码

不考虑各种特殊因素,demo

java 复制代码
@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/queryUsers")
    public PageInfo<User> userPageInfo(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.getUsers();
        PageInfo<User> userPageInfo = new PageInfo<>(users);
        return userPageInfo;
    }

mapper写上,启动,没有任何意外

bash 复制代码
***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌──->──┐
|  com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
└──<-──┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

自身循环依赖,神奇的现象。实际上这个现象很常见,方法自引用罢了,但是如果方法上有@Bean注解,那么这个就是创建SpringBean对象,就出现自我循环依赖了。

源码分析

既然知道了循环依赖的Bean class,那么查看源码

如上图,我们可以清晰的看见,这个autoconfiguration的加载顺序,和循环依赖的原因,那么为什么@Bean的方法在@Configuration的初始化时调用会造成循环依赖呢,根源是CGLIB动态代理。

如何验证上面的猜测正确:

使用系统变量开启Spring cglib的生成动态代理字节码写入文件模式

复制代码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/huahua/logs"); 

然后debug启动,可以看到当前的@Configuration注解的类先初始化Bean,而且是代理类,关键在于pageHelperProperties的方法已经被代理了,所以才会在@PostConstruct注解的方法调用时创建pageHelperProperties方法的Bean。

反编译代理类

果然pageHelperProperties方法被代理了,重写了。看看CALLBACK_0是否为空

明显是BeanMethodInterceptor处理(@Bean的处理逻辑)

问题就出在这里,使用@Configuration注解的Bean,被初始化后,在Bean还没有完全创建完成,又调用@Bean的方法,因为Springboot的代理机制,@Bean的方法被SpringCGLIB覆写以便创建@Bean方法的Bean,那么就形成自身循环依赖,因为不是构造函数循环依赖,所以Spring自己也能解决,但是Springboot在2.6.0开始不允许循环依赖存在,所以报错。

解决思路

知道了原理,那么解决就很简单了,升级pageHelper的版本即可,升级1.3.1版本后,还是循环依赖,😋,主要是要看代码,1.3.1的代码这里有修改,但是没有从根本上解决问题,上源码

知道1.4.1版本终于解决了这个问题,因为去掉了@Bean的pageHelperProperties的Bean的创建,实际上也没必要创建这个Bean,因为@EnableConfigurationProperties这个属性会在autoconfig自动(boot逻辑)载入@Configuration,所以在初始化一个空的properties有点多余。

修改后,启动正常,分页OK

调用分页:

总结

实际上循环依赖不仅是Spring Bean的循环依赖,还有接口调用的循环依赖,不过Springboot在2.6.0版本已经默认不允许循环依赖,就是不解决这个问题了,如果spring.main.allow-circular-references配置true,那么还是可以跟以前一样,不过不确定什么时候就移除这个配置。另外自身循环依赖比较特别,主要是Springboot下动态代理的结果,本身方法的引用是不会出现循环依赖的。

相关推荐
Stringzhua4 分钟前
【SpringCloud】Kafka消息中间件
spring·spring cloud·kafka
向阳121810 分钟前
Dubbo负载均衡
java·运维·负载均衡·dubbo
DARLING Zero two♡18 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
Gu Gu Study20 分钟前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
芊寻(嵌入式)42 分钟前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
WaaTong43 分钟前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_7430484443 分钟前
初识Java EE和Spring Boot
java·java-ee
AskHarries1 小时前
Java字节码增强库ByteBuddy
java·后端
一颗松鼠1 小时前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_1 小时前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法