设计模式(四)——代理模式
一、概述
1、介绍
代理模式(Proxy):为一个对象提供一个代理,以控制对这个对象的访问,即通过代理对象访问目标对象。这样做的好处是,可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。
代理模式有不同的形式,主要有三种,静态代理、动态代理(JDK代理、接口代理)和Cglib代理(可以在内存动态的创建对象,而不需要实现接口,属于动态代理的范畴)。
二、静态代理
1、介绍
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一起实现相同的接口或者继承相同父类。调用的时候通过调用代理对象的方法来调用目标对象。
2、模式原理
3、代码
代码示例:静态代理
1 // 接口 2 public interface ITeacher { 3 void teach(); 4 } 5 6 // 目标对象 7 public class Teacher implements ITeacher { 8 @Override 9 public void teach() { 10 System.out.println("老师正在上课"); 11 } 12 } 13 14 // 代理对象,静态代理 15 public class TeacherProxy implements ITeacher { 16 // 目标对象,通过构造器来聚合 17 private Teacher teacher; 18 19 public TeacherProxy(Teacher teacher) { 20 this.teacher = teacher; 21 } 22 23 @Override 24 public void teach() { 25 System.out.println("开始静态代理..."); 26 // ... 27 // 执行目标对象的方法 28 teacher.teach(); 29 // ... 30 System.out.println("静态代理提交..."); 31 } 32 } 33 34 public class Main { 35 public static void main(String[] args) { 36 // 创建代理对象,同时将目标对象作为参数传递 37 TeacherProxy teacherProxy = new TeacherProxy(new Teacher()); 38 // 通过代理对象执行目标对象的方法 39 teacherProxy.teach(); 40 } 41 }
4、优缺点
优点:在不修改目标对象功能的前提下,能通过代理对象对目标功能进行扩展。
缺点:因为代理对象需要与目标对象实现相同的接口,所以会有很多代理类。一旦接口增加方法,目标对象与代理对象都要维护。
三、动态代理(JDK代理)
1、介绍
代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理。代理对象的生成是利用JDK的API,动态的在内存中构建代理对象,动态代理也叫JDK代理、接口代理。
2、模式原理
根据传入的目标对象,利用返回机制,返回一个代理对象。然后通过代理对象,调用目标对象方法。
3、代码
代码示例:动态代理,JDK代理
1 // 接口 2 public interface ITeacher { 3 void teach(); 4 5 int add(int i, int j); 6 } 7 8 // 目标对象 9 public class Teacher implements ITeacher { 10 @Override 11 public void teach() { 12 System.out.println("老师正在上课"); 13 } 14 15 @Override 16 public int add(int i, int j) { 17 System.out.println("老师正在做加法"); 18 return i + j; 19 } 20 } 21 22 // 代理对象,动态代理 23 public class TeacherProxy { 24 // 目标对象,通过接口来聚合 25 private Object target; 26 27 public TeacherProxy(Object target) { 28 this.target = target; 29 } 30 31 // 给目标对象生成一个代理对象 32 public Object getProxyInstance() { 33 // ClassLoader loader:指定当前目标对象使用的类加载器 34 // Class<?>[] interfaces:目标对象实现的接口类型,使用泛型方式确认 35 // InvocationHandler h:事件处理,执行目标对象的方法时,会触发事件处理器方法,会把当前执行的目标对象方法作为参数传入 36 return Proxy.newProxyInstance(target.getClass().getClassLoader(), 37 target.getClass().getInterfaces(), 38 new InvocationHandler() { 39 @Override 40 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 41 System.out.println("JDK动态代理开始..."); 42 // 通过反射机制调用目标对象的方法 43 Object invoke = method.invoke(target, args); 44 System.out.println("JDK动态代理提交..."); 45 return invoke; 46 } 47 }); 48 } 49 } 50 51 public class Main { 52 public static void main(String[] args) { 53 // 创建代理对象,同时将创建的目标对象作为参数传递 54 TeacherProxy proxy = new TeacherProxy(new Teacher()); 55 56 // 通过代理对象执行目标对象的方法 57 ITeacher iTeacher = (ITeacher) proxy.getProxyInstance(); 58 iTeacher.teach(); 59 60 int add = iTeacher.add(1, 2); 61 System.out.println(add); 62 } 63 }
四、Cglib代理
1、介绍
目标对象不需要实现接口。静态代理和JDK代理都要求目标对象实现一个接口。但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现代理,即Cglib代理。
Cglib代理也叫作子类代理,它是内存中构建一个子类对象从而实现对目标对象功能扩展,Cglib代理也可以归属到动态代理。
Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP框架使用,例如Spring AOP,实现方法拦截。
在AOP编程中如何选择代理模式:①目标对象需要实现接口:用JDK代理。②目标对象不需要实现接口:用Cglib代理。
Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。
2、代码
代码示例:动态代理,cglib代理
1 // 目标对象.未实现接口 2 public class Teacher { 3 4 public void teach() { 5 System.out.println("老师正在上课"); 6 } 7 8 public int add(int i, int j) { 9 System.out.println("老师正在做加法"); 10 return i + j; 11 } 12 } 13 14 // 代理对象 15 public class ProxyFactory implements MethodInterceptor { 16 private final Object target; 17 18 public ProxyFactory(Object target) { 19 this.target = target; 20 } 21 22 public Object getProxyInstance() { 23 // 1.创建一个工具类 24 Enhancer enhancer = new Enhancer(); 25 // 2.设置父类 26 enhancer.setSuperclass(target.getClass()); 27 // 3.设置回调函数 28 enhancer.setCallback(this); 29 // 4.创建子类对象,即代理对象 30 return enhancer.create(); 31 } 32 33 @Override 34 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 35 System.out.println("cglib代理模式开始..."); 36 Object o1 = method.invoke(target, objects); 37 System.out.println("cglib代理模式结束..."); 38 39 return o1; 40 } 41 } 42 43 public class Main { 44 public static void main(String[] args) { 45 // 创建目标对象 46 Teacher teacher = new Teacher(); 47 48 // 生成目标对象的代理对象 49 Teacher teacherProxy = (Teacher) new ProxyFactory(teacher).getProxyInstance(); 50 51 int add = teacherProxy.add(1, 2); 52 System.out.println("----" + add); 53 } 54 }
注意:cglib代理在内存中动态构建子类,代理的类不能为final,否则报错java.lang.IllegalArgumentException。
目标对象的方法如果为final/static,就不会被代理,即不会执行目标对象额外的业务方法。
3、应用
性能开发:比如,监控、统计、鉴权、限流、事务、幂等、日志。将这些性能类功能与业务逻辑功能解耦,放到代理类中实现,让程序员更专注于业务方面的开发。典型例子就是 SpringAOP。
RPC:远程代理框架也可以看作一种代理模式。通过远程代理,将网络通信、数据编解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解跟服务器交互的细节。除此之外,RPC 服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。
防火墙代理:内网通过代理穿透防火墙,实现对公网的访问。
缓存代理:比如,当请求图片文件等资源时,先到缓存代理取,如果取到资源则ok;如果取不到资源,再到公网或者数据库取,然后缓存。
同步代理:主要使用在多线程编程中,完成多线程间同步工作。