java静态代理和动态代理

一 代理模式

        使用一个代理对象将对象包装起来,然后用该代理对象来取代该对象,任何对原始对象的调用都要通过代理,代理对象决定是否以及何时调用原始对象的方法

二 静态模式

      要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。如下图:

Cilent调用Source的method()方法,实际上是Proxy来调用method()方法,proxy再调用source的method,source负责实现功能,proxy与用户接触,起到了中介的作用,静态代理中Source跟Proxy都要实现接口Sourceable。

三:静态代理

我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?

电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告。

现在用代码来进行模拟。

首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为 Movie,代表电影播放的能力。

1 package com.frank.test;
2 
3 public interface Movie {
4     void play();
5 }

 

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


复制代码
package com.frank.test;

public class RealMovie implements Movie {

    @Override
    public void play() {
        // TODO Auto-generated method stub
        System.out.println("您正在观看电影 《肖申克的救赎》");
    }

}
复制代码

 

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

复制代码
 1 package com.frank.test;
 2 
 3 public class Cinema implements Movie {
 4 
 5     RealMovie movie;
 6 
 7     public Cinema(RealMovie movie) {
 8         super();
 9         this.movie = movie;
10     }
11 
12 
13     @Override
14     public void play() {
15 
16         guanggao(true);
17 
18         movie.play();
19 
20         guanggao(false);
21     }
22 
23     public void guanggao(boolean isStart){
24         if ( isStart ) {
25             System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!");
26         } else {
27             System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!");
28         }
29     }
30 
31 }
复制代码

 

 

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

复制代码
package com.frank.test;

public class ProxyTest {

    public static void main(String[] args) {

        RealMovie realmovie = new RealMovie();

        Movie movie = new Cinema(realmovie);

        movie.play();

    }

}
复制代码

 

然后观察结果:

电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!
您正在观看电影 《肖申克的救赎》
电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!

 

 

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

上面介绍的是静态代理的内容,为什么叫做静态呢?因为它的类型是事先预定好的,比如上面代码中的 Cinema 这个类,如果新增加一个电影,那么还要为新电影增加新的代理,非常的麻烦。下面要介绍的内容就是动态代理。

四:动态代理

既然是代理,那么它与静态代理的功能与目的是没有区别的,唯一有区别的就是动态与静态的差别。

那么在动态代理的中这个动态体现在什么地方?

上一节代码中 Cinema 类是代理,我们需要手动编写代码让 Cinema 实现 Movie 接口,而在动态代理中,我们可以让程序在运行的时候自动在内存中创建一个实现 Movie 接口的代理,而不需要去定义 Cinema 这个类。这就是它被称为动态的原因。

也许概念比较抽象。现在实例说明一下情况。

假设有一个大商场,商场有很多的柜台,有一个柜台卖茅台酒。我们进行代码的模拟。

复制代码
package com.frank.test;

public interface SellWine {

     void mainJiu();

}
复制代码

 

 

 

SellWine 是一个接口,你可以理解它为卖酒的许可证。

复制代码
package com.frank.test;

public class MaotaiJiu implements SellWine {

    @Override
    public void mainJiu() {
        // TODO Auto-generated method stub
        System.out.println("我卖得是茅台酒。");

    }

}
复制代码

 

然后创建一个类 MaotaiJiu,对的,就是茅台酒的意思。

我们还需要一个柜台来卖酒:

复制代码
package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;


public class GuitaiA implements InvocationHandler {

    private Object pingpai;


    public GuitaiA(Object pingpai) {
        this.pingpai = pingpai;
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("销售开始  柜台是: "+this.getClass().getSimpleName());
        method.invoke(pingpai, args);
        System.out.println("销售结束");
        return null;
    }

}
复制代码

 

 

GuitaiA 实现了 InvocationHandler 这个类,这个类是什么意思呢?大家不要慌张,待会我会解释。

然后,我们就可以卖酒了。

复制代码
package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MaotaiJiu maotaijiu = new MaotaiJiu();


        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);


        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);

        dynamicProxy.mainJiu();

    }

}
复制代码

 

 

这里,我们又接触到了一个新的概念,没有关系,先别管,先看结果。

销售开始  柜台是: GuitaiA
我卖得是茅台酒。
销售结束

 

看到没有,我并没有像静态代理那样为 SellWine 接口实现一个代理类,但最终它仍然实现了相同的功能,这其中的差别,就是之前讨论的动态代理所谓“动态”的原因。

动态代理语法

放轻松,下面我们开始讲解语法,语法非常简单。

