目录
[1.【JDK 动态代理】 和 【CGLIB 代理】对比](#1.【JDK 动态代理】 和 【CGLIB 代理】对比)
[3.我的目标类有接口,但是使用了 实体类进行注入,这时会报错吗。](#3.我的目标类有接口,但是使用了 实体类进行注入,这时会报错吗。)
3.1.有疑问,我之前都不报错,今天pull代码后报错,你认为是哪里的配置修改造成了错误的发生
[指定CGLIB代理后,没有默认构造函数的@Compoment类报错 No visible Constructors in class](#指定CGLIB代理后,没有默认构造函数的@Compoment类报错 No visible Constructors in class)
[什么情况下 默认 是 CGLIB, 不匹配时 JDK代理](#什么情况下 默认 是 CGLIB, 不匹配时 JDK代理)
■前言
之前代码注入对象时,没好好写,一个有接口的类,注入时,没有使用接口定义,而是使用实体类定义,虽然不符合规范,但是代码一直能跑起来。
今天pull共同的最新代码之后,代码报错了。
错误是:but was actually of type 'jdk.proxy4.$Proxy316'
===
■问题分析
1.【JDK 动态代理】 和 【CGLIB 代理】对比
===
| 对比维度 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 代理原理 | 基于接口,通过反射机制动态生成一个实现了目标接口的代理类 | 基于继承,通过 ASM 字节码框架动态生成目标类的子类 |
| 接口要求 | 必须有接口。只能代理接口中声明的方法 | 不需要接口 。可以代理普通类,但类和方法不能是 final |
| Spring 默认策略 | 如果目标 Bean 实现了至少一个接口,Spring AOP 默认使用 JDK 动态代理 | 如果目标 Bean 没有实现任何接口,Spring AOP 会自动切换为 CGLIB 代理 |
| 注入时的类型 | 注入的代理对象只能被赋值给其实现的接口类型,不能赋值给实现类类型(否则会抛异常) | 注入的代理对象是目标类的子类,因此既可以 赋值给接口,也可以赋值给目标类本身 |
| 能否代理实体类 | 不能。实体类(如 POJO)通常不实现接口,JDK 代理无法处理 | 可以 。只要实体类不是 final 的,且方法不是 final 的,CGLIB 就可以生成子类进行代理 |
| 主要限制 | 只能增强接口中定义的方法,无法增强实现类特有的 public 方法 | 无法代理 final 类、final 方法、private 方法 |
===
2.什么时候使用CGLIB,什么时候使用JDK代理
===
| 配置目标 | 具体配置方式 | 生效环境 | 实际代理结果 |
|---|---|---|---|
| 强制固化使用 CGLIB | Spring Boot:spring.aop.proxy-target-class=true |
Spring Boot | 无论有无接口,一律 CGLIB |
原生 Spring:@EnableAspectJAutoProxy(proxyTargetClass = true) |
原生 Spring | 无论有无接口,一律 CGLIB | |
XML:<aop:aspectj-autoproxy proxy-target-class="true"/> |
XML 配置项目 | 无论有无接口,一律 CGLIB | |
| "强制 JDK"/"不固化,根据情况自己选择"(两者实际行为一致) | Spring Boot:spring.aop.proxy-target-class=false |
Spring Boot | 有接口 → JDK;无接口 → CGLIB 兜底 |
原生 Spring:@EnableAspectJAutoProxy(proxyTargetClass = false) 显式写 false |
原生 Spring | 有接口 → JDK;无接口 → CGLIB 兜底 | |
原生 Spring:@EnableAspectJAutoProxy 不写 proxyTargetClass(默认值即 false) |
原生 Spring | 有接口 → JDK;无接口 → CGLIB 兜底 | |
原生 Spring:proxyTargetClass 保持默认 false(不设置) |
原生 Spring | 有接口 → JDK;无接口 → CGLIB 兜底 | |
不设置任何 proxy-target-class / proxyTargetClass 配置 |
原生 Spring(默认状态) | 动态判断:有接口 → JDK;无接口 → CGLIB | |
| 不设置任何代理配置 | Spring Boot 2.x+(注意差异) | Boot 自动配置默认已固化为 CGLIB,不设置也是 CGLIB;需显式设 spring.aop.proxy-target-class=false 才退回"有接口 JDK、无接口 CGLIB"的动态逻辑 |
====
3.我的目标类有接口,但是使用了 实体类进行注入,这时会报错吗。
==
**如果,****(注意有前提条件,如果)**实际生成的是 JDK 动态代理:一定报错
JDK 动态代理生成的 Proxy 类只实现了目标类所实现的接口,它不是目标实体类的子类。因此,这个 Proxy 对象在类型上与你的实体类不兼容。当 Spring 试图把这个代理对象注入到一个声明为实体类类型的字段时,就会因为类型不匹配而抛出异常,你看到的 $Proxy 报错正是由此而来。
===
报错:but was actually of type 'jdk.proxy4.$Proxy316' // 316 只是一个数字,还可以是其它值
===
| 实际使用的代理类型 | 代理对象与实体类的关系 | 按实体类注入的结果 | 触发条件(配置/环境) |
|---|---|---|---|
| JDK 动态代理 | $Proxy 只实现接口,不是实体类的子类 |
报错 (类型不匹配,如 $Proxy 无法赋值给实体类) |
① Spring Boot 配了 spring.aop.proxy-target-class=false;② 原生 Spring 未强制 CGLIB(proxyTargetClass 为 false/未设置)且目标类有接口;③ XML 配 proxy-target-class="false" |
| CGLIB 代理 | 代理对象是实体类的子类 | 不报错(可安全赋值给实体类类型) | ① Spring Boot 默认(spring.aop.proxy-target-class=true 或未改默认值);② 原生 Spring @EnableAspectJAutoProxy(proxyTargetClass = true);③ XML 配 proxy-target-class="true" |
=====
3.1.有疑问,我之前都不报错,今天pull代码后报错,你认为是哪里的配置修改造成了错误的发生
====
| 可能的改动 | 影响 |
|---|---|
Spring Boot 的 application.properties/yml 里新增或改成了 spring.aop.proxy-target-class=false |
Boot 默认是 CGLIB,改成 false 后对有接口的类改用 JDK 代理,直接触发你的报错 |
原生 Spring 项目里,@EnableAspectJAutoProxy 的 proxyTargetClass = true 被删掉或改成了 false |
有接口的类从 CGLIB 退回 JDK 代理 |
XML 配置里 <aop:aspectj-autoproxy proxy-target-class="true"/> 被删或改成 false |
同上 |
| 引入了新的 starter / 自动配置,覆盖了原来的 AOP 代理设置 | 例如某些安全、事务相关的自动配置会重新定义 proxy-target-class |
=====
■临时对应策
====
java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* 配置类:强制使用 CGLIB 代理
* proxyTargetClass = true 表示无论目标类是否有接口,都基于类生成子类代理
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
// 目标 Bean:一个没有实现任何接口的普通类(类似实体类/POJO)
@Bean
public OrderService orderService() {
return new OrderService();
}
// 切面 Bean
@Bean
public LogAspect logAspect() {
return new LogAspect();
}
}
===
指定CGLIB代理后,没有默认构造函数的@Compoment类报错 No visible Constructors in class
====
指定CGLIB代理后,没有默认构造函数的@Compoment类报错 No visible Constructors in class
你遇到的 No visible Constructors in class 报错,根本原因是:CGLIB 通过生成目标类的子类来实现代理,而生成这个子类时,需要能够访问到父类(也就是你的 @Component 类)的某个构造方法。 当你的类没有任何可见构造方法(即没有 public、protected 或包级私有的构造方法,只有 private 构造方法)时,CGLIB 无法生成子类,就会抛出这个异常。
===
为什么指定 CGLIB 后才会触发
在 JDK 动态代理模式下,代理对象是 $Proxy,它只实现接口,不需要继承你的类,所以你的类构造方法是否可见完全不影响代理的生成。一旦你强制使用 CGLIB,Spring 就必须生成你的类的子类,此时 CGLIB 需要调用父类的构造方法来完成子类的实例化过程,构造方法的可见性就成了硬性门槛。
===
什么是CGLIB
==
CGLIB(Code Generation Library)是 Spring AOP 在需要代理普通类(尤其是没有实现接口的类)时使用的底层技术。它的核心是在运行时动态生成目标类的一个子类 ,并通过重写方法来插入增强逻辑。你之前遇到的"按实体类注入成功"以及"
No visible Constructors报错",根源都在于这套"继承"机制。

=====
■最终对应策
====
规范代码,都使用接口声明
===
■发生原因(谁修改了什么)(感觉还是迷)
====
新增或改成了 spring.aop.proxy-target-class=false
默认是 CGLIB,改成 false 后对有接口的类改用 JDK 代理,直接触发报错
错误是:but was actually of type 'jdk.proxy4.$Proxy316'
===
什么情况下 默认 是 CGLIB, 不匹配时 JDK代理

====

===
■其它(查看Spring的版本)
==
java
import org.springframework.core.SpringVersion;
import org.springframework.boot.SpringBootVersion;
public class VersionPrinter implements CommandLineRunner {
@Override
public void run(String... args) {
System.out.println("Spring Framework Version: " + SpringVersion.getVersion());
// 如果是 Spring Boot 项目,也可以同时打印 Boot 版本
System.out.println("Spring Boot Version: " + SpringBootVersion.getVersion());
}
}
===