zoukankan      html  css  js  c++  java
  • 代理模式(Proxy Pattern)

    参考文档:
    普通代理:http://yangguangfu.iteye.com/blog/815787
    动态代理:http://www.cnblogs.com/MOBIN/p/5597215.html
    远程代理&虚拟代理&缓冲代理:http://blog.csdn.net/will130/article/details/50729535
    动态代理机制详解(JDK CGLIB,Javassist,ASM):http://blog.csdn.net/luanlouis/article/details/24589193
    定义:
    为其他对象提供一种代理以控制对这个对象的访问。
    类型:
    结构型
    代理分类:
    静态代理(静态定义代理类,我们自己静态定义的代理类。比如我们自己定义一个明星的经纪人类)
    动态代理(通过程序动态生成代理类,该代理类不是我们自己定义的。而是由程序自动生成)
    JDK自带的动态代理
    CGLIB
    静态代理uml类图:
    静态代理模式组成:
    Subject:抽象角色。指代理角色(经纪人)和真实角色(明星)对外提供的公共方法,一般为一个接口
    RealSubject:真实角色。需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此
    Proxy:代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作将统一的流程控制都放到代理角色中处理!
    常用的代理分类:
    远程代理(Remote Proxy)
    它使得客户端程序可以访问在远程主机上的对象,远程主机可能具有更好的计算性能与处理速度,可以快速响应并处理客户端的请求。远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存。客户端完全可以认为被代理的远程业务对象是在本地而不是在远程,而远程代理对象承担了大部分的网络通信工作,并负责对远程业务方法的调用。
    应用:rpc框架,webservice等
    虚拟代理(Virtual Proxy
    是一种节省内存的技术,它建议创建那些占用大量内存或处理复杂的对象时,把创建这类对象推迟到使用它的时候。在特定的应用中,不同部分的功能由不同的对象组成,应用启动的时候,不会立即使用所有的对象。在这种情况下,虚拟代理模式建议推迟对象的创建直到应用程序需要它为止。对象被应用第一次引用时创建并且同一个实例可以被重用。这种方法优缺点并存。
    优点:这种方法的优点是,在应用程序启动时,由于不需要创建和装载所有的对象,因此加速了应用程序的启动
    缺点因为不能保证特定的应用程序对象被创建,在访问这个对象的任何地方,都需要检测确认它不是空(null)。也就是,这种检测的时间消耗是最大的缺点
    缓存代理(Cache Proxy)
    它为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,从而可以避免某些方法的重复执行,优化系统性能。
    安全代理
    用来控制真实对象访问时的权限
    智能指引
    即当调用真实对象时,代理处理另外一些事

    装饰模式vs代理模式

    装饰器模式关注于在一个对象上动态的添加方法,然而代理模式关注于控制对对象的访问。换句话 说,用代理模式,代理类(proxy class)可以对它的客户隐藏一个对象的具体信息。因此,当使用代理模式的时候,我们常常在一个代理类中创建一个对象的实例。并且,当我们使用装饰器模式的时候,我们通常的做法是将原始对象作为一个参数传给装饰者的构造器

    静态代理举个栗子
    定义抽象目标类
    interface IMusicBox {
        public void sing();
    }
    View Code

    定义具体目标类

    class MusicBox implements IMusicBox {
        public void sing() {
            System.out.println("im singing");
        }
    }
    View Code

    定义代理

    public class StaticMusicBoxProxy implements IMusicBox {
        // 以真实角色作为代理角色的属性
        private IMusicBox box;
    
        public StaticMusicBoxProxy(IMusicBox box) {
            this.box = box;
        }
    
        public void sing() {
            before();
            box.sing();
            after();
        }
    
        public void before() {
            System.out.println("before sing do something");
        }
    
        public void after() {
            System.out.println("after sing do something");
        }
    }
    View Code

    客户端调用

    MusicBox box = new MusicBox();
    IMusicBox bo = (IMusicBox) new StaticMusicBoxProxy(box);
    bo.sing();
    View Code

    输出

    动态代理

    一般来说,对代理模式而言,一个主题类与一个代理类一一对应,这也是静态代理模式的特点。但是,也存在这样的情况,有n各主题类,但是代理类中的“前处理、后处理”都是一样的,仅调用主题不同。也就是说,多个主题类对应一个代理类,共享“前处理,后处理”功能,动态调用所需主题,大大减小了程序规模,这就是动态代理模式的特点

    JDK自带的动态代理(只能针对实现了接口的类生成代理)

    获取RealSubject上的所有接口列表
    确定要生成的代理类的类名,默认为:com.sun.proxy.$ProxyXX
    根据需要实现的接口信息,在代码中动态创建该Proxy类的字节码
    将对应的字节码转换为对于的class对象
    创建InvocationHandler实例handler,用来处理Proxy所有方法的调用
    Proxy的class对象以创建的handler对象为参数,实例化一个proxy对象

    举个栗子
    定义抽象目标
     public interface IHello{
            void sayHello();
            void sayBye();
        }
    View Code

    定义目标对象

      static class Hello implements IHello{
            public void sayHello() {
                System.out.println("Hello world!!");
            }
            @Override
            public void sayBye() {
                System.out.println("Hello bye!!");
            }
        }
    View Code

    定义InvocationHandler

    class MyInvocationHandler implements InvocationHandler{
            //目标对象
            private Object target;
            public MyInvocationHandler(Object target){
                this.target = target;
            }
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                
                System.out.println("------插入前置通知代码-------------");
                //执行相应的目标方法
                Object rs = method.invoke(target,args);
                System.out.println("------插入后置处理代码-------------");
                
                return rs;
            }
        }
    View Code

    调用

     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            MyInvocationHandler handler=new MyInvocationHandler(new Hello());
            IHello iHello = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), new Class<?>[]{IHello.class},handler);
            iHello.sayHello();
        }
    View Code

    输出

    动态代理实现原理

    上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码,发现它只是封装了创建动态代理类的步骤(红色标准部分):

    public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * Look up or generate the designated proxy class.
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    View Code

    其实,我们最应该关注的是 Class<?> cl = getProxyClass0(loader, intfs);这句,这里产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,我这里不具体进入分析如何产生的这个类文件,只需要知道这个类文件时缓存在java虚拟机中的,我们可以通过下面的方法将其打印到文件里面,一睹真容:

     public static void printProxyClass(){
             byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", IHello.class.getInterfaces());
             String path = "E:/proxy.class";
             try(
                 FileOutputStream fos = new FileOutputStream(path)) {
                 fos.write(classFile);
                 fos.flush();
             } catch (Exception e) {
             }
        }
    View Code

    jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件临时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的该类实例。通过对这个生成的代理类源码的查看,我们很容易能看出,实际调用的是MyInvocationHandler的invoker方法。
    我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能

    CGLIB代理(针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明称final的)
    引入依赖cglib-nodep-2.2.jar
    查找A上的所有非final的public类型的方法定义
    将这些方法的定义转换成字节码
    将组成的字节码转换成相应的代理的class对象
    实现MethodInterceptor接口,用来处理对代理类上所有方法的请求(这个接口和Jdk动态代理InvocationHandler的功能和角色是一样的)
    举个栗子
    定义目标类
    class Hello {
            public void sayHello() {
                System.out.println("Hello world!!");
            }
    
            public void sayBye() {
                System.out.println("Hello bye!!");
            }
        }
    View Code

    定义通用proxy

    public class CglibProxy implements MethodInterceptor {
        private Enhancer enhance = new Enhancer();
    
        public Object getProxy(Class<?> clazz) {
            enhance.setSuperclass(clazz);
            enhance.setCallback(this);
            return enhance.create();
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("before do something");
            proxy.invoke(obj, args);
            System.out.println("after do something");
            return null;
        }
    
    }
    View Code

    调用

    public static void main(String[] args) {
            CglibProxy proxy = new CglibProxy();
            Hello h = (Hello) proxy.getProxy(Hello.class);
            h.sayBye();
        }
    View Code
  • 相关阅读:
    maven 常用编译
    java 秒时间格式化
    git clone 带用户名密码
    Filebeat占用内存和CPU过高问题排查
    新的一周,全新的开始.
    vs2008 打开aspx文件时设计界面死机情况的解决
    php面试题及答案
    JQuery 对html控件操作总结
    网页常用Javascript
    针对修改php_ini不起作用的方案
  • 原文地址:https://www.cnblogs.com/amei0/p/8073546.html
Copyright © 2011-2022 走看看