动态代码涉及了一个非常重要的类 Proxy。正是通过 Proxy 的静态方法 newProxyInstance 才会动态创建代理。

Proxy

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

 

 

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

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

初学者应该对于 InvocationHandler 很陌生,我马上就讲到这一块。

InvocationHandler

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

public interface InvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

 

 

 

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

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

因为,Proxy 动态产生的代理会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。

以下面的例子进行分析

public class ProxyInvocationHandler implements InvocationHandler {
   private Rent rent;

   public void setRent(Rent rent) {
       this.rent = rent;
  }

   //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
   public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),this);
  }

   // proxy : 代理类 method : 代理类的调用处理程序的方法对象.
   // 处理代理实例上的方法调用并返回结果
   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       seeHouse();
       //核心:本质利用反射实现!
       Object result = method.invoke(rent, args);
       fare();
       return result;
  }

   //看房
   public void seeHouse(){
       System.out.println("带房客看房");
  }
   //收中介费
   public void fare(){
       System.out.println("收中介费");
  }

}

   public static void main(String[] args) {
       //真实角色
       Host host = new Host();
       //代理实例的调用处理程序
       ProxyInvocationHandler pih = new ProxyInvocationHandler();
       pih.setRent(host); //将真实角色放置进去!
       Rent proxy = (Rent)pih.getProxy(); //动态生成对应的代理类!
       proxy.rent();
  }

 

 

 

 

 

 

 所以分析上面的程序,首先proxy是一个代理对象可以等价于反射得到的Class,因此可以newinstance获取一个代理实例,其次proxy有一个属性叫做InvocationHandler,这个属性是调用处理程序实现的接口,也就是代理实例调用方法的时候用到它,。先分析它

public Object getProxy(){
       return Proxy.newProxyInstance(this.getClass().getClassLoader(),
               rent.getClass().getInterfaces(),this);
  }
很明显就是代理类获取代理的实例,第一个参数定义了由哪个ClassLoader对象对生成的代理类进行加载,也就是ProxyInvocationHandler 这个类的classloader
来加载Proxy代理类(只有加载了才能用),第二个参数定义了被代理类的接口的Class对象,这样就可以知道接口类型,该代理也会实现该接口,即代理类就会交给InvocationHandler这个
处理实例来实现相应的接口,之后才能调用接口中的方法,第三个参数则是实现了这个调用处理程序的实例是说也就是当前类,当前类拥有invoke方法,这个方法完成了
被代理实例的方法的调用。也就是下面的程序   @Override
   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
       seeHouse();
       //核心:本质利用反射实现!
       Object result = method.invoke(rent, args);
       fare();
       return result;
  }
第一份参数就是获取的proxy实例,只有这个实例才可以调用这个方法,第二个参数就是在通过反射获取的代理proxy就可以获取其方法(此时也实现了被代理类的接口)
给Method对象,method.invoke就可以激活该方法,第一个参数

是当前被代理对象,第二个参数就是方法也传入的参数,再看main函数,声明的代理类便可以调用被代理实现的方法了。
public void test02(){//代理各种类
Host host = new Host();
ProxyInvocationHandler2 proxyInvocationHandler2 = new ProxyInvocationHandler2();
proxyInvocationHandler2.setTarget(host);
Rent proxy = (Rent)proxyInvocationHandler2.getProxy();
proxy.rent();//这个是代理类的rent方法,被封装在invoke里面
}
总结就是被代理类通过反射获知其实现的接口,代理类拿到之后也去实现。被代理类的方法也会在代理类里面通过反射获取,代理类的Class对象Proxy直接调用被代理类实现的
相同的方法(此时等价invoke方法)完成动态代理。(个人理解)
复制代码
public class GuitaiA implements InvocationHandler {

    private Object pingpai;


    public GuitaiA(Object pingpai) {
        this.pingpai = pingpai;
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO Auto-generated method stub
        System.out.println("销售开始  柜台是: "+this.getClass().getSimpleName());
        method.invoke(pingpai, args);
        System.out.println("销售结束");
        return null;
    }

}
复制代码

 

 

 

GuitaiA 就是实际上卖酒的地方。

现在,我们加大难度,我们不仅要卖茅台酒,还想卖五粮液

复制代码
package com.frank.test;

public class Wuliangye implements SellWine {

    @Override
    public void mainJiu() {
        // TODO Auto-generated method stub
        System.out.println("我卖得是五粮液。");

    }

}
复制代码

 

 

 

Wuliangye 这个类也实现了 SellWine 这个接口,说明它也拥有卖酒的许可证,同样把它放到 GuitaiA 上售卖。

