一、代理模式的概念

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
有三个角色:调用者,代理对象,真实对象
image.png
image.png
按理说,顾客可以直接从厂家购买产品,但是现实生活中,很少有这样的销售模式。一般都是厂家委托给代理商进行销售,顾客跟代理商打交道,而不直接与产品实际生产者进行关联。
所以,代理就有一种中间人的味道。

二、静态代理

我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?
电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。
现在用代码来进行模拟。
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

  1. public interface Movie {
  2. void play();
  3. }

然后,我们要有一个真正的实现这个 Movie 接口的类,和一个只是实现接口的代理类。

  1. public class RealMovie implements Movie {
  2. @Override
  3. public void play() {
  4. System.out.println("您正在观看电影 《肖申克的救赎》");
  5. }
  6. }

这个表示真正的影片。它实现了 Movie 接口,play() 方法调用时,影片就开始播放。那么 Proxy 代理呢?

  1. //Cinema是代理对象
  2. public class Cinema implements Movie {
  3. RealMovie movie;//目标对象、被代理对象
  4. public Cinema(RealMovie movie) {
  5. super();
  6. this.movie = movie;
  7. }
  8. @Override
  9. public void play() {
  10. guanggao();
  11. movie.play();
  12. }
  13. public void guanggao(){
  14. System.out.println("电影马上开始了,先放广告!");
  15. }
  16. }

Cinema 就是 Proxy 代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。现在,我们编写测试代码。

  1. public class ProxyTest {
  2. public static void main(String[] args) {
  3. RealMovie realmovie = new RealMovie();
  4. Movie movie = new Cinema(realmovie);
  5. movie.play();
  6. }
  7. }

画图讲解:
image.png
现在可以看到,代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。

上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类。下面要介绍的内容就是动态代理。

三、JDK动态代理

既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。
那么在动态代理的中这个动态体现在什么地方?
上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。也许概念比较抽象。现在实例说明一下情况。
新建一个类Handler实现InvocationHandler接口

  1. public class Handler implements InvocationHandler {
  2. private Movie realMovie;
  3. public Handler(Movie movie) {
  4. super();
  5. this.realMovie = movie;
  6. }
  7. @Override
  8. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  9. System.out.println("放电影之前先放一些广告");//这里是代理对象附加的功能
  10. method.invoke(realMovie,args);//调用目标对象的对应方法,这才是真正要做的事情
  11. System.out.println("放映结束了,再搞点促销");//这里是代理对象附加的功能
  12. return null;
  13. }
  14. }

通过JDK的动态代理功能创建一个代理类Proxy.newProxyInstance,这个类和目标类实现了相同的接口

  1. public class Test {
  2. public static void main(String[] args) {
  3. //设置系统属性,生成的java代理类源码会保存在工程目录中
  4. System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
  5. //1.先创建一个Movie的对象
  6. RealMovie realMovie = new RealMovie();
  7. //2.创建一个Handler对象
  8. InvocationHandler handler = new Handler(realMovie);
  9. //3.创建一个代理对象
  10. //第一个参数Test.class.getClassLoader()是类加载器,可以使用当前类的类加载器
  11. //第二个参数RealMovie.class.getInterfaces()是目标对象实现的接口列表
  12. //第三个参数handler就是我们自己实现的invocationHandler
  13. Movie proxyInstance = (Movie)Proxy.newProxyInstance(Test.class.getClassLoader(), RealMovie.class.getInterfaces(), handler);
  14. //4.调用代理对象的方法
  15. proxyInstance.play();
  16. }
  17. }

动态代理语法:

  1. public static Object newProxyInstance(ClassLoader loader,
  2. Class<?>[] interfaces,
  3. InvocationHandler handler)

下面讲解它的 3 个参数意义。

  • loader 是类加载器
  • interfaces 代码要用来代理的接口
  • handler 一个 InvocationHandler 对象

InvocationHandler 是一个接口,官方文档解释说,每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。

  1. public interface InvocationHandler {
  2. public Object invoke(Object proxy, Method method, Object[] args)
  3. throws Throwable;
  4. }

InvocationHandler 内部只是一个 invoke() 方法,正是这个方法决定了怎么样处理代理传递过来的方法调用。

  • proxy 代理对象
  • method 代理对象调用的方法
  • args 调用的方法中的参数

因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
注意:要想使用JDK的方法生成动态代理,目标类必须实现某一个接口。

生成JDK动态的步骤:
1、创建被代理对象(一定要实现接口)
2、创建一个InvocationHandler的对象,实现invoke的方法,在invoke方法里面,通过反射调用被代理的对象的方法,也可以在目标方法的前后增加自己的逻辑
3、调用 Proxy.newProxyInstance(JdkProxyTest.class.getClassLoader(), new Class[]{Movie.class}, handler);生成代理对象

  1. //第一个参数是类加载器,可以使用当前类的类加载器
  2. //第二个参数是目标对象实现的接口列表
  3. //第三个参数就是我们自己实现的invocationHandler

4、调用代理对象的方法

四、CGLIB动态代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑
注意:Cglib不需要目标类实现任何接口就可以生成目标类的代理类
在工程中引入两个jar文件asm-7.1.jar和cglib-3.3.0.jar
创建一个类,这是一个需要被代理的类,也就是父类,通过字节码技术创建这个类的子类,实现动态代理。

public class SayHello {
 public void say(){
  System.out.println("hello everyone");
 }
}

创建一个类CglibProxy

public class CglibProxy implements MethodInterceptor{
    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz){
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }
    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        System.out.println("在这里可以增加需要在目标方法之前执行的逻辑");
        //通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("在这里可以增加需要在目标方法之后执行的逻辑");
        return result;
    }
}

测试一下

public class TestCglib {

    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        //通过生成子类的方式创建代理类
        SayHello proxyImp = (SayHello)proxy.getProxy(SayHello.class);
        proxyImp.say();
    }
}