【代理模式】实现方法增强
- 【一】需求描述
-
- 【1】目标
- [【2】基础接口 & 实现类](#【2】基础接口 & 实现类)
-
- [(1)顶层接口 IDatabaseTemplate](#(1)顶层接口 IDatabaseTemplate)
- [(2)原始实现类 DefaultDatabaseTemplate(被代理目标对象)](#(2)原始实现类 DefaultDatabaseTemplate(被代理目标对象))
- (3)统一日志增强工具(三种代理共用耗时打印逻辑)
- 【二】实现方案
-
- [【1】方案 1:静态代理实现](#【1】方案 1:静态代理实现)
-
- [(1)静态代理类 DatabaseTemplateStaticProxy](#(1)静态代理类 DatabaseTemplateStaticProxy)
- (2)使用方式
- [(3)静态代理 优缺点](#(3)静态代理 优缺点)
- [【2】方案 2:JDK 动态代理(接口代理,Spring AOP 默认首选)](#【2】方案 2:JDK 动态代理(接口代理,Spring AOP 默认首选))
-
- [(1)统一拦截处理器 DatabaseInvocationHandler](#(1)统一拦截处理器 DatabaseInvocationHandler)
- (2)代理工厂,快速生成代理对象
- (3)使用方式
- [(4)JDK 动态代理 优缺点](#(4)JDK 动态代理 优缺点)
- (5)真实使用案例
- [【3】方案 3:CGLIB 动态代理(无接口类也能代理)](#【3】方案 3:CGLIB 动态代理(无接口类也能代理))
-
- [(1)引入 Maven 依赖(Spring 内置已集成,单独使用需引入)](#(1)引入 Maven 依赖(Spring 内置已集成,单独使用需引入))
- [(2)CGLIB 方法拦截器 DatabaseMethodInterceptor](#(2)CGLIB 方法拦截器 DatabaseMethodInterceptor)
- [(3)CGLIB 代理工厂](#(3)CGLIB 代理工厂)
- (4)使用方式(两种场景都支持)
- [(5)CGLIB 代理 优缺点](#(5)CGLIB 代理 优缺点)
- 【4】选型建议
- 【三】动态代理分析
-
- 【1】JDK动态代理为什么只能代理实现了接口的类?
-
- [(1)JDK Proxy 生成的代理类 固定实现目标接口](#(1)JDK Proxy 生成的代理类 固定实现目标接口)
- [(2)设计定位:JDK 动态代理初衷是「面向接口代理」](#(2)设计定位:JDK 动态代理初衷是「面向接口代理」)
- (3)方法拦截的实现逻辑:只拦截接口声明的方法
- (4)总结原因
- [【2】JDK动态代理为什么不能代理私有 / 公共非接口方法?](#【2】JDK动态代理为什么不能代理私有 / 公共非接口方法?)
- 【3】JDK动态代理和CGLIB动态代理的核心区别?
- 【四】spring源码中代理模式设计的案例
-
- [【1】核心案例 1:Spring AOP 切面代理](#【1】核心案例 1:Spring AOP 切面代理)
- [【2】核心案例 2:Spring 声明式事务 @Transactional](#【2】核心案例 2:Spring 声明式事务 @Transactional)
- [【3】案例 3:Spring BeanFactory 工厂代理、FactoryBean 代理包装](#【3】案例 3:Spring BeanFactory 工厂代理、FactoryBean 代理包装)
- [【4】案例 4:CGLIB 代理专用场景:无接口类代理、Spring Boot 强制 CGLIB](#【4】案例 4:CGLIB 代理专用场景:无接口类代理、Spring Boot 强制 CGLIB)
- 【5】总结
【一】需求描述
【1】目标
(1)顶层接口 IDatabaseTemplate,包含各类数据库 CRUD 方法;
(2)对接口所有方法做增强:方法执行前后记录耗时、打印入参 / 返回值 / 耗时日志;
(3)分别实现三种代理:静态代理、JDK 动态代理、CGLIB 动态代理;对比三者实现、优缺点、适用场景。
【2】基础接口 & 实现类
(1)顶层接口 IDatabaseTemplate
java
import java.util.List;
import java.util.Map;
public interface IDatabaseTemplate {
List<Map<String, Object>> queryList(String sql, Object... params);
Map<String, Object> queryOne(String sql, Object... params);
int executeUpdate(String sql, Object... params);
}
(2)原始实现类 DefaultDatabaseTemplate(被代理目标对象)
java
import java.util.List;
import java.util.Map;
public class DefaultDatabaseTemplate implements IDatabaseTemplate {
@Override
public List<Map<String, Object>> queryList(String sql, Object... params) {
// 模拟数据库查询逻辑
sleepRandomMs();
return null;
}
@Override
public Map<String, Object> queryOne(String sql, Object... params) {
sleepRandomMs();
return null;
}
@Override
public int executeUpdate(String sql, Object... params) {
sleepRandomMs();
return 1;
}
private void sleepRandomMs() {
try {
Thread.sleep((long) (Math.random() * 200));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
(3)统一日志增强工具(三种代理共用耗时打印逻辑)
java
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@Slf4j
public class DatabaseLogUtil {
public static Object doInvoke(String methodName, Object[] args, Runnable before, Callable<Object> targetMethod) throws Exception {
before.run();
long start = System.currentTimeMillis();
Object result = null;
try {
result = targetMethod.call();
long cost = System.currentTimeMillis() - start;
log.info("【数据库操作正常】方法:{},入参:{},耗时:{}ms,返回结果:{}",
methodName, Arrays.toString(args), cost, result);
return result;
} catch (Exception e) {
long cost = System.currentTimeMillis() - start;
log.error("【数据库操作异常】方法:{},入参:{},耗时:{}ms,异常信息:{}",
methodName, Arrays.toString(args), cost, e.getMessage(), e);
throw e;
}
}
}
【二】实现方案
【1】方案 1:静态代理实现
(1)静态代理类 DatabaseTemplateStaticProxy
必须实现和目标相同的接口 IDatabaseTemplate,持有目标对象,手动重写每一个接口方法,在每个方法内写增强逻辑。
java
import lombok.extern.slf4j.Slf4j;
import java.util.List;
import java.util.Map;
@Slf4j
public class DatabaseTemplateStaticProxy implements IDatabaseTemplate {
// 持有被代理的真实对象
private final IDatabaseTemplate target;
// 构造注入目标实现
public DatabaseTemplateStaticProxy(IDatabaseTemplate target) {
this.target = target;
}
@Override
public List<Map<String, Object>> queryList(String sql, Object... params) {
try {
return (List<Map<String, Object>>) DatabaseLogUtil.doInvoke(
"queryList", new Object[]{sql, params},
() -> log.debug("开始执行queryList查询"),
() -> target.queryList(sql, params)
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Map<String, Object> queryOne(String sql, Object... params) {
try {
return (Map<String, Object>) DatabaseLogUtil.doInvoke(
"queryOne", new Object[]{sql, params},
() -> log.debug("开始执行queryOne查询"),
() -> target.queryOne(sql, params)
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public int executeUpdate(String sql, Object... params) {
try {
return (Integer) DatabaseLogUtil.doInvoke(
"executeUpdate", new Object[]{sql, params},
() -> log.debug("开始执行executeUpdate更新"),
() -> target.executeUpdate(sql, params)
);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
(2)使用方式
java
// 1. 创建原始实现
IDatabaseTemplate realTarget = new DefaultDatabaseTemplate();
// 2. 创建静态代理包装对象
IDatabaseTemplate proxy = new DatabaseTemplateStaticProxy(realTarget);
// 3. 调用代理方法,自动打印耗时日志
proxy.queryOne("select * from table where id = ?", 1001);
(3)静态代理 优缺点
(1)优点
1-实现简单,无任何第三方依赖,JDK 原生支持;
2-编译期即可校验接口方法,方法写错直接编译报错,排错简单;
3-执行无反射开销,性能最高。
(2)缺点(致命短板)
1-接口新增方法必须同步修改代理类:如果IDatabaseTemplate新增batchInsert(),代理类必须手动新增对应实现,代码维护爆炸;
2-接口方法越多,代理类代码冗余严重,大量重复增强逻辑;
3-只能代理实现了同一接口的类,无法代理没有接口的普通类。
【2】方案 2:JDK 动态代理(接口代理,Spring AOP 默认首选)
核心:基于接口动态生成代理类,不需要手动写代理实现类,通过InvocationHandler统一拦截所有接口方法,一处统一写增强逻辑。
(1)统一拦截处理器 DatabaseInvocationHandler
java
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@Slf4j
public class DatabaseInvocationHandler implements InvocationHandler {
// 被代理的真实目标对象
private final Object target;
public DatabaseInvocationHandler(Object target) {
this.target = target;
}
// 所有接口方法执行都会进入该统一拦截方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return DatabaseLogUtil.doInvoke(
method.getName(), args,
() -> log.debug("开始执行数据库方法:{}", method.getName()),
() -> method.invoke(target, args)
);
}
}
(2)代理工厂,快速生成代理对象
java
import java.lang.reflect.Proxy;
public class JdkProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T getProxy(T target) {
Class<?> targetCls = target.getClass();
// JDK动态代理要求:目标类必须实现至少一个接口
return (T) Proxy.newProxyInstance(
targetCls.getClassLoader(),
targetCls.getInterfaces(),
new DatabaseInvocationHandler(target)
);
}
}
(3)使用方式
java
// 原始目标
IDatabaseTemplate realTarget = new DefaultDatabaseTemplate();
// 动态生成代理对象,不需要手动写代理类
IDatabaseTemplate proxy = JdkProxyFactory.getProxy(realTarget);
// 任意接口方法都会被统一拦截,自动打印耗时日志
proxy.queryList("select * from dim_table where dt = ?", "2026-06-29");
(4)JDK 动态代理 优缺点
(1)优点
1-只写一次拦截逻辑,接口新增方法无需修改代理代码,自动拦截所有接口方法;
2-无重复冗余代码,统一增强逻辑,易维护;
3-JDK 原生,无需引入第三方 Jar 包。
(2)缺点
1-强制限制:只能代理实现了接口的类;如果目标类没有接口,无法使用;
2-底层依赖反射执行方法,相比静态代理有微小性能损耗(业务系统完全可忽略);
3-无法代理类的普通非接口方法(如目标类自己写的私有 / 公共非接口方法)。
(5)真实使用案例
java
@Slf4j
public class DatabaseTemplateWrapper implements InvocationHandler {
private IDatabaseTemplate databaseTemplate;
public DatabaseTemplateWrapper(IDatabaseTemplate databaseTemplate) {
this.databaseTemplate = databaseTemplate;
}
public static IDatabaseTemplate createProxy(IDatabaseTemplate target) {
return (IDatabaseTemplate) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{IDatabaseTemplate.class},
new DatabaseTemplateWrapper(target));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = method.invoke(databaseTemplate, args);
return result;
} catch (Exception e) {
throw e.getCause();
} finally {
log.info("数据库访问插件,执行耗时:{} 毫秒", (System.currentTimeMillis() - start));
}
}
java
// 生成数据模板
DatabaseClassLoader databaseClassLoader = getDatabaseClassLoader(datasourcePluginConfig, dataSource);
IDatabaseTemplate iDatabaseTemplate = getDatabaseTemplate(databaseClassLoader, connectProperties, poolProperties);
if (EngineConfUtil.getBoolVal(ConfItem.插件执行SQL耗时追踪)) {
return new Tuple2<>(dataSource.getTepltCd(), DatabaseTemplateWrapper.createProxy(iDatabaseTemplate));
} else {
return new Tuple2<>(dataSource.getTepltCd(), iDatabaseTemplate);
}
【3】方案 3:CGLIB 动态代理(无接口类也能代理)
核心:底层通过字节码生成目标类的子类,重写父类所有非 final 方法实现拦截,不需要依赖接口,支持纯普通类代理。
(1)引入 Maven 依赖(Spring 内置已集成,单独使用需引入)
xml
<!-- SpringBoot项目无需手动引入,spring-core自带cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
(2)CGLIB 方法拦截器 DatabaseMethodInterceptor
java
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
@Slf4j
public class DatabaseMethodInterceptor implements MethodInterceptor {
private final Object target;
public DatabaseMethodInterceptor(Object target) {
this.target = target;
}
// 拦截目标类所有非final方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return DatabaseLogUtil.doInvoke(
method.getName(), args,
() -> log.debug("CGLIB拦截数据库方法:{}", method.getName()),
() -> proxy.invokeSuper(target, args)
);
}
}
(3)CGLIB 代理工厂
java
import net.sf.cglib.proxy.Enhancer;
public class CglibProxyFactory {
@SuppressWarnings("unchecked")
public static <T> T getProxy(T target) {
Enhancer enhancer = new Enhancer();
// 设置父类(目标类,无需接口)
enhancer.setSuperclass(target.getClass());
// 设置方法拦截器
enhancer.setCallback(new DatabaseMethodInterceptor(target));
// 生成子类代理对象
return (T) enhancer.create();
}
}
(4)使用方式(两种场景都支持)
(1)场景 1:目标有接口(你的 IDatabaseTemplate)
java
DefaultDatabaseTemplate realTarget = new DefaultDatabaseTemplate();
// 代理返回目标类子类对象,可强转IDatabaseTemplate接口
IDatabaseTemplate proxy = (IDatabaseTemplate) CglibProxyFactory.getProxy(realTarget);
proxy.executeUpdate("insert into test values(?)", 100);
(2)场景 2:目标无任何接口(纯普通类,JDK 代理做不到)
java
// 假设没有接口的普通数据库工具类
class SimpleDbUtil {
public void select() {}
}
SimpleDbUtil proxy = CglibProxyFactory.getProxy(new SimpleDbUtil());
(5)CGLIB 代理 优缺点
(1)优点
1-不强制要求目标实现接口,任意普通类都可以代理;
2-拦截目标类全部非 final 方法,不管是接口实现方法还是类自有方法;
3-Spring AOP 默认自动切换:有接口用 JDK,无接口自动用 CGLIB。
(2)缺点
1-底层字节码生成子类,无法代理被 final 修饰的类 / 方法;因为final 方法无法被子类重写,无法拦截增强;
2-依赖第三方 cglib 字节码框架,JDK 原生不自带;
3-生成子类字节码有少量创建开销,频繁创建代理对象性能不如 JDK;
4-必须有无参构造函数,无无参构造会抛异常;构造方法会多执行一次(字节码创建子类时默认调用父类无参构造)。
【4】选型建议
使用场景:基于接口IDatabaseTemplate做数据库方法增强
(1)优先推荐:JDK 动态代理
1-面向标准接口开发,无第三方依赖,代码易维护;
2-新增数据库方法无需改动代理拦截代码,完全适配后续迭代;
(2)不推荐静态代理:接口一旦新增方法,代理类必须同步修改,长期维护成本爆炸;
(3)CGLIB 备选:如果后续有不需要接口的数据库工具类,再使用 CGLIB 统一增强;
(4)Spring 项目可直接使用 AOP 封装 JDK 动态代理,底层自动完成代理创建,无需手动写工厂。

【三】动态代理分析
【1】JDK动态代理为什么只能代理实现了接口的类?
(1)JDK Proxy 生成的代理类 固定实现目标接口
java.lang.reflect.Proxy 在运行时会动态生成一个全新的 Java 字节码类,这个代理类有两个硬性设计约束:
(1)代理类固定继承父类:java.lang.reflect.Proxy
Java 语法规定:一个类只能有一个直接父类(单继承)。
生成的代理类已经硬编码继承了Proxy,就再也不能继承你的业务目标类(比如DefaultDatabaseTemplate)。
(2)想要具备和目标对象一模一样的方法,只能让动态代理类去实现目标对象的所有接口。
例如,目标类如下:
java
public class DefaultDatabaseTemplate implements IDatabaseTemplate {
@Override
public List<Map> queryList(String sql, Object... params) { ... }
}
JDK 动态代理自动生成的代理类伪代码:
java
// 强制继承Proxy,单继承限制导致无法继承DefaultDatabaseTemplate
public final class $Proxy0 extends Proxy implements IDatabaseTemplate {
private InvocationHandler h;
// 构造传入InvocationHandler
public $Proxy0(InvocationHandler h) {
super(h);
this.h = h;
}
// 实现接口所有方法,统一转发给invoke
@Override
public List<Map> queryList(String sql, Object... params) {
Object[] args = new Object[]{sql, params};
return (List<Map>) h.invoke(this, 接口方法对象, args);
}
}
关键矛盾:
$Proxy0 extends Proxy 已经占用唯一父类名额,不可能再写 extends DefaultDatabaseTemplate。
想要拥有 queryList/queryOne 这些数据库方法,唯一方案就是实现 IDatabaseTemplate 接口。
(2)设计定位:JDK 动态代理初衷是「面向接口代理」
Java 原生反射模块设计Proxy的初衷:做标准的接口门面、远程 RPC、标准接口拦截,遵循面向接口编程思想:
(1)接口定义行为规范,实现类是内部细节;
(2)代理只关心「接口定义的行为」,不关心具体实现子类;
(3)如果允许代理普通无接口类,会破坏面向接口的设计思想,同时增加字节码实现复杂度。
(3)方法拦截的实现逻辑:只拦截接口声明的方法
(1)动态生成的$Proxy0只会重写接口中定义的方法;
(2)目标类里不属于接口的自有方法(普通 public 方法、protected 方法),代理类不会生成对应重写逻辑;
(3)如果目标没有接口,Proxy 无法知道需要生成哪些方法,直接无法创建代理类。
对比 CGLIB:
CGLIB 不走接口,使用 ASM 字节码框架生成目标类的子类,重写父类所有非 final 方法,不受单继承限制,所以不需要接口。
(4)总结原因
(1)Java 是单继承(extend)语言,JDK 动态代理生成的代理类已经固定继承了java.lang.reflect.Proxy类,无法再继承业务实现类;
(2)要想获取并增强业务实现类的方法,要么使用extend继承父类,要么使用implements实现接口,现在继承的路子走不通了,就只能使用implements实现接口了,所以要求只能代理实现了接口的类。
【2】JDK动态代理为什么不能代理私有 / 公共非接口方法?
根据上一个问题,JDK动态代理只能使用implements来获取代理类的方法,而implements代理的特性限制了只能获取public的方法。
【3】JDK动态代理和CGLIB动态代理的核心区别?
JDK动态代理受限于JAVA是单继承的语言特性,所以只能代理实现接口的类的公共方法。
而 CGLIB 基于 ASM 字节码框架,直接读取父类 Class 字节码,运行时动态生成一个目标业务类的子类作为代理对象,直接继承父类即可获得全部方法,不需要借助接口定义行为,可以直接扫描并重写父类所有非 final 方法,所以不需要业务类必须实现接口
【四】spring源码中代理模式设计的案例
Spring 核心两大场景大量使用代理:AOP 切面增强、IoC 容器动态包装 Bean,同时内置多种代理实现
Spring 两套代理底层封装:
(1)JdkDynamicAopProxy:JDK 动态代理,目标实现接口时默认使用;
(2)CglibAopProxy:CGLIB 字节码代理,无接口 / 强制 CGLIB 时使用;
统一对外门面:AopProxy 顶层接口,统一 getProxy() 获取代理对象。
【1】核心案例 1:Spring AOP 切面代理
(1)场景说明
给 Bean 方法做增强:日志、耗时、事务、权限、异常捕获,底层全部靠动态代理包装目标 Bean。
(2)源码流程
(1)容器创建 Bean 后,走 AbstractAutoProxyCreator(如 AnnotationAwareAspectJAutoProxyCreator)后置处理器;
(2)wrapIfNecessary() 判断是否需要切面增强;
(3)有切面逻辑时,创建 AopProxy 实现类:
1-目标类存在接口 → JdkDynamicAopProxy(JDK 动态代理);
2-目标无接口 / 配置proxy-target-class=true → CglibAopProxy(CGLIB 代理);
(4)返回代理对象放入单例池,业务注入拿到的是代理而非原始对象。
(3)关键类
(1)顶层统一接口:org.springframework.aop.framework.AopProxy
(2)JDK 代理实现:JdkDynamicAopProxy,内部持有 InvocationHandler 实现 AopInvocationHandler;
(3)CGLIB 代理实现:CglibAopProxy,内部持有 MethodInterceptor 实现 DynamicAdvisedInterceptor;
(4)入口生成器:DefaultAopProxyFactory,自动判断选用哪种代理。
【2】核心案例 2:Spring 声明式事务 @Transactional
(1)实现原理
@Transactional 依靠 AOP 代理实现事务拦截,本质就是上面的 AOP 代理。
(1)开启事务注解后,自动注册 BeanFactoryTransactionAttributeSourceAdvisor 切面;
(2)容器生成 Service 代理对象;
(3)调用带有@Transactional的方法时,代理拦截器先开启事务、方法执行后提交 / 回滚。
(2)关键现象印证代理特性
(1)同一个类内 this.xxx() 调用事务方法失效:this 是原始对象,不是代理对象,无法触发拦截;
(2)接口 Service 默认 JDK 代理,仅拦截接口定义方法;
(3)配置 proxy-target-class=true 强制 CGLIB 代理,可拦截类自有方法。
【3】案例 3:Spring BeanFactory 工厂代理、FactoryBean 代理包装
FactoryBean 代理包装(动态代理)
FactoryBean 可以返回代理对象而非原始目标 Bean,典型场景:Mybatis MapperFactoryBean
Mybatis Mapper 只有 Mapper 接口,无实现类:
(1)MapperFactoryBean#getObject() 内部使用 JDK 动态代理生成 Mapper 接口代理;
(2)调用 Mapper 接口方法时,代理拦截器转换为 Mybatis JDBC 查询。
和 Feign 思路完全一致:纯接口靠 JDK 动态代理实现逻辑。
【4】案例 4:CGLIB 代理专用场景:无接口类代理、Spring Boot 强制 CGLIB
(1)SpringBoot 2.x 后默认开启 proxy-target-class=true,全局优先 CGLIB 代理;
(2)当目标类没有实现任何接口时,Spring 自动切换 CGLIB;
(3)Configuration 配置类内部@Bean依赖调用:使用 CGLIB 代理拦截方法,保证单例 Bean 不会重复创建。
经典坑:@Configuration 类普通方法调用重复实例化
依靠 CGLIB 代理拦截方法,先从容器拿 Bean,而非直接 new,是 CGLIB 代理典型落地。
【5】总结
(1)Spring AOP 是动态代理集大成者,内置两套 JDK/CGLIB 动态代理自动切换;
(2)RPC、Mybatis Mapper 纯接口场景统一使用JDK 动态代理;
(3)数据源事务感知包装等固定增强使用静态代理;
(4)无接口类、配置类依赖拦截使用CGLIB 动态代理。