复制代码
public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MaotaiJiu maotaijiu = new MaotaiJiu();

        Wuliangye wu = new Wuliangye();

        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
        InvocationHandler jingxiao2 = new GuitaiA(wu);

        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);
        SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao2);

        dynamicProxy.mainJiu();

        dynamicProxy1.mainJiu();

    }

}
复制代码

 

我们来看结果:

销售开始  柜台是: GuitaiA
我卖得是茅台酒。
销售结束
销售开始  柜台是: GuitaiA
我卖得是五粮液。
销售结束

 

 

 

有人会问,dynamicProxy 和 dynamicProxy1 什么区别没有?他们都是动态产生的代理,都是售货员,都拥有卖酒的技术证书。

我现在扩大商场的经营,除了卖酒之外,还要卖烟。

首先,同样要创建一个接口,作为卖烟的许可证。

package com.frank.test;

public interface SellCigarette {
    void sell();
}

 

 

然后,卖什么烟呢?我是湖南人,那就芙蓉王好了。

复制代码
public class Furongwang implements SellCigarette {

    @Override
    public void sell() {
        // TODO Auto-generated method stub
        System.out.println("售卖的是正宗的芙蓉王,可以扫描条形码查证。");
    }

}
复制代码

 

 

然后再次测试验证:

复制代码
package com.frank.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MaotaiJiu maotaijiu = new MaotaiJiu();

        Wuliangye wu = new Wuliangye();

        Furongwang fu = new Furongwang();

        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
        InvocationHandler jingxiao2 = new GuitaiA(wu);

        InvocationHandler jingxiao3 = new GuitaiA(fu);

        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);
        SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao2);

        dynamicProxy.mainJiu();

        dynamicProxy1.mainJiu();

        SellCigarette dynamicProxy3 = (SellCigarette) Proxy.newProxyInstance(Furongwang.class.getClassLoader(),
                Furongwang.class.getInterfaces(), jingxiao3);

        dynamicProxy3.sell();

    }

}
复制代码

 

然后,查看结果:

复制代码
销售开始  柜台是: GuitaiA
我卖得是茅台酒。
销售结束
销售开始  柜台是: GuitaiA
我卖得是五粮液。
销售结束
销售开始  柜台是: GuitaiA
售卖的是正宗的芙蓉王,可以扫描条形码查证。
销售结束
复制代码

 

 

结果符合预期。大家仔细观察一下代码,同样是通过 Proxy.newProxyInstance() 方法,却产生了 SellWine 和 SellCigarette 两种接口的实现类代理,这就是动态代理的魔力。

动态代理的秘密

一定有同学对于为什么 Proxy 能够动态产生不同接口类型的代理感兴趣,我的猜测是肯定通过传入进去的接口然后通过反射动态生成了一个接口实例。
比如 SellWine 是一个接口,那么 Proxy.newProxyInstance() 内部肯定会有

new SellWine();

 

 

这样相同作用的代码,不过它是通过反射机制创建的。那么事实是不是这样子呢?直接查看它们的源码好了

需要注意的是卖酒的是一个代理对象(茅台,五粮液),卖芙蓉王的是另一个代理对象

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        MaotaiJiu maotaijiu = new MaotaiJiu();

        Wuliangye wu = new Wuliangye();

        Furongwang fu = new Furongwang();

        InvocationHandler jingxiao1 = new GuitaiA(maotaijiu);
        InvocationHandler jingxiao2 = new GuitaiA(wu);

        InvocationHandler jingxiao3 = new GuitaiA(fu);

        SellWine dynamicProxy = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao1);
        SellWine dynamicProxy1 = (SellWine) Proxy.newProxyInstance(MaotaiJiu.class.getClassLoader(),
                MaotaiJiu.class.getInterfaces(), jingxiao2);

        dynamicProxy.mainJiu();

        dynamicProxy1.mainJiu();

        SellCigarette dynamicProxy3 = (SellCigarette) Proxy.newProxyInstance(Furongwang.class.getClassLoader(),
                Furongwang.class.getInterfaces(), jingxiao3);

        dynamicProxy3.sell();

        System.out.println("dynamicProxy class name:"+dynamicProxy.getClass().getName());
        System.out.println("dynamicProxy1 class name:"+dynamicProxy1.getClass().getName());
        System.out.println("dynamicProxy3 class name:"+dynamicProxy3.getClass().getName());

    }

}
销售开始  柜台是: GuitaiA
我卖得是茅台酒。
销售结束
销售开始  柜台是: GuitaiA
我卖得是五粮液。
销售结束
销售开始  柜台是: GuitaiA
售卖的是正宗的芙蓉王,可以扫描条形码查证。
销售结束

