设计模式(四)——代理模式

一、概述

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;如果取不到资源,再到公网或者数据库取,然后缓存。
  同步代理:主要使用在多线程编程中,完成多线程间同步工作。