zoukankan      html  css  js  c++  java
  • 动态代理

    一、静态代理

    代理模式上,基本上有Subject角色,RealSubject角色,Proxy角色。其中:Subject角色负责定义RealSubject和Proxy角色应该实现的接口;RealSubject角色用来真正完成业务服务功能;Proxy角色负责将自身的Request请求,调用realsubject 对应的request功能来实现业务功能,自己不真正做业务。

    上面的这幅代理结构图是典型的静态的代理模式:
      当在代码阶段规定这种代理关系,Proxy类通过编译器编译成class文件,当系统运行时,此class已经存在了。这种静态的代理模式固然在访问无法访问的资源,增强现有的接口业务功能方面有很大的优点,但是大量使用这种静态代理,会使我们系统内的类的规模增大,并且不易维护;并且由于Proxy和RealSubject的功能 本质上是相同的,Proxy只是起到了中介的作用,这种代理在系统中的存在,导致系统结构比较臃肿和松散。
      为了解决这个问题,就有了动态地创建Proxy的想法:在运行状态中,需要代理的地方,根据Subject 和RealSubject,动态地创建一个Proxy,用完之后,就会销毁,这样就可以避免了Proxy 角色的class在系统中冗杂的问题了。
      下面以一个代理模式实例阐述这一问题:
      将车站的售票服务抽象出一个接口TicketService,包含问询,卖票,退票功能,车站类Station实现了TicketService接口,车票代售点StationProxy则实现了代理角色的功能,类图如下所示。

     售票服务接口:

    package com.demo.proxy;
    
    public interface TicketService {
        public void sellTicket();
    
        public void inquire();
    
        public void withdraw();
    }
    

     售票服务接口实现类,车站 :

    package com.demo.proxy;
    
    public class Station implements TicketService {
        @Override
        public void sellTicket() {
            System.out.println("
    	售票.....
    ");
        }
        @Override
        public void inquire() {
            System.out.println("
    	问询。。。。
    ");
        }
        @Override
        public void withdraw() {
            System.out.println("
    	退票......
    ");
        }
    }
    

     车票代售点 :

    package com.demo.proxy;
    
    public class StationProxy implements TicketService {
        private Station station;
    
        public StationProxy(Station station) {
            this.station = station;
        }
    
        public void sellTicket() {
            // 1.做真正业务前,提示信息
            this.showAlertInfo("××××您正在使用车票代售点进行购票,每张票将会收取5元手续费!××××");
            // 2.调用真实业务逻辑
            station.sellTicket();
            // 3.后处理
            this.takeHandlingFee();
            this.showAlertInfo("××××欢迎您的光临,再见!××××
    ");
        }
    
        public void inquire() {
            // 1做真正业务前,提示信息
            this.showAlertInfo("××××欢迎光临本代售点,问询服务不会收取任何费用,本问询信息仅供参考,具体信息以车站真实数据为准!××××");
            // 2.调用真实逻辑
            station.inquire();
            // 3。后处理
            this.showAlertInfo("××××欢迎您的光临,再见!××××
    ");
        }
    
        public void withdraw() {
            // 1。真正业务前处理
            this.showAlertInfo("××××欢迎光临本代售点,退票除了扣除票额的20%外,本代理处额外加收2元手续费!××××");
            // 2.调用真正业务逻辑
            station.withdraw();
            // 3.后处理
        }
    
        /*
         * 展示额外信息
         */
        private void showAlertInfo(String info) {
            System.out.println(info);
        }
    
        /*
         * 收取手续费
         */
        private void takeHandlingFee() {
            System.out.println("收取手续费,打印发票。。。。。
    ");
        }
    }
    

     InvocationHandler角色的由来

      仔细思考代理模式中的代理Proxy角色。Proxy角色在执行代理业务的时候,无非是在调用真正业务之前或者之后做一些“额外”业务。

     

    有上图可以看出,代理类处理的逻辑很简单:在调用某个方法前及方法后做一些额外的业务。换一种思路就是:在触发(invoke)真实角色的方法之前或者之后做一些额外的业务。那么,为了构造出具有通用性和简单性的代理类,可以将所有的触发真实角色动作交给一个触发的管理器,让这个管理器统一地管理触发。这种管理器就是Invocation Handler。
      动态代理模式的结构跟上面的静态代理模式稍微有所不同,多引入了一个InvocationHandler角色。
      先解释一下InvocationHandler的作用:
      在静态代理中,代理Proxy中的方法,都指定了调用了特定的realSubject中的对应的方法,在上面的静态代理模式下,Proxy所做的事情,无非是调用在不同的request时,调用触发realSubject对应的方法;更抽象点看,Proxy所作的事情;在Java中 方法(Method)也是作为一个对象来看待了,动态代理工作的基本模式就是将自己的方法功能的实现交给 InvocationHandler角色,外界对Proxy角色中的每一个方法的调用,Proxy角色都会交给InvocationHandler来处理,而InvocationHandler则调用具体对象角色的方法。如下图所示:

    在这种模式之中:代理Proxy 和RealSubject 应该实现相同的功能,这一点相当重要。(我这里说的功能,可以理解为某个类的public方法)
      在面向对象的编程之中,如果我们想要约定Proxy 和RealSubject可以实现相同的功能,有两种方式:
      a.一个比较直观的方式,就是定义一个功能接口,然后让Proxy 和RealSubject来实现这个接口。
      b.还有比较隐晦的方式,就是通过继承。因为如果Proxy 继承自RealSubject,这样Proxy则拥有了RealSubject的功能,Proxy还可以通过重写RealSubject中的方法,来实现多态。
    其中JDK中提供的创建动态代理的机制,是以a 这种思路设计的,而cglib 则是以b思路设计的。

    二、JDK的动态代理创建机制----通过接口

      比如现在想为RealSubject这个类创建一个动态代理对象,JDK主要会做以下工作:
      1.获取RealSubject上的所有接口列表;
      2.确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXXXX ;
      3.根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码;
      4.将对应的字节码转换为对应的class 对象;
      5.创建InvocationHandler实例handler,用来处理Proxy所有方法调用;
      6.Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象。

      JDK通过java.lang.reflect.Proxy包来支持动态代理,一般情况下,使用下面的newProxyInstance方法。 

    而对于InvocationHandler,需要实现下列的invoke方法:
    在调用代理对象中的每一个方法时,在代码内部,都是直接调用了InvocationHandler 的invoke方法,而invoke方法根据代理类传递给自己的method参数来区分是什么方法。

    JDK动态代理示例
      现在定义两个接口Vehicle和Rechargable,Vehicle表示交通工具类,有drive()方法;Rechargable接口表示可充电的(工具),有recharge() 方法;定义一个实现两个接口的类ElectricCar,类图如下:

    交通工具接口:

    package com.demo.proxy;
    
    public interface Vehicle {
        public void drive();
    }

    可充电设备接口 :

    package com.demo.proxy;
    
    public interface Rechargable {
        public void recharge();
    }

    电能车类,实现Rechargable,Vehicle接口:

    package com.demo.proxy;
    
    public class ElectricCar implements Rechargable, Vehicle {
        @Override
        public void recharge() {
            System.out.println("Electric Car is Moving silently...");
        }
    
        @Override
        public void drive() {
            System.out.println("Electric Car is Recharging...");
        }
    }
    

     InvocationHandler实现类:

    package com.demo.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class InvocationHandlerImpl implements InvocationHandler {
    
        private ElectricCar car;
    
        public InvocationHandlerImpl(ElectricCar car) {
            this.car = car;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("You are going to invoke " + method.getName() + " ...");
            method.invoke(car, null);
            System.out.println(method.getName() + " invocation has been finished...");
            return null;
        }
    }
    

     测试类:

    package com.demo.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;
    
    public class Test {
        public static void main(String[] args) {
            ElectricCar car = new ElectricCar();
            ClassLoader classLoader = car.getClass().getClassLoader();
            Class[] interfaces = car.getClass().getInterfaces();
            InvocationHandler handler = new InvocationHandlerImpl(car);
            Object o = Proxy.newProxyInstance(classLoader, interfaces, handler);
            Vehicle vehicle = (Vehicle) o;
            vehicle.drive();
            Rechargable rechargable = (Rechargable) o;
            rechargable.recharge();
        }
    }
    

     代码执行结果:

    You are going to invoke drive ...
    Electric Car is Recharging...
    drive invocation has been finished...
    You are going to invoke recharge ...
    Electric Car is Moving silently...
    recharge invocation has been finished...
    

     动态代理类有以下特点:

      1.继承自 java.lang.reflect.Proxy,实现了 Rechargable,Vehicle 这两个ElectricCar实现的接口;
      2.类中的所有方法都是final 的;
      3.所有的方法功能的实现都统一调用了InvocationHandler的invoke()方法。

    三、cglib 生成动态代理类的机制----通过类继承

      JDK中提供的生成动态代理类的机制有个鲜明的特点是: 某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法,比如:如果上面例子的ElectricCar实现了继承自两个接口的方法外,另外实现了方法bee() ,则在产生的动态代理类中不会有这个方法了!更极端的情况是:如果某个类没有实现接口,那么这个类就不能用JDK产生动态代理了!
      幸好我们有cglib,“CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。”
      cglib 创建某个类A的动态代理类的模式是:
      1.查找A上的所有非final 的public类型的方法定义;
      2.将这些方法的定义转换成字节码;
      3.将组成的字节码转换成相应的代理的class对象;
      4.实现 MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色是一样的)

    CGLIB原理:动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
    CGLIB底层:使用字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。
    CGLIB缺点:对于final方法,无法进行代理。

    程序猿类:

    package com.demo.proxy;
    
    public class Programmer {
        public void code() {
            System.out.println("I'm a Programmer,Just Coding.....");
        }
    }

    实现了方法拦截器接口:

    package com.demo.proxy;
    
    public class Hacker implements MethodInterceptor {
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    
            System.out.println("**** I am a hacker,Let's see what the poor programmer is doing Now...");
            proxy.invokeSuper(obj, args);
            System.out.println("****  Oh,what a poor programmer.....");
            return null;
        }
    }

    测试类:

    package com.demo.proxy;
    
    public class TestCGLIB {
        public static void main(String[] args) {
    
            Programmer progammer = new Programmer();
    
            Hacker hacker = new Hacker();
            //cglib 中加强器,用来创建动态代理
            Enhancer enhancer = new Enhancer();
            //设置要创建动态代理的类
            enhancer.setSuperclass(progammer.getClass());
            // 设置回调,这里相当于是对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实行intercept()方法进行拦截
            enhancer.setCallback(hacker);
            Programmer proxy = (Programmer) enhancer.create();
            proxy.code();
        }
    }
    

     运行结果:

    **** I am a hacker,Let's see what the poor programmer is doing Now...
    I'm a Programmer,Just Coding.....
    ****  Oh,what a poor programmer.....
    

     参考:https://www.cnblogs.com/flyingeagle/articles/7102282.html

  • 相关阅读:
    KINDLE 小说下载--超级书库
    修改PR Cs6,PS Cs6,AU Cs6的启动界面
    SQLMAP用户手册
    Burp Suite 实战指南--说明书
    XSS跨站测试代码
    万能密码字典
    python数据结构之队列(一)
    python数据结构之栈
    python实现链表(二)
    python实现链表(一)
  • 原文地址:https://www.cnblogs.com/xidian2014/p/10282666.html
Copyright © 2011-2022 走看看