dynamicProxy class name:com.sun.proxy.$Proxy0
dynamicProxy1 class name:com.sun.proxy.$Proxy0
dynamicProxy3 class name:com.sun.proxy.$Proxy1

SellWine 接口的代理类名是:com.sun.proxy.$Proxy0
SellCigarette 接口的代理类名是:com.sun.proxy.$Proxy1

这说明动态生成的 proxy class 与 Proxy 这个类同一个包。

下面用一张图让大家记住动态代理涉及到的角色。
这里写图片描述
红框中 $Proxy0 就是通过 Proxy 动态生成的。
$Proxy0 实现了要代理的接口。
$Proxy0 通过调用 InvocationHandler 来执行任务。

五:cglib代理(子类代理)

对于未实现接口的类,可以通过在运行期间动态的在内存中构建一个子类对象,从而对目标对象进行扩展。

UserDao与上相同

代理工厂类:使用了spring框架中的cglib代码生成包,其可以在运行期扩展java类与实现java的接口,被Spring AOP所使用,为其提供方法的interception。

 

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
 
import java.lang.reflect.Method;
 
/**
 * Created by Administrator on 2017/11/16.
 */
public class ProxyObjectFactory implements MethodInterceptor {
 
    // 接收一个目标对象
    private Object target;
    public ProxyObjectFactory(Object target) {
        this.target = target;
    }
 
    /**
     * 返回目标对象的子类代理对象
     * @return
     */
    public Object getProxyInstance() {
        //字节码生成工具类
        Enhancer enhancer = new Enhancer();
        //设置父类
        enhancer.setSuperclass(target.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建子类对象
        return enhancer.create();
    }
 
    // 事件处理器,执行目标方法时候触发
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
 
        String methodName = method.getName();
 
        //方法返回值
        Object result = null;
 
        //invoke方法用来在运行时动态的调用某个实例的方法
 
        if ("save".equals(methodName)) {
            System.out.println("目标对象方法执行前操作");
            result = method.invoke(target, objects);
            System.out.println("目标对象方法执行前操作");
        } else {
            //直接调用目标对象方法
            result = method.invoke(target, objects);
        }
        return result;
    }
}

 

测试方法:

 

public class AppTest {
 
    @Test
    public void testCglibProxy() {
        //目标对象
        UserDao target = new UserDao();
        //目标对象的子类代理对象
        UserDao proxyInstance = (UserDao) new ProxyObjectFactory(target).getProxyInstance();
 
        proxyInstance.save();
        proxyInstance.find();
 
        System.out.println(target.getClass());
        System.out.println(proxyInstance.getClass());
    }
}

 

结果:

目标对象方法执行前操作
模拟:保存用户
目标对象方法执行前操作
模拟:查询用户
class cglibProxy.UserDao
class cglibProxy.UserDao$$EnhancerByCGLIB$$cf3e68e5

 

由于是子类代理,所以

 

1. 目标对象可以不实现接口

2. 目标类不可以为final

3. 目标对象的方法如果为static,不会被代理拦截,会直接执行

代理的作用

可能有同学会问,已经学习了代理的知识,但是,它们有什么用呢?

主要作用,还是在不修改被代理对象的源码上,进行功能的增强。

这在 AOP 面向切面编程领域经常见。

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

主要功能
日志记录,性能统计,安全控制,事务处理,异常处理等等。

上面的引用是百度百科对于 AOP 的解释,至于,如何通过代理来进行日志记录功能、性能统计等等,这个大家可以参考 AOP 的相关源码,然后仔细琢磨。

同注解一样,很多同学可能会有疑惑,我什么时候用代理呢?

这取决于你自己想干什么。你已经学会了语法了,其他的看业务需求。对于实现日志记录功能的框架来说,正合适。

至此,静态代理和动态代理者讲完了。

总结

    1. 代理分为静态代理和动态代理两种。
    2. 静态代理,代理类需要自己编写代码写成。
    3. 动态代理,代理类通过 Proxy.newInstance() 方法生成。
    4. 不管是静态代理还是动态代理,代理与被代理者都要实现两样接口,它们的实质是面向接口编程。
    5. 静态代理和动态代理的区别是在于要不要开发者自己定义 Proxy 类。
    6. 动态代理通过 Proxy 动态生成 proxy class,但是它也指定了一个 InvocationHandler 的实现类。
    7. 代理模式本质上的目的是为了增强现有代码的功能。
posted @ 2020-11-09 18:24  你的雷哥  阅读(350)  评论(0编辑  收藏  举报