适用范围:
为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
例如:我们要去买火车票
- 抽象接口:都是卖票的
- 被代理对象:12306官方售票
- 代理对象:各种第三方代理售商
- 用户去使用第三方代理买票,本质上就是实现了代理—-通过对代理类的访问控制被代理对象
三种常用动态代理
1.静态代理
创建一个接口,然后创建被代理的类实现该接口并且实现该接口中的抽象方法。之后再创建一个代理类,同时使其也实现这个接口。在代理类中持有一个被代理对象的引用,而后在代理类方法中调用该对象的方法。
- 接口:
1 2 3
| public interface SaleTicket { void sale(int money); }
|
- 被代理类:
1 2 3 4 5 6 7 8 9 10
| public class SaleBy12306 implements SaleTicket{ public void sale(int money) { if(money > 100){ System.out.println("购票成功!"); } else { System.out.println("购票失败!"); } } }
|
- 代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class SaleByOthers implements SaleTicket { private final SaleBy12306 saleTicket;
public SaleByOthers(SaleBy12306 saleTicket) { this.saleTicket = saleTicket; }
// 代理商 除了正常卖票还能 打折...之类的 public void sale(int money) { System.out.println("打折"); saleTicket.sale(money); // 最后还是去12306取票 } }
|
- 代理类调用:
被代理类被传递给了代理类SaleByOthers,代理类在执行具体方法时通过所持用的被代理类完成调用。
1 2 3 4 5 6 7 8 9 10 11
| public class Client { public static void main(String[] args) { SaleBy12306 saleBy12306 = new SaleBy12306(); SaleByOthers saleByOthers = new SaleByOthers(saleBy12306); saleByOthers.sale(200); } } 输出: 打折 购票成功!
|
总结: 使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
2.动态代理
利用反射机制在运行时创建代理类,实现对被代理对象的访问控制。
接口、被代理类不变,我们构建一个handler类来实现InvocationHandler接口。
- 接口:
1 2 3
| public interface SaleTicket { void sale(int money); }
|
- 被代理类:
1 2 3 4 5 6 7 8 9 10
| public class SaleBy12306 implements SaleTicket{ public void sale(int money) { if(money > 100){ System.out.println("购票成功!"); } else { System.out.println("购票失败!"); } } }
|
- 代理类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| // 售票代理商 处理器 public class SaleTicketHandler implements InvocationHandler { private Object object;
public SaleTicketHandler(Object object) { this.object = object; }
/** * 动态代理执行方法 类似Thread的run方法 * @param proxy 被代理对象 * @param method 被代理对象的方法 * @param args 被代理方法参数 * @return * @throws Throwable */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 一样在售票前后可以做操作 System.out.println("售票前...." + method.getName()); // 使用反射执行12306的售票方法 Object reslut = method.invoke(object,args); System.out.println("售票后...." + method.getName()); return reslut; } }
|
- 代理类调用:
被代理类被传递给了代理类saleTicketByProxy ,代理类在执行具体方法时通过所持用的被代理类完成调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Client { public static void main(String[] args) { SaleBy12306 saleBy12306 = new SaleBy12306(); InvocationHandler handler = new SaleTicketHandler(saleBy12306); /** * 此处代理类Proxy的newProxyInstance需要三个参数 * 1.被代理对象的类加载器 * 2.被代理对象的接口 * 3.实际的代理对象处理器 -- ProxyHandler */ SaleTicket saleTicketByProxy = (SaleTicket) Proxy.newProxyInstance( saleBy12306.getClass().getClassLoader(), saleBy12306.getClass().getInterfaces(), handler );
saleTicketByProxy.sale(200); } }
|
通过Proxy类的静态方法newProxyInstance返回一个接口的代理实例。针对不同的代理类,传入相应的代理程序控制器InvocationHandler。
- 如果再想加入一个代理对象 也是相同的写法,而且可以使用匿名内部类
1 2 3 4 5 6 7 8 9 10
| SaleTicket saleTicketByProxy2 = (SaleTicket) Proxy.newProxyInstance( saleBy12306.getClass().getClassLoader(), saleBy12306.getClass().getInterfaces(), new InvocationHandler(){ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(saleBy12306,args); } } ); saleTicketByProxy2.sale(200);
|
动态代理底层实现:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
总结: JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
3.CGLIB动态代理
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
1.CGLIB代理机制
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
1 2 3 4 5 6 7 8 9 10
| public interface MethodInterceptor extends Callback{ // 拦截被代理类中的方法 public Object intercept( Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy ) throws Throwable; }
|
- obj :被代理的对象(需要增强的对象)
- method :被拦截的方法(需要增强的方法)
- args :方法入参
- methodProxy :用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
2.CGLIB代理类使用流程
- 定义一个类;
- 自定义
MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke
方法类似
- 通过
Enhancer
类的 create()
创建代理类;
Maven依赖
1 2 3 4 5
| <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
|
1.被代理类
1 2 3 4 5 6 7 8 9 10
| public class SaleBy12306 { public void sale(int money) { if(money > 100){ System.out.println("购票成功!"); } else { System.out.println("购票失败!"); } } }
|
2.自定义 MethodInterceptor
(方法拦截器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class SaleTicketMethodIntercepter implements MethodInterceptor {
/** * @param o 被代理的对象(需要增强的对象) * @param method 被拦截的方法(需要增强的方法) * @param args 方法入参 * @param methodProxy 用于调用原始方法 */ public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //调用方法之前,我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object object = methodProxy.invokeSuper(o, args); //调用方法之后,我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return object; } }
|
3.获取代理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) { // 创建动态代理增强类 Enhancer enhancer = new Enhancer(); // 设置类加载器 enhancer.setClassLoader(clazz.getClassLoader()); // 设置被代理类 enhancer.setSuperclass(clazz); // 设置方法拦截器 enhancer.setCallback(new SaleTicketMethodIntercepter()); // 创建代理类 return enhancer.create(); } }
|
4.实际使用
1 2 3 4 5 6
| public class Client { public static void main(String[] args) { SaleBy12306 saleBy12306 = (SaleBy12306) CglibProxyFactory.getProxy(SaleBy12306.class); saleBy12306.sale(200); } }
|
总结:
1.JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。
- 另外, CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为 final 类型的类和方法。
- 就二者的效率来说,大部分情况都是 JDK 动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
2. 静态代理和动态代理的对比
- 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!
- JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的。
参考资料:
JavaGuide
简书-java动态代理