@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下动态代理的结果,本身方法的引用是不会出现循环依赖的。

相关推荐
平凡的小码农13 分钟前
JAVA实现大写金额转小写金额
java·开发语言
一直在进步的派大星16 分钟前
Docker 从安装到实战
java·运维·docker·微服务·容器
老华带你飞20 分钟前
公寓管理系统|SprinBoot+vue夕阳红公寓管理系统(源码+数据库+文档)
java·前端·javascript·数据库·vue.js·spring boot·课程设计
yttandb27 分钟前
重生到现代之从零开始的C语言生活》—— 内存的存储
c语言·开发语言·生活
我明天再来学Web渗透31 分钟前
【hot100-java】【二叉树的层序遍历】
java·开发语言·数据库·sql·算法·排序算法
结衣结衣.1 小时前
python中的函数介绍
java·c语言·开发语言·前端·笔记·python·学习
茫茫人海一粒沙1 小时前
Python 代码编写规范
开发语言·python
原野心存1 小时前
java基础进阶知识点汇总(1)
java·开发语言
程序猿阿伟1 小时前
《C++高效图形用户界面(GUI)开发:探索与实践》
开发语言·c++
暗恋 懒羊羊1 小时前
Linux 生产者消费者模型
linux·开发语言·ubuntu