SpringBoot中修改proxyTargetClass,但事务代理始终为CGLIB

一、配置

在Springboot中,开启事务特别简单,如下

1.在启动类上或者配置类上增加@EnableTransactionManagement

2.在需要开启事务的方法上增加@Transactional

 

二、问题

因为本人有个特别不好的习惯,经常把@Transactional注解使用在interface的方法上,最近遇到一个问题,事务不回滚。但在排查问题时debug发现,开启事务的类是以CGLIB方式实现的代理对象,但印象中,如果不配置proxyTargetClass的话,默认应该是false,JDK的动态代理,于是找了一下原因,特此记录一下过程

 

三、查问题

  1.查看@Transactional源码,并没有代理相关配置

  2.查看@EnableTransactionManagement,存在代理相关配置,如下

/**
 * Indicate whether subclass-based (CGLIB) proxies are to be created ({@code true}) as
 * opposed to standard Java interface-based proxies ({@code false}). The default is
 * {@code false}. <strong>Applicable only if {@link #mode()} is set to
 * {@link AdviceMode#PROXY}</strong>.
 * <p>Note that setting this attribute to {@code true} will affect <em>all</em>
 * Spring-managed beans requiring proxying, not just those marked with
 * {@code @Transactional}. For example, other beans marked with Spring's
 * {@code @Async} annotation will be upgraded to subclass proxying at the same
 * time. This approach has no negative impact in practice unless one is explicitly
 * expecting one type of proxy vs another, e.g. in tests.
 */
boolean proxyTargetClass() default false;

默认值是false,是JDK的动态代理,与记忆符合,但与实际情况不符。

  3.因为Spring的声明事务是基于AOP的,所以全文搜索AOP相关字样发现(此方法比较笨),Springboot引入的一个包spring-boot-autoconfigure,在此包下有AOP的配置,并且有proxy-target-class的配置,默认配置为true,并且还有个参数spring.aop.auto的配置,默认为true

//出自spring-boot-autoconfigure包下spring-configuration-metadata.json文件
{
  "defaultValue": true,
  "name": "spring.aop.auto",
  "description": "Add @EnableAspectJAutoProxy.",
  "type": "java.lang.Boolean"
},
{
  "defaultValue": true,
  "name": "spring.aop.proxy-target-class",
  "description": "Whether subclass-based (CGLIB) proxies are to be created (true), as opposed to standard Java interface-based proxies (false).",
  "type": "java.lang.Boolean"
},

  4.进行验证

application.yaml配置spring.aop.auto

application.yaml配置spring.aop.proxy-target-class

注解@EnableTransactionManagement

proxyTargetClass

代理技术

备注

true

false

false

JDK动态代理

 

true

true

false

CGLIB

默认值

true

false

true

CGLIB

 

true

true

true

CGLIB

 

false

false

false

JDK动态代理

 

false

true

false

JDK动态代理

 

false

false

true

CGLIB

 

false

true

true

CGLIB

 

 

四、总结

    当springboot开启spring.aop.auto设置为true时(类似于自动增加了@EnableAspectJAutoProxy注解),则注解配置配置文件配置有一个配置CGLIB(true),则代理使用CGLIB实现代理,否则使用JDK动态代理

    当springboot开启spring.aop.auto设置为false时,代理方式仅仅受注解配置影响。

    最后本人修改了注解位置,修改到了具体实现类的方法上。

 

五、其他说明

