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

本文讲述了在将SpringBoot项目升级到2.7.x版本时遇到的pagehelper循环依赖问题,分析了原因在于@Configuration中的Bean方法在初始化时被CGLIB代理,导致自身循环。解决方法是升级pagehelper版本至1.4.1以上,移除不必要的Bean创建。

前言

最近做项目,把项目旧的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(有深意)

        <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

@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写上,启动,没有任何意外

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

<think>嗯,用户问的是关于@Configuration注解不生效的问题。我需要仔细想想可能的原因。首先,用户可能在使用Spring框架时遇到了配置类没有被正确识别的情况。我应该先回顾一下@Configuration的作用,它是用来定义配置类的,里面通常有@Bean注解的方法,Spring会处理这些来生成Bean定义。 然后,可能的原因有哪些呢?首先想到的是配置类没有被组件扫描到。如果包路径不在扫描范围内,Spring就不会处理这个类,自然@Configuration也不会生效。这时候需要检查@ComponentScan或者Spring Boot的自动扫描配置是否正确,包路径是否包含配置类所在的包。 接下来,配置类可能没有被正确导入。比如,如果使用@Import注解或者通过其他配置类引入,可能漏掉了,导致配置类没有被加载。这时候需要确认是否有其他配置类需要引用这个配置类,或者是否应该用@Import引入。 然后,是否有多个配置类导致冲突?有时候,尤其是大型项目中,可能存在多个配置类,Bean之间的依赖或重复定义可能导致一些问题。这时候需要检查是否有重复的Bean定义,或者配置类之间的顺序问题。 还有可能是代理模式的问题。Spring默认使用CGLIB代理@Configuration类,这样在调用@Bean方法时能保证单例。如果配置类被标记为final或者有私有构造函数,可能导致代理失败,从而@Bean方法每次返回新实例。这时候需要检查配置类是否符合要求,不能被final修饰,必须有可被重写的构造函数。 另外,项目依赖可能有问题。比如,Spring的核心库没有正确引入,或者版本不兼容,导致注解无法被处理。这时候需要检查pom.xml或build.gradle中的依赖是否正确,特别是spring-context的版本是否合适。 还有可能用户自己编写的配置类中存在错误,比如@Bean方法有误,导致Spring容器初始化失败,这时候整个配置类可能都不会被处理。需要检查配置类内部是否有语法错误,或者依赖的其他Bean不存在等问题。 还有可能使用了错误的注解,比如误用了@Component而不是@Configuration,虽然两者都可以声明Bean,但在处理@Bean方法时行为不同,可能引发问题。 或者,配置类中的@Bean方法没有被正确识别,比如方法是非public的,或者静态的,这会影响Spring的代理和Bean生成。需要确保@Bean方法是public且非静态的,除非特意声明为静态的。 还有可能,应用上下文没有正确加载配置类。比如,在单元测试中,如果没有明确指定配置类,或者使用了错误的上下文配置,导致配置类未被加载。这时候需要检查测试注解中的classes属性是否包含了该配置类。 再想想,是否有可能配置类被其他条件注解所控制,比如@Profile或者@Conditional,导致在某些环境下配置类未被激活。需要检查是否有条件注解,并且当前运行的环境是否符合条件。 另外,可能用户没有正确理解配置类的加载顺序,比如在Spring Boot中,自动配置和用户自定义配置的优先级问题,导致自定义配置被覆盖。这时候可能需要调整配置的顺序或使用@Order注解。 还有可能,配置类中的Bean依赖于其他尚未初始化的Bean,导致循环依赖或其他初始化问题,从而使得配置类无法完全生效。需要检查Bean之间的依赖关系,是否存在循环依赖或者错误的依赖顺序。 总结下来,用户的问题可能涉及组件扫描、配置类导入、代理问题、依赖冲突、条件注解、Bean定义错误等多个方面。需要逐一排查这些可能性,找到具体的原因。用户可能需要检查他们的配置类是否被正确扫描,配置类本身是否正确,依赖是否正确,以及是否有其他因素影响了配置类的处理。</think>在Spring框架中,`@Configuration`注解用于标记一个类作为配置类,其中可以定义Bean(通过`@Bean`方法)。如果`@Configuration`注解不生效,可能是以下原因导致的: --- ### **1. 配置类未被扫描到** - **原因**:Spring需要扫描到配置类才能处理它。如果配置类所在的包未被组件扫描覆盖,`@Configuration`注解不会生效。 - **解决**: - 检查`@ComponentScan`注解的包路径是否包含配置类所在的包。 - 如果是Spring Boot项目,默认扫描主类(`@SpringBootApplication`)所在包及其子包。确保配置类在这些包中。 ```java @SpringBootApplication @ComponentScan("com.example.config") // 显式指定扫描包 public class Application { ... } ``` --- ### **2. 配置类未被正确导入** - **原因**:如果配置类需要被其他配置类或上下文显式引入,但未使用`@Import`注解,可能导致未被加载。 - **解决**: - 使用`@Import`注解显式导入配置类: ```java @Configuration @Import(MyConfig.class) public class AppConfig { ... } ``` --- ### **3. 代理模式问题(CGLIB代理)** - **原因**:`@Configuration`类默认通过CGLIB代理增强,以确保单例Bean的正确性。如果配置类被标记为`final`、有私有构造函数或无法被继承,会导致代理失败。 - **解决**: - 确保配置类是非`final`的。 - 确保构造函数是`public`或`protected`(非私有)。 --- ### **4. 依赖缺失或版本问题** - **原因**:项目中缺少Spring核心依赖(如`spring-context`),或版本不兼容。 - **解决**: - 检查`pom.xml`或`build.gradle`中是否包含`spring-context`依赖: ```xml <!-- Maven --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.x.x</version> </dependency> ``` --- ### **5. 配置类中的错误** - **原因**:配置类内部存在语法错误或Bean定义错误(如循环依赖),导致Spring容器启动失败。 - **解决**: - 检查控制台日志,定位启动失败的异常信息。 - 确保`@Bean`方法正确,且依赖的Bean已存在。 --- ### **6. 与其他配置冲突** - **原因**:项目中存在多个同名Bean,或配置类被其他配置覆盖。 - **解决**: - 使用`@Bean(name = "customName")`指定唯一名称。 - 检查是否有`@Primary`或`@Conditional`注解影响Bean的加载。 --- ### **7. 测试环境未加载配置类** - **原因**:在单元测试中未显式指定配置类。 - **解决**: - 使用`@ContextConfiguration`或`@SpringBootTest`加载配置类: ```java @SpringBootTest(classes = MyConfig.class) public class MyTest { ... } ``` --- ### **排查步骤** 1. 检查Spring启动日志,确认配置类是否被加载。 2. 确保配置类在组件扫描范围内。 3. 检查是否有依赖缺失或版本冲突。 4. 确保配置类中无语法或逻辑错误。 5. 使用调试工具(如IDE的断点)验证`@Bean`方法是否执行。 如果仍无法解决,建议提供具体的代码和错误日志,以便进一步定位问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值