小易说IT 小易说IT

Spring 中使用 CGLIB 动态代理(SpringBoot3 / Java17)

前置要点:Spring 内置了 repackaged CGLIB(org.springframework.cglib),不需要额外引入 cglib 依赖Spring 规则:

  • 目标 Bean没有实现任何接口:Spring AOP 自动走 CGLIB

  • 目标 Bean 有接口:默认优先 JDK;设置 proxyTargetClass=true 强制 CGLIB

方式 1:注解开启(@EnableAspectJAutoProxy,最常用)

@Configuration
// proxyTargetClass = true → 强制CGLIB代理,哪怕类实现了接口
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
}

SpringBoot application.yml 配置(等价上面注解)

spring:
  aop:
    proxy-target-class: true # true=CGLIB;false=优先JDK

SpringBoot2+ 默认 proxy-target-class=true,也就是默认 CGLIBSpring Fra...

XML 老式配置(SSM)

<aop:aspectj-autoproxy proxy-target-class="true"/>

方式 2:手动用 ProxyFactory(原生 Spring API,不依赖 @Aspect 注解)

适合安全增强(鉴权、审计、参数校验)场景,自己写拦截逻辑,不用 AspectJ 注解:

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;

// 目标类:无接口,普通Bean
public class UserSecurityService {
    public void deleteUser(Long uid) {
        System.out.println("执行删除用户:" + uid);
    }
}

// 安全增强拦截器(AOP联盟标准MethodInterceptor)
class SecurityInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // ========== 安全增强逻辑:权限校验、审计日志 ==========
        System.out.println("[安全校验] 前置:校验操作权限");
        // 放行原方法
        Object ret = invocation.proceed();
        System.out.println("[安全审计] 后置:记录操作日志");
        return ret;
    }
}

// 生成CGLIB代理
public class TestProxyFactory {
    public static void main(String[] args) {
        UserSecurityService target = new UserSecurityService();
        ProxyFactory proxyFactory = new ProxyFactory(target);
        proxyFactory.setProxyTargetClass(true); // ✅ 关键:强制CGLIB(继承类代理)
        proxyFactory.addAdvice(new SecurityInterceptor());

        UserSecurityService proxy = (UserSecurityService) proxyFactory.getProxy();
        proxy.deleteUser(1001L);
    }
}

方式 3:ProxyFactoryBean(XML 配置 Bean)

<bean id="userServiceTarget" class="com.demo.UserSecurityService"/>
<bean id="securityAdvice" class="com.demo.SecurityInterceptor"/>

<bean id="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
    <property name="target" ref="userServiceTarget"/>
    <property name="proxyTargetClass" value="true"/> <!-- CGLIB开关 -->
    <property name="interceptorNames" value="securityAdvice"/>
</bean>

CGLIB 硬性限制(踩坑重点)

  1. 目标类不能是 final:CGLIB 靠生成子类继承目标类,final 类无法继承,直接报错Spring

  2. 被拦截方法不能是 final /private:无法被子类重写,不会进入拦截器

  3. 同类内部 this.xxx () 调用,不会触发 AOP 增强

    public void methodA(){
        this.methodB(); // ❌ this指向原对象,不是代理对象,methodB切面失效
    }
    

    解决:从 Spring 容器拿代理对象调用,或者 AopContext.currentProxy()

    ((UserSecurityService)AopContext.currentProxy()).methodB();
    

    注意:需要开启 @EnableAspectJAutoProxy(exposeProxy = true)

  4. Java17+ 模块化强封装:部分版本会报非法访问 JVM 启动参数(开发环境使用,生产尽量规避)

    --add-opens java.base/java.lang=ALL-UNNAMED
    

和【安全增强】场景结合的选型建议

  • 统一面向接口业务层:优先 JDK 代理,更稳定,无继承限制

  • 第三方 SDK、遗留代码、没有接口的业务类:使用 CGLIB 代理做权限、参数校验、防篡改、审计日志

  • 事务 @Transactional 也是 AOP,同样遵守这套代理规则(很多人遇到事务失效就是 CGLIB this 自调用问题)

补充:@Configuration 的 CGLIB 代理

@Configuration 默认 proxyBeanMethods=true,也是 CGLIB 子类代理,目的是保证 @Bean 方法之间互相调用拿到单例 Bean;如果不需要可以关闭:

@Configuration(proxyBeanMethods = false)

快速区分当前 Bean 是哪种代理

AopUtils.isJdkDynamicProxy(proxyObj); // true=JDK
AopUtils.isCglibProxy(proxyObj);     // true=CGLIB

本文原创作者:易君召,详见:https://www.yijunzhao.cc/about,转载请注明出处。

原文链接 https://www.yijunzhao.cc/archives/spring-cglib-dynamic-proxy-usage

欢迎访问 https://www.yijunzhao.cc/

https://www.yijunzhao.cc/