基于CGLIB的代理与基于JDK的动态代理实现的声明式事务的区别:

  • CGLIB基于继承实现,JDK动态代理基于实现接口实现
  • CGLIB的代理类需要事务注解@Transactional标注在类上(或方法)(此处还存在一些疑问,详见https://www.oschina.net/question/3221069_2310769);而JDK动态代理类事务注解@Transactional可以标注在接口上(或方法),也可以标注在实现类上(或方法)
<think>嗯,用户想了解SpringBoot动态代理的原理和使用方法。从引用内容来看,用户已经阅读了几篇关于动态代理的文章摘要,重点在SpringBoot中的应用场景和配置方法。用户可能正在学习SpringAOP的相关内容,可能是位Java开发者,正在搭建基于SpringBoot的项目。从问题表述看,用户对基本概念已有初步了解(能准确说出“动态代理”这个专业术语),现在需要系统性的原理说明和实操指导。需要特别注意三个技术关键点:1.动态代理的两种实现方式差异(JDK与CGLIB)2.SpringBoot的自动化配置逻辑3.具体应用时的实践技巧考虑到用户参考了四篇技术文章,其中提到事务管理、日志记录等具体场景,在回答时要突出实际应用价值。用户可能更关心“为什么用”和“怎么用”,而非纯粹的底层字节码实现。需要警惕一个易错点:用户引用[2]中提到修改代理方式的配置项,但要注意SpringBoot2.x之后默认行为的变化,这个版本差异必须说明清楚,否则可能误导用户配置。可以补充一个典型场景示例(如权限检查)来增强理解,但代码需要精简,避免让用户陷入实现细节。由于用户明确要求“原理”和“使用”两方面内容,采用分块结构最清晰。</think> 好的,我们来深入探讨 Spring Boot 中动态代理的原理和使用方法。Spring Boot 的动态代理主要是作为其 AOP(面向切面编程)功能的底层支撑技术。 ## 一、 动态代理原理 (Why & How) 动态代理的核心目标是:在**运行时**动态创建一个代理对象,这个对象可以“代替”真实的目标对象执行方法调用,并在调用前后(或异常时)加入额外的逻辑(通知),而**无需修改**目标对象的原始代码。这实现了**关注点分离**。 在 Spring Boot(实质上是 Spring Framework)中,主要使用两种动态代理技术: 1. **JDK 动态代理 (基于接口 - 默认策略):** * **原理:** 利用 Java 反射包(`java.lang.reflect.Proxy` 和 `InvocationHandler`)实现。 * **要求:** **目标对象必须实现至少一个接口**。 * **过程:** * Spring AOP 在运行时根据目标对象实现的接口创建对应的代理对象(实现相同的接口)。 * 当客户端调用代理对象的接口方法时。 * 调用被路由到 `InvocationHandler` 的 `invoke` 方法。 * 在这个 `invoke` 方法内,Spring AOP 框架负责调用关联的**通知**(Advice - 如 `@Before`, `@After`, `@Around` 等拦截器逻辑)。 * 通知执行前后,最终通过反射机制调用目标对象的**实际方法**(如果有必要)。 * **优点:** 标准 Java 库自带,无需额外依赖。 * **缺点:** 目标对象必须实现接口。 2. **CGLIB 动态代理 (基于类):** * **原理:** 利用第三方库(CGLIB - Code Generation Library)在运行时通过**字节码生成**技术动态创建目标类的**子类**作为代理对象。 * **要求:** 不需要目标对象实现接口。目标类及其方法**不能是 `final`**(因为需要被继承)。 * **过程:** * CGLIB 在运行时生成目标类的子类字节码。 * 创建代理对象(子类实例),覆盖父类(即目标类)的方法。 * 在覆盖的方法中加入拦截逻辑(调用 Spring AOP 的通知)。 * 最终通过 `super.method()` 调用父类(目标类)的实际方法(如果有必要)。 * **优点:** 可以代理**不实现任何接口的类**。性能在某些场景下可能优于 JDK 代理。 * **缺点:** 需要额外依赖 CGLIB 库。无法代理 `final` 类或 `final` 方法。创建过程比 JDK 代理稍慢(首次)。 ### Spring Boot/Spring AOP 的选择策略 * **默认行为 (Spring Boot < 1.5):** * 如果目标对象**实现了接口** -> 使用 **JDK 动态代理**。 * 如果目标对象**没有实现接口** -> 使用 **CGLIB 代理**。 * **默认行为 (Spring Boot >= 1.5 / Spring 4.x+):** * **默认为 `proxyTargetClass=true`** -> 强制使用 **CGLIB 代理**,无论目标对象是否有接口。 * **原因:** 更统一的行为(特别是涉及到如 `@Configuration` 类代理时),避免 JDK 代理只能基于接口的局限性,简化配置。 * **手动指定方式 (参考引用[2][^2]):** * 在 `application.properties` 或 `application.yml` 中配置: ```properties spring.aop.proxy-target-class=false # 强制使用 JDK 动态代理(要求目标对象有接口) spring.aop.proxy-target-class=true # 强制使用 CGLIB 代理(默认值) ``` ## 二、 使用方法 (How to Use - with AOP) Spring Boot 的动态代理是作为 AOP 基础设施“自动”启用的。开发者通常不需要直接操作代理对象,而是通过声明**切面 (Aspect)** 和 **通知 (Advice)** 来定义增强逻辑。步骤如下: 1. **添加依赖 (参考引用[3][^3]):** ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> ``` `spring-boot-starter-aop` 自动引入了 Spring AOP 和 AspectJ 相关的必要库(包括 CGLIB 或 AspectJ Weaver)。 2. **创建切面类 (Aspect):** * 使用 `@Aspect` 和 `@Component` 注解标记类。 * 在类中定义**切入点 (Pointcut)** 和**通知 (Advice)**。 * **切入点表达式** (`@Pointcut`): 定义了哪些方法(连接点)需要被增强(即代理)。 * **通知方法** (`@Before`, `@After`, `@AfterReturning`, `@AfterThrowing`, `@Around`): 定义了具体在切入点方法的何时插入增强逻辑。 3. **示例切面 (简化自引用场景):** ```java import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Aspect // 声明这是一个切面 @Component // 让 Spring Boot 扫描并管理这个 Bean public class LoggingAspect { // 定义一个切入点:匹配 com.example.service 包下所有方法 @Pointcut("execution(* com.example.service.*.*(..))") public void serviceLayer() {} // 定义一个前置通知:在serviceLayer切入点匹配的方法执行之前运行 @Before("serviceLayer()") public void logMethodEntry() { System.out.println("即将执行 Service 层方法..."); // 这里可以添加更复杂的逻辑,如记录日志、参数等 } // @Around("serviceLayer()") 环绕通知更强大,可以控制方法的整个调用过程 } ``` * Spring Boot 应用启动时,会自动扫描到 `LoggingAspect` Bean。 * 对于所有 `com.example.service` 包下的类实例(无论是实现接口还是直接类),Spring AOP 容器会: * 根据配置策略(默认或指定的 `proxy-target-class`)选择 JDK 代理CGLIB 代理,为这些 Bean 创建**动态代理对象**。 * 将这些代理对象注册到 Spring IoC 容器中,替代原始的 Bean 实例。 * 当其他组件(如 Controller)通过 `@Autowired` 注入 `com.example.service` 下的 Bean 时,实际注入的是这个**代理对象**。 * 当调用代理对象的方法(如 `service.method()`): * 如果匹配到切入点(这里是 `serviceLayer()` 匹配的方法),代理会先调用通知(这里调用 `logMethodEntry()`)。 * 然后继续执行原始目标方法(`service.method()`)。 * (如果是 `@Around` 通知,控制流会更复杂)。 ### 关键的使用抽象层 作为开发者,99%的情况下,**你只需关注如何编写好的切面和通知逻辑即可**。Spring Boot(通过 Spring Framework AOP)在后台透明地处理了基于 JDK 或 CGLIB动态代理的创建和管理工作。你定义的切入点决定了哪些 Bean 需要被代理,你定义的通知决定了在代理方法调用时需要执行的额外代码。 **总结:** 1. **原理:** Spring Boot 主要依靠 **JDK 动态代理(接口代理)** 或 **CGLIB 动态代理(类代理)** 在运行时创建增强的代理对象,实现 AOP 功能。 2. **策略:** 默认使用 CGLIB 代理 (`proxyTargetClass=true`),可通过配置 `spring.aop.proxy-target-class` 显式指定。 3. **使用:** 核心在于 **使用 AOP:** 添加 `spring-boot-starter-aop` 依赖,编写带有 `@Aspect` 和 `@Component` 的切面类,在其中通过 `@Pointcut` 定义切入点,并通过 `@Before`, `@After`, `@Around` 等注解定义通知逻辑。 4. **透明性:** Spring Boot/AOP 框架自动处理代理对象的创建、注册和方法调用拦截,开发者聚焦切面逻辑本身。动态代理是实现这一目标的关键底层技术。[^1][^2][^3][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值