代理模式(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
原文地址:https://www.cnblogs.com/amei0/p/8073546.html