zoukankan      html  css  js  c++  java
  • 设计模式之代理模式

    代理模式有静态代理和动态代理

    静态代理:提到代理,我们首先想到的就是代理商了,代理商就是替厂家卖产品,这里有三个关键的地方,第一点,卖产品的行为是代理商做的(至少对于消费者来说,消费者接触的就是代理商);第二产品是厂家的(也就是说代理商卖的商品是厂家的,而代理商是没有产品的,从这点来说实质上还是厂家再卖产品)。

    回到我们的静态代理上,静态代理有三个角色,委托类(相当于厂家),代理类(相当于代理商),委托类需要实现的接口(产品,只是产品将厂家和代理商联系了起来,同样也是接口将委托类和代理类联系起来了)。了解了这些,静态代理地UML图为:

     1.RealSubject是委托类,Proxy是代理类

    2.Subject是委托类和代理类实现的接口

    3.request()是委托类和代理类的共同方法,相当于卖产品这种行为。

     具体代码如下:

     1 //接口
     2 interface Subject {
     3     void request();
     4 }
     5 
     6 //委托类
     7 class RealSubject implements Subject {
     8     
     9     public void request(){
    10         System.out.println("RealSubject");
    11     }
    12 }
    13 
    14 //代理类
    15 class Proxy implements Subject {
    16     private Subject subject;
    17    //代理的行为(方法) 
    18     public Proxy(Subject subject){
    19         this.subject = subject;
    20     }
    21     public void request(){
    22         System.out.println("begin");
    23         subject.request();
    24         System.out.println("end");
    25     }
    26 }
    27 
    28 //测试类
    29 public class ProxyTest {
    30     public static void main(String args[]) {
    31         RealSubject subject = new RealSubject();
    32         Proxy p = new Proxy(subject);
    33         p.request();
    34     }
    35 }    

    静态代理实现中,代理类在编译期间就已经确定(程序员直接写了出来)。

    动态代理:

    代理对象如何产生?

    (代理对象的)方法执行过程(如何进行代理的)?进去再出来机制,进去就是利用接口给类传参数来产生需要的代理类,出来就是通过接口,代理类执行自己写的方法。

    主要体现在Proxy类中产生代理类的静态方法public static Object getInstance(Class[] interfac,MethodHandler h)

    出来时,代理类中,在代理方法中通过MethodHandler变量h ,调用h.invoke(this,Method m);这时方法便执行实现了(自己写的)MethodHandler类的invoke(Object,Method)方法,在MethodHandler类中,我们实现插入的逻辑。

    动态代理中,代理类并不是在Java代码中实现,而是在运行时期生成,相比静态代理,动态代理可以代理任何类所实现的接口的任何方法,进行任意的处理。例如:可以代理所有的类而不用像静态代理那样为每个类生成一个代理类,而是动态生成代理类。修改代理的逻辑时(如将时间代理换成日志代理)而不同修改代理类的逻辑,也就是说更加灵活。动态代理更加灵活,代码重用性好,模块化,低内聚。

    接下来,用具体类分析:

    1.定义委托类实现的接口(接口中的方法也就是要代理的方法)和委托类

    1 package dynamic_proxy2;
    2 
    3 public interface Service {
    4     public void add();
    5 }
    1 package dynamic_proxy2;
    2 
    3 public class UserServiceImpl implements Service{
    4     public void add(){
    5         System.out.println("this is an add()");
    6     }
    7     
    8 }

    2.利用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口生成代理

     1 package dynamic_proxy2;
     2 
     3 import java.lang.reflect.InvocationHandler;
     4 import java.lang.reflect.Method;
     5 import java.lang.reflect.Proxy;
     6 
     7 public class MyInvocationHandler implements InvocationHandler{
     8     private Object target;//委托对象
     9     
    10     public MyInvocationHandler(Object target){
    11         this.target = target;
    12     }
    13     
    14     /**
    15      * 此方法由生成的代理类调用,并生成调用方法的方法对象,并传入到此方法,此后在此方法中由原生类进行反射调用
    16      */
    17     @Override
    18     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    19         System.out.println("add() begin");
    20         Object result = method.invoke(target,args);//反射
    21         System.out.println("add() end");
    22         return result;
    23     }
    24     
    25     public Object getProxy(){
    26         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    27         Class<?>[] interfaces = target.getClass().getInterfaces();
    28         
    29         /**
    30          * classLoader:用ContextClassLoader进行对代理类进行加载
    31          * interfaces:原生类实现的接口
    32          * this:实现了InvocationHandler接口的MyInvocationHandler(自己写的),
    33          * 这个对象对传入到代理类的InvocationHandler变量中
    34          */
    35         return Proxy.newProxyInstance(classLoader, interfaces, this);
    36     }
    37 
    38 }

    3.使用动态代理:

     1 package dynamic_proxy2;
     2 
     3 public class ProxyTest {
     4 
     5     public static void main(String[] args) {
     6         Service service = new UserServiceImpl();//原生类
     7         
     8         MyInvocationHandler handler = new MyInvocationHandler(service);
     9         Service serviceproxy = (Service) handler.getProxy();//获得代理对象
    10         
    11         /**
    12          * 生成add()对象 m,并调用InvocationHandler变量的invoke(this,m)
    13          */
    14         serviceproxy.add();
    15     }
    16 
    17 }

      以上的这些也就是程序员需要编写的,动态代理的核心Proxy类和InvocationHandler接口已经由JDK实现了。

    接下来看看运行结果

    1 add() begin
    2 this is an add()
    3 add() end

    然后我们借用这个例子再具体说说,动态代理的特点与好处:

    1.若我们想对任何的类进行代理,我们只要创建这个类即可,然后生成该类的对象作为参数传给实现了InvocationHandler接口的MyInvocationHandler类的构造器方法。

    2.若我们相对委托类进行任何的处理只需在MyInvocationHandler类中的invoke方法(此方法即是实现的InvocationHandler的方法)中写入处理代码。

    了解了这些后,我们就该谈谈代理类是如何产生的?以及代理类如何进行代理的?

    首先,我们来谈谈代理类的产生,代理类(对象)的生成过程是由Proxy类的newProxyInstance方法实现,分为三个步骤:

    1.ProxyGenerator.generateProxyClass方法负责生成代理类的字节码,在我的模拟JDK动态代理实现中,是生成代理类源码,这个代理类源码由委托类实现的接口,类加载器组装而成。

    2.native方法Proxy.defineClass0负责字节码的加载,并返回对应的Class对象。在模拟实验中,是利用Java提供的编译器,加载器,对代理类进行编译和加载。

    3.利用反射机制生成代理类的对象。在模拟实验中,通过反射机制,得到带有委托类实现接口参数的构造器对象,并利用这个对象生成代理类的对象。代码如:

    1 //ul为类加载器,方法里的参数为生成的代理类的源码
    2 Class c = ul.loadClass("com.bjsxt.proxy.TankTimeProxy");
    3 //Moveable为委托类,代理类实现的接口        
    4 Constructor ctr = c.getConstructor(Moveable.class);
    5 //得到代理类的对象
    6 Moveable m = (Moveable)ctr.newInstance(new Tank());

    通过反编译得到代理类的源码:

     1 public final class $proxy1 extends Proxy implements Service {
     2 
     3     public $proxy1(InvocationHandler invocationhandler) {
     4         super(invocationhandler);
     5     }
     6 
     7     public final boolean equals(Object obj) {
     8         try {
     9             return ((Boolean)super.h.invoke(this, m1, new Object[] {
    10                 obj
    11             })).booleanValue();
    12         }
    13         catch(Error _ex) { }
    14         catch(Throwable throwable) {
    15             throw new UndeclaredThrowableException(throwable);
    16         }
    17     }
    18 
    19     public final String toString() {
    20         try {
    21             return (String)super.h.invoke(this, m2, null);
    22         }
    23         catch(Error _ex) { }
    24         catch(Throwable throwable) {
    25             throw new UndeclaredThrowableException(throwable);
    26         }
    27     }
    28 
    29     public final void add() {
    30         try {
    31             super.h.invoke(this, m3, null);
    32             return;
    33         }
    34         catch(Error _ex) { }
    35         catch(Throwable throwable) {
    36             throw new UndeclaredThrowableException(throwable);
    37         }
    38     }
    39 
    40     public final int hashCode() {
    41         try {
    42             return ((Integer)super.h.invoke(this, m0, null)).intValue();
    43         }
    44         catch(Error _ex) { }
    45         catch(Throwable throwable) {
    46             throw new UndeclaredThrowableException(throwable);
    47         }
    48     }
    49 
    50     private static Method m1;
    51     private static Method m2;
    52     private static Method m3;
    53     private static Method m0;
    54 
    55     static {
    56         try {
    57             m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
    58                 Class.forName("java.lang.Object")
    59             });
    60             m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
    61             m3 = Class.forName("zzzzzz.Service").getMethod("add", new Class[0]);
    62             m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    63         }
    64         catch(NoSuchMethodException nosuchmethodexception) {
    65             throw new NoSuchMethodError(nosuchmethodexception.getMessage());
    66         }
    67         catch(ClassNotFoundException classnotfoundexception) {
    68             throw new NoClassDefFoundError(classnotfoundexception.getMessage());
    69         }
    70     }
    71 }
    72 

    根据我们前面的分析,生成的代理类中应该有InvocationHandler变量,实现了代理方法会调用子类(也就是我们实现InvocationHandler接口的类)的invoke方法。

    而从上面的代码中并没有发现有InvocationHandler变量,但是它(生成的代理类)继承自Proxy类,接下来看一下Proxy类

     1 public class Proxy implements java.io.Serializable {
     2 
     3     private static final long serialVersionUID = -2222568056686623797L;
     4 
     5     /** parameter types of a proxy class constructor */
     6     private static final Class<?>[] constructorParams =
     7         { InvocationHandler.class };
     8 
     9     /**
    10      * a cache of proxy classes
    11      */
    12     private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    13         proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    14 
    15     /**
    16      * the invocation handler for this proxy instance.
    17      * @serial
    18      */
    19     protected InvocationHandler h;

    在代码的最后,我们发现了InvocationHandler变量h。由此代理方法的整个调用过程就是,

    1,代理类调用代理方法,在代理方法内,得到该方法对象,

    2,调用InvocationHandler变量h的invoke方法,传入代理类,代理方法对象,代理方法参数,

     此时h引用的是我们实现InvocationHandler接口的子类,这步操作是在zileidegetProxy()方法里

     1 public Object getProxy(){
     2         ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
     3         Class<?>[] interfaces = target.getClass().getInterfaces();
     4         
     5         /**
     6          * classLoader:用ContextClassLoader进行对代理类进行加载
     7          * interfaces:原生类实现的接口
     8          * this:实现了InvocationHandler接口的MyInvocationHandler(自己写的),
     9          * 这个对象对传入到代理类的InvocationHandler变量中
    10          */
    11         return Proxy.newProxyInstance(classLoader, interfaces, this);
    12     }
    super.h.invoke(this, m3, null);

    JDK动态代理的局限性
    通过反射类Proxy和InvocationHandler接口实现的动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类

     便无法使用这种方式实现动态代理。而且就算实现了接口也只能代理实现了接口的方法,委托类原有的类仍然不能被代理。

    参考:https://www.jianshu.com/p/a1d094fc6c00

  • 相关阅读:
    Java第一次作业——Java语言基础
    P田日志<1>
    没解决的问题-git连接失败
    解决每次centos7执行java --version git --version等命令时都要重新source /etc/profile后才能执行,否则找不到命令-转载
    【Linux】VMware虚拟机中如何配置静态IP-转载
    Parallels Desktop 16 破解版 出现网络初始化失败和不能连接USB设备解决方法-转载
    Ubuntu安装git时出错,git-compat-util.h:280:25: fatal error: openssl/ssl.h: 没有那个文件或目录-转载
    遇到的问题29
    解决Idea 导入maven工程编译 Error running 'tms [clean]': Cannot run program "C:Program FilesJavajdk --转载
    jenkins执行shell命令时,提示“Command not found”处理方法-需要在jenkins调用shell脚本的最前面加一行脚本, #!/bin/bash -ilex-------转载
  • 原文地址:https://www.cnblogs.com/gulingjingguai/p/8146370.html
Copyright © 2011-2022 走看看