一、什么是代理
代理 = 中介 / 帮你办事的人
你不想亲自做一件事,找个人帮你做,但你还是能控制他做什么、怎么做。
你(目标对象)→ 代理(中介)→ 实际办事
↑
你可以在这里加额外操作(记录日志、权限校验、事务管理)
二、静态代理 vs 动态代理
2.1 静态代理(编译时就确定代理谁)
// 1. 定义接口
public interface userservice {
void saveuser(string name);
void deleteuser(long id);
}
// 2. 真正的实现类
public class userserviceimpl implements userservice {
@override
public void saveuser(string name) {
system.out.println("保存用户:" + name);
}
@override
public void deleteuser(long id) {
system.out.println("删除用户:" + id);
}
}
// 3. 代理类(需要手动写,代理谁就写谁的代理)
public class userserviceproxy implements userservice {
private userservice target; // 持有真正的实现类
public userserviceproxy(userservice target) {
this.target = target;
}
@override
public void saveuser(string name) {
system.out.println("【代理】开始执行");
long start = system.currenttimemillis();
target.saveuser(name); // 调用真正的实现
long cost = system.currenttimemillis() - start;
system.out.println("【代理】执行完成,耗时 " + cost + "ms");
}
@override
public void deleteuser(long id) {
system.out.println("【代理】开始执行");
target.deleteuser(id);
system.out.println("【代理】执行完成");
}
}
// 4. 使用
public class main {
public static void main(string[] args) {
userservice target = new userserviceimpl();
userservice proxy = new userserviceproxy(target);
proxy.saveuser("张三");
}
}静态代理的问题:
有 10 个接口 → 要写 10 个代理类
有 100 个接口 → 要写 100 个代理类
太累了
动态代理解决这个问题:不需要手写代理类,运行时自动生成。
三、动态代理的两种实现
| jdk 动态代理 | cglib 动态代理 | |
|---|---|---|
| 要求 | 目标类必须实现接口 | 不需要接口,只要不是 final 类 |
| 原理 | 基于 java.lang.reflect.proxy | 基于字节码生成子类(asm) |
| 性能 | 反射调用,稍慢 | 字节码生成,稍快 |
| spring 默认 | 目标类有接口时用 jdk 代理 | 目标类没接口时用 cglib |
| 限制 | 只能代理接口方法 | 不能代理 final 类/方法 |
spring boot 2.x 默认全部用 cglib(即使有接口也用 cglib,因为性能更好)。
四、jdk 动态代理(mybatis 用的就是这个)
4.1 核心类
java.lang.reflect.proxy(创建代理对象) java.lang.reflect.invocationhandler(拦截调用的处理器)
4.2 手写一个 jdk 动态代理
import java.lang.reflect.invocationhandler;
import java.lang.reflect.method;
import java.lang.reflect.proxy;
public class jdkproxydemo {
public static void main(string[] args) {
// 1. 创建目标对象
userservice target = new userserviceimpl();
// 2. 创建代理对象
userservice proxy = (userservice) proxy.newproxyinstance(
target.getclass().getclassloader(), // 类加载器
target.getclass().getinterfaces(), // 要代理的接口
new myinvocationhandler(target) // 调用处理器
);
// 3. 调用代理对象的方法
proxy.saveuser("张三");
proxy.deleteuser(1l);
}
}
// 3. 定义调用处理器(核心!拦截所有方法调用)
class myinvocationhandler implements invocationhandler {
private object target; // 被代理的目标对象
public myinvocationhandler(object target) {
this.target = target;
}
@override
public object invoke(object proxy, method method, object[] args) throws throwable {
// --- 代理逻辑开始 ---
string methodname = method.getname();
system.out.println("【代理】方法开始:" + methodname);
long start = system.currenttimemillis();
// --- 代理逻辑结束 ---
// 调用真正的目标方法
object result = method.invoke(target, args);
// --- 代理逻辑开始 ---
long cost = system.currenttimemillis() - start;
system.out.println("【代理】方法结束:" + methodname + ",耗时 " + cost + "ms");
// --- 代理逻辑结束 ---
return result;
}
}运行结果:
【代理】方法开始:saveuser
保存用户:张三
【代理】方法结束:saveuser,耗时 0ms
【代理】方法开始:deleteuser
删除用户:1
【代理】方法结束:deleteuser,耗时 0ms
4.3 每一步发生了什么
proxy.newproxyinstance() 做了什么?
│
▼
① 根据 classloader + interface[] 动态生成一个新类
│ 这个类实现了 userservice 接口
│ 但它的方法实现全部委托给 invocationhandler
│
▼
② 生成的类大致长这样(你不需要写,jvm 自动帮你生成):
│
│ class $proxy0 implements userservice {
│ invocationhandler h;
│
│ public void saveuser(string name) {
│ // 通过反射拿到方法对象
│ method m = userservice.class.getmethod("saveuser", string.class);
│ // 交给 invocationhandler 处理
│ h.invoke(this, m, new object[]{name});
│ }
│
│ public void deleteuser(long id) {
│ method m = userservice.class.getmethod("deleteuser", long.class);
│ h.invoke(this, m, new object[]{id});
│ }
│ }
│
▼
③ new $proxy0() → 返回代理对象
4.4 为什么 mybatis 用的是 jdk 动态代理
// mybatis 的 mapper 是接口
public interface usermapper {
user selectbyid(long id);
}
// jdk 动态代理要求:目标必须实现接口 ✅
// mapper 恰好是接口 → 天然适合 jdk 动态代理
// mybatis 内部大致是这样的
object mapperproxy = proxy.newproxyinstance(
usermapper.class.getclassloader(),
new class[]{usermapper.class},
new mapperproxy(sqlsession) // 拦截器
);
// 拿到的 mapperproxy 就是你可以直接用的 "mapper 实现"五、cglib 动态代理(spring aop 底层用的就是这个)
5.1 核心原理
不需要接口,通过 asm 字节码框架生成目标类的子类,重写父类方法来实现拦截。
目标类:userserviceimpl(普通类,没实现接口)
│
▼
cglib 生成子类:userserviceimpl$$enhancerbyspringcglib
│ 这个子类继承了 userserviceimpl
│ 重写了所有非 final 的 public 方法
│
▼
调用子类的方法时 → 拦截 → 转发给 methodinterceptor
5.2 手写一个 cglib 代理
import net.sf.cglib.proxy.enhancer;
import net.sf.cglib.proxy.methodinterceptor;
import net.sf.cglib.proxy.methodproxy;
import java.lang.reflect.method;
public class cglibproxydemo {
public static void main(string[] args) {
// 1. 创建 cglib 代理
enhancer enhancer = new enhancer();
enhancer.setsuperclass(userserviceimpl.class); // 父类是谁
enhancer.setcallback(new mymethodinterceptor()); // 拦截器
// 2. 创建代理对象(子类)
userserviceimpl proxy = (userserviceimpl) enhancer.create();
// 3. 调用
proxy.saveuser("张三");
}
}
// 拦截器(类似 jdk 的 invocationhandler)
class mymethodinterceptor implements methodinterceptor {
@override
public object intercept(object obj, method method, object[] args, methodproxy methodproxy)
throws throwable {
system.out.println("【cglib 代理】方法开始:" + method.getname());
long start = system.currenttimemillis();
// 调用父类的方法(不是 method.invoke)
object result = methodproxy.invokesuper(obj, args);
long cost = system.currenttimemillis() - start;
system.out.println("【cglib 代理】方法结束:" + method.getname() + ",耗时 " + cost + "ms");
return result;
}
}注意:cglib 调用父类方法用 methodproxy.invokesuper(obj, args),而不是 method.invoke()(那会死循环调自己)。
六、jdk 代理 vs cglib 代理的代码对比
// ========== jdk 动态代理 ==========
// 1. 目标必须有接口
public interface userservice { void save(string name); }
public class userserviceimpl implements userservice { ... }
// 2. 用 proxy.newproxyinstance
object proxy = proxy.newproxyinstance(
classloader,
new class[]{userservice.class}, // 接口
new invocationhandler() {
public object invoke(object p, method m, object[] args) {
// 拦截逻辑
return m.invoke(target, args); // 调目标方法
}
}
);
// ========== cglib 动态代理 ==========
// 1. 目标可以是普通类(不需要接口)
public class userservice { public void save(string name) { ... } }
// 2. 用 enhancer
enhancer enhancer = new enhancer();
enhancer.setsuperclass(userservice.class); // 父类
enhancer.setcallback(new methodinterceptor() {
public object intercept(object obj, method m, object[] args, methodproxy mp) {
// 拦截逻辑
return mp.invokesuper(obj, args); // 调父类方法
}
});
object proxy = enhancer.create();七、spring aop 和动态代理的关系
spring aop 的底层就是动态代理。
@service
public class userservice {
@transactional // spring aop 拦截这个方法
public void saveuser(string name) {
// 你的业务代码
usermapper.insert(name);
}
}spring 怎么拦截的?
spring 启动
│
▼
发现 userservice 有 @transactional
│
▼
用动态代理包装 userservice
│ 有接口 → jdk 动态代理(或 cglib,看配置)
│ 没接口 → cglib 动态代理
│
▼
你注入的 userservice 是代理对象
│
▼
调用 saveuser() 时
│ 代理拦截 → 开启事务 → 执行你的方法 → 提交/回滚事务
// 你写的代码(看起来像直接调)
userservice.saveuser("张三");
// 实际执行的(代理拦截后)
@transactional 开启
→ userservice.saveuser("张三") // 真正的业务方法
@transactional 提交所以 spring aop = 动态代理 + 切面逻辑。
八、mybatis + spring aop 的动态代理对比
| mybatis mapper | spring aop | |
|---|---|---|
| 代理谁 | mapper 接口 | service/controller 等 bean |
| 代理方式 | jdk 动态代理(mapper 是接口) | 有接口用 jdk,没接口用 cglib |
| 拦截器 | mapperproxy(找 sql、执行) | transactioninterceptor(事务管理)等 |
| 用途 | 把接口调用转成 sql 执行 | 日志、事务、权限、缓存等切面功能 |
| 创建时机 | spring 启动时 | spring 启动时 |
| 拦截时机 | 每次调用 mapper 方法时 | 每次调用 bean 方法时 |
九、面试话术汇总
q1:什么是动态代理?和静态代理的区别?
话术:动态代理不需要手写代理类,在运行时通过反射或字节码技术自动生成。静态代理每个目标类都要写一个代理类,10 个接口写 10 个,维护成本高。动态代理只需要一个处理器,所有方法调用都走同一个处理器,通用性强。jdk 动态代理基于反射,cglib 基于字节码生成子类。
q2:jdk 动态代理的原理?
话术:jdk 动态代理通过 proxy.newproxyinstance() 方法,传入目标类的接口和一个 invocationhandler 处理器,jvm 在运行时动态生成一个实现了目标接口的代理类。调用代理对象的方法时,所有调用都会被路由到 invocationhandler 的 invoke() 方法,在这里可以插入额外逻辑,再通过反射调用目标对象的真正方法。
q3:cglib 动态代理的原理?
话术:cglib 通过 asm 字节码框架,运行时生成目标类的子类,重写所有非 final 的 public 方法。调用子类方法时,拦截交给 methodinterceptor,再调用父类的原始方法。因为是生成子类,所以不需要接口,但不能代理 final 类和 final 方法。spring boot 2.x 默认全部用 cglib,即使有接口也用,因为性能比 jdk 反射调用更好。
q4:spring 默认用哪种动态代理?
话术:spring boot 2.x 之前,默认规则是目标类有接口用 jdk 动态代理,没有接口用 cglib。spring boot 2.x 之后,默认全部用 cglib,因为 cglib 性能更好。可以通过 spring.aop.proxy-target-class=false 强制用 jdk 代理。
q5:mybatis 的 mapper 为什么用 jdk 动态代理?
话术:因为 mapper 是接口,jdk 动态代理要求目标必须有接口,正好匹配。mybatis 用 proxy.newproxyinstance() 创建代理对象,invocationhandler 是 mapperproxy,里面做了三件事:根据方法名找 sql、通过 sqlsession 执行、结果映射成 java 对象。
q6:动态代理有什么局限性?
话术:两个局限。jdk 动态代理只能代理接口方法,不能代理非接口方法;cglib 不能代理 final 类和 final 方法(因为是生成子类,final 不能被重写)。性能上,jdk 代理通过反射调用,cglib 通过字节码生成方法直接调用,cglib 首次生成稍慢但后续调用更快。另外动态代理会增加调试难度,因为堆栈里多了一层代理类。
十、一张图看完整个知识体系
动态代理
├── 静态代理(手写代理类,不灵活)
│
├── jdk 动态代理(基于接口)
│ ├── proxy.newproxyinstance()
│ ├── invocationhandler.invoke()
│ └── 应用:mybatis mapper、spring aop(有接口时)
│
└── cglib 动态代理(基于子类)
├── enhancer + methodinterceptor
├── asm 字节码生成子类
└── 应用:spring aop(无接口时 / spring boot 2.x 全部用)
十一、一句话速记
代理 = 中介,帮你干活还能加额外操作
jdk 代理 = 基于接口,proxy + invocationhandler,反射调用
cglib 代理 = 基于子类,enhancer + methodinterceptor,字节码生成
mybatis 用 jdk 代理(mapper 是接口)
spring boot 2.x 默认全用 cglib(性能更好)
动态代理 = spring aop 的底层原理
到此这篇关于java动态代理原理入门与面试话术汇总的文章就介绍到这了,更多相关java动态代理原理内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论