前言作为 Java 开发者代理模式绝对是绕不开的核心知识点 —— 它是 Spring AOP、事务管理、RPC 框架等底层实现的基石也是面试高频考点。接下来我会从核心概念以及分类静态代理、动态代理讲解带你深入了解代理是什么一、代理模式是什么1.初识拿生活里的小例子来初步认识买房子不直接找房东找房产中介代理中介帮你带看、谈价、签合同增强功能最终还是房东卖房核心功能买火车票不直接去火车站找票务代理点代理点帮你买票最终还是铁路系统售票2.程序中的代理目标对象 / 被代理类真正做核心业务的类代理对象 / 代理类不做核心业务只做增强操作日志、事务、权限校验接口目标类和代理类共同实现的规范保证两者方法一致代理模式就是在调用目标类中间创建一个代理类直接调用变成间接调用这样我们可以在代理类调用目标类之前和之后去添加一些预处理和后处理的操作来扩展目标类没有的功能3.核心作用无侵入式增强代码不修改业务代码控制对目标对象的访问解耦核心业务与通用功能日志、事务4.分类分为静态代理、动态代理二、静态代理手写代理类1.什么是静态代理就是手动编写代理类对象编译后直接生成.class文件并且代理类和目标类要实现同一个接口2.优缺点优点简单易懂、适合固定业务缺点冗余度极高一个目标类就要写一个代理类编译前确定代理运行时无法动态新增无法代理全部方法如果接口新增方法所有实现类代理类都必须重写该方法3.静态代理代码示例就拿房东买房为例1定义公共接口目标类和代理类都要实现这个接口保证方法统一/** * 公共接口租房接口 */ public interface RentHouse { // 核心方法租房 void rent(); }2定义目标类房东只做核心业务出租自己的房子/** * 目标类房东被代理对象 */ public class Landlord implements RentHouse { Override public void rent() { System.out.println(房东出租房子); } }3定义代理类房产中介包含目标对象调用核心方法增强方法/** * 代理类中介代理对象 */ public class RentProxy implements RentHouse { // 持有目标对象房东 private RentHouse target; // 构造方法传入目标对象 public RentProxy(RentHouse target) { this.target target; } Override public void rent() { // 【增强操作方法执行前】 before(); // 调用目标对象的核心方法 target.rent(); // 【增强操作方法执行后】 after(); } // 前置增强带看房屋 private void before() { System.out.println(中介带看房屋、讲解户型); } // 后置增强签订合同 private void after() { System.out.println(中介签订租房合同、收取佣金); } }4测试测试代码public class StaticProxyTest { public static void main(String[] args) { // 1. 创建目标对象房东 RentHouse landlord new Landlord(); // 2. 创建代理对象传入目标对象 RentHouse proxy new RentProxy(landlord); // 3. 调用代理对象的方法 proxy.rent(); } }运行结果中介带看房屋、讲解户型 房东出租房子 中介签订租房合同、收取佣金4.致命缺点如果接口新增方法所有实现类 代理类都必须重写该方法这会导致代码爆炸维护成本极高解决方案是使用动态代理public interface RentHouse { void rent(); // 新增方法 void repairHouse(); }三、动态代理自动生成代理类动态代理的核心程序运行时自动生成代理类字节码不需要手动编写 .java 文件Java 中主流有两种动态代理JDK 动态代理基于接口实现必须有接口CGLIB 动态代理基于继承实现无接口也能用四、JDK动态代理Java原生基于接口1.核心原理必须要求目标类实现接口通过java.lang.reflect.Proxy类自动生成代理对象通过InvocationHandler接口处理方法增强逻辑2.核心APIProxy.newProxyInstance(类加载器, 接口数组, 调用处理器)生成代理对象InvocationHandler.invoke(Object proxy, Method method, Object[] args)所有代理方法都会走这里3.JDK动态代理实例继续用上面的RentHouse接口和Landlord目标类1编写通用调用处理器所有额外增加的逻辑都在这里面一个处理器可以给多个目标类使用import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * JDK动态代理通用调用处理器 */ public class JdkProxyHandler implements InvocationHandler { // 目标对象 private Object target; public JdkProxyHandler(Object target) { this.target target; } /** * param proxy 代理对象 * param method 要执行的目标方法 * param args 方法参数 * return 方法返回值 * throws Throwable */ Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强 before(); // 执行目标方法 Object result method.invoke(target, args); // 后置增强 after(); return result; } private void before() { System.out.println(JDK代理前置增强日志/权限); } private void after() { System.out.println(JDK代理后置增强事务/统计); } }2编写工具类获取代理对象import java.lang.reflect.Proxy; /** * JDK代理工具类 */ public class JdkProxyFactory { public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目标类的类加载器 target.getClass().getInterfaces(), // 目标类实现的接口 new JdkProxyHandler(target) // 调用处理器 ); } }3测试public class JdkProxyTest { public static void main(String[] args) { // 1. 创建目标对象 RentHouse landlord new Landlord(); // 2. 获取JDK动态代理对象 RentHouse proxy (RentHouse) JdkProxyFactory.getProxy(landlord); // 3. 执行方法 proxy.rent(); } }运行结果JDK代理前置增强日志/权限 房东出租房子 JDK代理后置增强事务/统计4.JDK动态代理优缺点优点Java 原生无需引入第三方依赖动态生成代理类无代码冗余解耦通用功能与业务代码缺点必须实现接口没有接口的类无法使用五、CGLIB动态代理基于继承、无接口也能用1.为什么需要CGLIBJDK 动态代理必须基于接口如果一个类没有实现任何接口就无法使用 JDK 代理这时候就用CGLIB。2. 核心原理基于继承实现生成目标类的子类作为代理类重写目标类的非 final 方法实现增强底层使用 ASM 字节码框架生成代理类3.环境准备CGLIB不是java原生需要在配置文件里面引入依赖!-- Maven依赖 -- dependency groupIdcglib/groupId artifactIdcglib/artifactId version3.3.0/version /dependency4.CGLIB动态代理实例我们创建一个没有接口的目标类1创建目标类无接口/** * 目标类普通类没有实现任何接口 */ public class UserService { // 核心业务添加用户 public void addUser() { System.out.println(业务方法添加用户成功); } }2编写CGLIB方法拦截器在该类里面添加额外的方法逻辑import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * CGLIB代理方法拦截器相当于JDK的InvocationHandler */ public class CglibProxyInterceptor implements MethodInterceptor { /** * 所有方法执行都会进入这里 * param o 代理对象 * param method 目标方法 * param objects 参数 * param methodProxy 方法代理 * return * throws Throwable */ Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { // 前置增强 before(); // 执行目标方法调用父类方法因为代理类是子类 Object result methodProxy.invokeSuper(o, objects); // 后置增强 after(); return result; } private void before() { System.out.println(CGLIB代理前置日志); } private void after() { System.out.println(CGLIB代理后置事务); } }3编写CGLIB代理工厂import net.sf.cglib.proxy.Enhancer; /** * CGLIB代理工厂 */ public class CglibProxyFactory { public static Object getProxy(Class? targetClass) { // CGLIB核心类增强器 Enhancer enhancer new Enhancer(); // 设置父类目标类 enhancer.setSuperclass(targetClass); // 设置方法拦截器 enhancer.setCallback(new CglibProxyInterceptor()); // 创建代理对象 return enhancer.create(); } }4测试public class CglibProxyTest { public static void main(String[] args) { // 获取代理对象 UserService proxy (UserService) CglibProxyFactory.getProxy(UserService.class); // 执行方法 proxy.addUser(); } }运行结果CGLIB代理前置日志 业务方法添加用户成功 CGLIB代理后置事务5.注意事项不能代理final类无法继承不能代理final方法无法重写Spring 5 之后默认使用 CGLIB 作为动态代理实现六、静态代理 VS JDK动态代理 VS CGLIB动态代理特性静态代理JDK 动态代理CGLIB 动态代理实现方式手动编写代理类基于接口反射基于继承 字节码生成代理时机编译期生成 .class 文件运行时生成代理类运行时生成代理类是否需要接口必须实现接口必须实现接口不需要接口执行效率最高无反射较高反射调用较低字节码生成依赖无依赖Java 原生无依赖需要引入 CGLIB 依赖Spring 使用场景极少使用有接口时默认使用无接口时使用