Spring AOP - Caused by: java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given

本文探讨了在集成perf4j和javasimon时遇到的问题:使用Spring结合CGLIB创建有参数构造函数的对象时抛出IllegalArgumentException异常。文中提供了解决方案并推荐了一个详细的参考资料。

 

今天在集成perf4j和javasimon到代码里的时候发现一些问题,就是本来在自己的测试代码里运行好好的东西,在放到项目代码里的时候总是报以下错误:
...
Caused by: java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given
...
比较了代码半天发现在自己的测试代码里没有用到有构造参数的spring bean,而在项目代码中很多的类都是通过有参数构造函数来创建的。而spring在借助cglib创建目标对象子类时,并没有对有参数构造函数做处理,而是使用的默认我参数构造函数,这就导致了以上异常的出现。要解决这个问题,比较简单的做法就是添加一个默认构造函数,当然前提是你的类允许你修改,但是对于一些不能有默认构造函数的类就比较麻烦了,比如第三方的类。这里有一个很详细的文章对这个问题做了描述, http://netfork.javaeye.com/blog/286215
<think>我们正在处理一个Spring框架中的特定错误:'java.lang.IllegalArgumentException: Superclass has no null constructors but no arguments were given'。 根据错误信息,它表示在尝试创建一个类的实例时,该类的超类没有无参构造函数,但创建时却没有提供参数。 在Spring中,这通常发生在以下情况: 1. 使用代理(例如AOP代理)时,Spring需要创建目标类的子类(CGLIB代理),而目标类的超类没有无参构造器。 2. 当使用基于构造器的依赖注入时,如果没有正确配置,也可能导致类似问题。 具体到错误信息,重点在于“Superclass has no null constructors”,即超类没有无参构造器。因此,当我们试图通过代理(比如CGLIB)来扩展这个类时,由于代理类需要调用超类的无参构造器(默认行为),但超类没有,就会抛出此异常。 可能的解决方案: 1. 为超类添加一个无参构造函数(如果可行且符合设计)。 2. 如果超类不可修改(比如是第三方库中的类),则考虑避免使用需要代理的场景。例如,如果使用了AOP(如@Transactional, @Async等),那么这些代理可能会要求目标类有默认构造器。 3. 使用基于接口的代理(JDK动态代理)而不是基于类的代理(CGLIB)。但前提是目标类实现了接口,并且代理是针对接口的。 在Spring中,可以通过配置`spring.aop.proxy-target-class=false`来强制使用JDK动态代理(默认为false,但在Spring Boot中默认可能是true,具体看版本)。 但是,如果你使用的是Spring Boot,可以通过在配置文件中设置`spring.aop.proxy-target-class=false`来切换。 4. 如果使用构造器注入,确保在子类中正确调用超类的构造器,并提供必要的参数。 由于错误发生在Spring框架5.2.15.RELEASE中,我们需要考虑该版本的具体行为。 此外,该错误可能与特定的Bean有关。请检查抛出异常的Bean及其超类的构造器定义。 另一种情况:使用`@Configuration`注解的配置类,如果配置类没有无参构造器,也可能会导致此问题。配置类通常需要无参构造器,因为Spring在创建配置类时可能会使用CGLIB代理。 步骤总结: 1. 定位到抛出异常的Bean类,检查其超类(父类)是否有无参构造器。 2. 如果超类没有无参构造器,考虑是否可以在超类中添加无参构造器(注意:如果超类中有final字段,可能需要重新设计)。 3. 如果不能修改超类,检查是否可以通过使用接口代理(JDK动态代理)来避免CGLIB代理。这要求被代理的Bean实现了至少一个接口,并且代理设置正确。 4. 检查Spring配置,确保代理设置正确(例如,在非Spring Boot项目中,可以在配置文件中设置`<aop:config proxy-target-class="false">`)。 5. 如果问题发生在配置类(`@Configuration`)上,尝试将配置类改为使用非代理模式(使用`@Bean`方法时避免调用其他`@Bean`方法,或者使用`@Configuration(proxyBeanMethods = false)`,这在Spring Boot 2.2以后可用,但注意版本兼容性,因为用户使用的是5.2.15,对应Spring Boot 2.3.x,所以支持)。 另外,在Spring 5.2中,还可以尝试使用`@Scope(proxyMode = ScopedProxyMode.INTERFACES)`来指定代理模式为接口代理(如果Bean实现了接口)。 但是,请注意,如果Bean没有实现任何接口,那么只能使用CGLIB代理,此时就必须确保超类有无参构造器。 因此,如果无法修改超类,且Bean没有实现接口,那么可能需要重新设计类结构,或者避免使用需要代理的特性(如事务管理)。 现在,我们来整理具体的解决步骤: 步骤1:确认错误发生的具体位置 - 查看堆栈跟踪,确定是哪个Bean的创建导致了错误。 步骤2:检查该Bean及其父类 - 找到对应的类,检查其父类是否提供了无参构造器。如果没有,尝试添加无参构造器(如果父类是你自己编写的)。 步骤3:如果父类是第三方库中的且无法修改 - 检查该Bean是否实现了接口?如果是,可以尝试使用JDK动态代理。 -Spring配置中设置为使用JDK动态代理(设置`spring.aop.proxy-target-class=false`)。 步骤4:如果该Bean没有实现接口 - 考虑是否必须使用CGLIB代理?如果必须,那么唯一的办法是修改父类提供无参构造器(显然不可能时),或者避免使用代理(例如,不使用AOP功能)。 - 或者,尝试将该Bean的实例创建方式改为通过工厂方法,避免Spring通过继承的方式来创建代理?但这通常不可行。 步骤5:如果该Bean是配置类(`@Configuration`) -Spring 5.2中,可以设置`@Configuration(proxyBeanMethods = false)`,这样配置类就不会被代理,从而避免了CGLIB创建子类。但是注意,这会影响`@Bean`方法的调用方式(每次调用都会返回新的实例,除非是单例且方法不被多次调用),所以需要评估是否适合。 步骤6:检查依赖注入方式 - 如果该Bean使用了构造器注入,并且父类也有构造器注入,确保子类构造器正确地调用了父类构造器并传递了参数。 举例说明: - 假设有一个父类: public class Parent { private String name; public Parent(String name) { this.name = name; } } - 子类: public class Child extends Parent { public Child() { // 这里没有显式调用super,编译器会自动添加super(),但Parent没有无参构造器,所以编译错误?实际上,这里会编译错误,因为必须显式调用父类构造器。 // 所以,如果子类构造器没有正确调用父类构造器,编译就会失败。因此这种情况在编译时就会捕获,不会等到运行时。 } } - 因此,运行时错误通常不是由于子类构造器没有正确调用父类构造器(因为编译时就失败了),而是因为Spring在尝试创建代理子类时,代理子类需要调用父类的无参构造器,但父类没有。 所以,根本原因还是在于代理机制需要无参构造器。 总结:优先考虑使用JDK动态代理(如果条件允许)或者为父类添加无参构造器。如果都不行,尝试重构代码避免代理。 针对Spring 5.2.15.RELEASE,具体设置代理方式: -Spring Boot项目中,可以在`application.properties`中设置: spring.aop.proxy-target-class=false - 在非Spring Boot项目中,可以在XML配置中: <aop:config proxy-target-class="false"> </aop:config> 或者使用注解方式(在配置类上): @EnableAspectJAutoProxy(proxyTargetClass = false) 注意:修改代理方式可能会影响整个应用,因此需要测试。 另外,如果错误发生在特定的Bean上,也可以单独设置该Bean的代理模式,例如: @Scope(proxyMode = ScopedProxyMode.INTERFACES) 但是,这个注解通常用于作用域代理(如request, session等),对于一般的AOP代理可能不适用。 因此,全局设置可能是最简单的方式。 最后,如果上述方法都不适用,可能需要考虑升级Spring版本(因为某些版本可能存在bug)?但根据错误信息,这应该是一个设计上的限制,不是bug。 根据以上分析,我们给出以下解决方案: 解决方案1:为父类添加无参构造器 解决方案2:配置Spring使用JDK动态代理(如果Bean有接口) 解决方案3:对于配置类,使用`@Configuration(proxyBeanMethods = false)` 解决方案4:避免在无法提供无参构造器的类上使用Spring代理功能(例如事务、缓存、异步等) 请根据实际情况选择适合的解决方案。 生成相关问题: 1. 如何为Spring配置JDK动态代理而不是CGLIB代理? 2.Spring中,`@Configuration(proxyBeanMethods = false)`的作用是什么? 3. 如何检查一个Spring Bean是否被代理? 4. 除了无参构造器问题,还有哪些原因会导致CGLIB代理创建失败? </think>### 解决Spring 5.2.15中 `java.lang.IllegalArgumentException: Superclass has no null constructors` 错误的方法 该错误通常发生在Spring尝试通过CGLIB代理创建Bean时,目标类的**超类缺少无参构造函数**,但Spring又未提供构造参数值。以下是系统性的解决方案: #### 1. **检查超类构造器** - 确认目标Bean的超类是否定义了显式带参构造器 - 若超类是自定义类,添加无参构造器: ```java public class SuperClass { public SuperClass() {} // 显式添加无参构造器 } ``` #### 2. **配置代理模式** -Bean实现了接口,强制使用JDK动态代理替代CGLIB: ```java @SpringBootApplication @EnableAspectJAutoProxy(proxyTargetClass = false) // 关键配置 public class Application {...} ``` 或在`application.properties`中添加: ```properties spring.aop.proxy-target-class=false ``` #### 3. **配置Bean的构造参数** - 若必须使用带参超类构造器,显式定义参数值: ```java @Configuration public class AppConfig { @Bean public SubClass subClass() { return new SubClass(new SuperClass("param")); // 手动提供构造参数 } } ``` #### 4. **检查注解使用** - 避免在`final`类或`private`方法上使用代理相关注解: ```java // 错误示例 public final class ServiceImpl {...} // final类无法被CGLIB代理 // 正确做法 public class ServiceImpl implements Service {...} // 非final类 ``` #### 5. **升级依赖** - 检查是否存在已知Bug,升级Spring到5.2.16+或5.3.x: ```xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>5.3.18</version> </dependency> ``` > **根本原因**:CGLIB代理需要继承目标类,当超类没有无参构造器时,Spring无法实例化代理对象[^1]。Spring 5.2.x默认优先使用CGLIB代理[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值