前言
最近做项目,把项目旧的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下动态代理的结果,本身方法的引用是不会出现循环依赖的。