静态代理和动态代理
代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。
其实代理模式的核心思想就是这么简单,在java中,代理又分静态代理和动态代理2种,其中动态代理根据不同实现又区分JDK动态代理( 基于接口的的动态代理)和Cglib动态代理(基于子类的动态代理 )。
我们日常见到的spring aop,拦截器,mybatis分页插件,以及日志拦截、事务拦截、权限拦截这些都有动态代理的应用。
静态代理和动态代理的区别
最大的区别静态代理编译期确定的,动态代理在运行期确定的。
静态代理缺点:
冗余:由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
不易维护:一旦接口增加方法,目标对象与代理对象都要进行修改。
生活中常见的代理情况:
租房中介、婚庆公司等
代理模式的两个设计原则:
1. 代理类 与 委托类 具有相似的行为(共同)
2. 代理类增强委托类的行为
静态代理
代理的三要素
a、有共同的行为(结婚) - 接口
b、目标角色(新人) - 实现行为
c、代理角色(婚庆公司) - 实现行为 增强目标对象行为
静态代理的特点
1、目标角色固定
2、在应用程序执行前就得到目标角色
3、代理对象会增强目标对象的行为
4、有可能存在多个代理 引起"类爆炸"(缺点)
静态代理的实现
定义行为(共同) 定义接口
1 /** 2 * 定义行为 3 */ 4 public interface Marry { 5 public void toMarry(); 6 }
目标对象(实现行为)
1 /** 2 * 静态代理 ——> 目标对象 3 */ 4 public class ZhangSan implements Marry{ 5 // 实现行为 6 @Override 7 public void toMarry() { 8 System.out.println("欢迎来到张三婚礼现场!"); 9 } 10 }
代理对象(实现行为、增强目标对象的行为)
1 /** 2 * 静态代理 ——> 代理对象 3 */ 4 public class MarryCompanyProxy implements Marry { 5 // 目标对象 6 private Marry marry; 7 8 // 通过构造器将目标对象传入 9 public MarryCompanyProxy(Marry marry) { 10 this.marry = marry; 11 } 12 13 // 实现行为 14 @Override 15 public void toMarry() { 16 // 增强行为 17 before(); 18 // 执行目标对象中的方法 19 marry.toMarry(); 20 // 增强行为 21 after(); 22 } 23 24 /** 25 * 增强行为 26 */ 27 private void after() { 28 System.out.println("新婚快乐,早生贵子!"); 29 } 30 31 /** 32 * 增强行为 33 */ 34 private void before() { 35 System.out.println("场地正在布置中..."); 36 } 37 }
通过代理对象实现目标对象的功能
1 public class Test1 { 2 public static void main(String[] args) { 3 // 目标对象 4 ZhangSan zhangSan = new ZhangSan(); 5 LiSi liSi = new LiSi(); 6 // 构造代理角色同时传入真实角色 7 MarryCompanyProxy marryCompanyProxy = new MarryCompanyProxy(zhangSan); 8 // 通过代理对象调用目标对象中的方法 9 marryCompanyProxy.toMarry(); 10 } 11 }
动态代理
相比于静态代理,动态代理在创建代理对象上更加的灵活,动态代理类的字节码在程序运行时,由Java反射机制动态产生。它会根据需要,通过反射机制在程序运行期,动态的为目标对象创建代理对象,无需程序员手动编写它的源代码。动态代理不仅简化了编程工作,而且提高了软件系统的可扩展性,因为反射机制可以生成任意类型的动态代理类。代理的行为可以代理多个方法,即满足生产需要的同时又达到代码通用的目的。
动态代理的两种实现方式:
1. JDK 动态代理
2. CGLIB动态代理
动态代理的特点
1、目标对象不固定
2、在应用程序执行时动态创建目标对象
3、代理对象会增强目标对象的行为
JDK动态代理
注:JDK动态代理的目标对象必须有接口实现newProxyInstance
Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下操作方法:
1 /* 2 返回一个指定接口的代理类的实例方法调用分派到指定的调用处理程序。 (返回代理对象) 3 loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载 4 interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果 我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这 组接口中的方法了 5 h:一个InvocationHandler接口,表示代理实例的调用处理程序实现的接口。每个代理实例都具有一个 关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序 的 invoke 方法(传入InvocationHandler接口的子类) 6 */ 7 public static Object newProxyInstance(ClassLoader loader, 8 Class<?>[] interfaces, 9 InvocationHandler h)
获取代理对象
1 import java.lang.reflect.InvocationHandler; 2 import java.lang.reflect.Method; 3 import java.lang.reflect.Proxy; 4 5 public class JdkHandler implements InvocationHandler { 6 // 目标对象 7 private Object target; // 目标对象的类型不固定,创建时动态生成 8 9 // 通过构造器将目标对象赋值 10 public JdkHandler(Object target) { 11 this.target = target; 12 } 13 14 /** 15 * 1、调用目标对象的方法(返回Object) 16 * 2、增强目标对象的行为 17 * 18 * @param proxy 调用该方法的代理实例 19 * @param method 目标对象的方法 20 * @param args 目标对象的方法形参 21 * @return 22 * @throws Throwable 23 */ 24 @Override 25 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 26 // 增强行为 27 System.out.println("==============方法前执行"); 28 // 调用目标对象的方法(返回Object) 29 Object result = method.invoke(target, args); 30 // 增强行为 31 System.out.println("方法后执行=============="); 32 return result; 33 } 34 35 /** 36 * 得到代理对象 37 * public static Object newProxyInstance(ClassLoader loader, 38 * Class<?>[] interfaces, 39 * InvocationHandler h) 40 * loader:类加载器 41 * interfaces:接口数组 42 * h:InvocationHandler接口 (传入InvocationHandler接口的实现类) 43 * 44 * @return 45 */ 46 public Object getProxy() { 47 return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this); 48 } 49 }
通过代理对象实现目标对象的功能
1 public class Test1 { 2 public static void main(String[] args) { 3 //JDK动态代理 4 // 目标对象 5 ZhangSan zhangSan = new ZhangSan(); 6 // 获取代理对象 7 JdkHandler jdkHandler = new JdkHandler(zhangSan); 8 Marry marry = (Marry) jdkHandler.getProxy(); 9 // 通过代理对象调用目标对象中的方法 10 marry.toMarry(); 11 } 12 }
注:JDK的动态代理依靠接口实现,如果有些类并没有接口实现,则不能使用JDK代理。
CGLIB 动态代理
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能使用JDK的动态代理,cglib是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
添加依赖
在pom.xml文件中引入cglib的相关依赖
1 <dependency> 2 <groupId>cglib</groupId> 3 <artifactId>cglib</artifactId> 4 <version>2.2.2</version> 5 </dependency>
定义类
实现MethodInterceptor接口
1 import net.sf.cglib.proxy.Enhancer; 2 import net.sf.cglib.proxy.MethodInterceptor; 3 import net.sf.cglib.proxy.MethodProxy; 4 5 import java.lang.reflect.Method; 6 7 public class CglibInterceptor implements MethodInterceptor { 8 // 目标对象 9 private Object target; 10 11 // 通过构造器传入目标对象 12 public CglibInterceptor(Object target) { 13 this.target = target; 14 } 15 16 /** 17 * 获取代理对象 18 * 19 * @return 20 */ 21 public Object getProxy() { 22 //通过Enhancer对象的create()方法可以生成一个类,用于生成代理对象 23 Enhancer enhancer = new Enhancer(); 24 //设置父类 (将目标类作为其父类) 25 enhancer.setSuperclass(target.getClass()); 26 //设置拦截器 回调对象为本身对象 27 enhancer.setCallback(this); 28 //生成一个代理类对象,并返回 29 return enhancer.create(); 30 } 31 32 /** 33 * 拦截器 34 * 1、目标对象的方法调用 35 * 2、增强行为 36 * 37 * @param object 由CGLib动态生成的代理类实例 38 * @param method 实体类所调用的被代理的方法引用 39 * @param objects 参数值列表 40 * @param methodProxy 生成的代理类对方法的代理引用 41 * @return 42 * @throws Throwable 43 */ 44 @Override 45 public Object intercept(Object object, Method method, Object[] objects, 46 MethodProxy methodProxy) throws Throwable { 47 // 增强行为 48 System.out.println("==============方法前执行"); 49 // 调用目标对象的方法(返回Object) 50 Object result = methodProxy.invoke(target, objects); 51 // 增强行为 52 System.out.println("方法后执行=============="); 53 return result; 54 } 55 }
1 public class Test1 { 2 public static void main(String[] args) { 3 //CGLIB动态代理 4 // 目标对象 5 ZhangSan zhangSan = new ZhangSan(); 6 CglibInterceptor cglibInterceptor = new CglibInterceptor(zhangSan); 7 Marry marry = (Marry) cglibInterceptor.getProxy(); 8 marry.toMarry(); 9 } 10 }
JDK代理与CGLIB代理的区别
JDK动态代理实现接口,Cglib动态代理继承思想
JDK动态代理(目标对象存在接口时)执行效率高于Ciglib
如果目标对象有接口实现,选择JDK代理,如果没有接口实现选择Cglib代理
- Spring 中的 AOP,有接口就用 JDK 动态代理,没有接口就用 Cglib 动态代理。
- Spring Boot 中的 AOP,2.0 之前和 Spring 一样;2.0 之后首选 Cglib 动态代理,如果用户想要使用 JDK 动态代理,需要自己手动配置。