java代理模式

来源:互联网 发布:被墙域名查询 编辑:程序博客网 时间:2024/06/17 17:41

代理模式

代理模式(Proxy Pattern)的定义:为其他对象提供一种代理以控制对这个对象的访问。
代理模式使用代理对象完成用户请求,屏蔽用户对真实对象的访问。现实世界的代理人被授权执行当事人的一些事宜,无需当事人出面,从第三方的角度看,似乎当事人并不存在,因为他只和代理人通信。而事实上代理人是要有当事人的授权,并且在核心问题上还需要请示当事人。


生活中,比较常见的代理场景如:火车票代售点代卖火车票了。
这里写图片描述

在软件设计中,Spring的AOP就是使用代理模式的原理来实现的。


在代理模式中主要有四种角色:

  1. 客户端(client):使用代理对象和真实对象完成一些工作的调用方。
  2. 抽象对象(interface):代理对象和真实对象的公共对外方法(如订火车票)
  3. 代理对象(proxy):用来代理、封装代理对象,代理对象内包含真实对象的引用,从而能够操作真实对象。
  4. 真实对象(target):真正实现业务逻辑的对象;
    这里写图片描述

业务场景

假设有这样一个业务场景:动物园中有许多小动物,小动物每天都要吃饭,现在需要记录动物园中所有动物的吃饭时间。
抽象对象(Animal),真实对象(Cat,SingleDog),代理对象(xxProxy),接口(吃饭:eat());

public interface Animal {    /**     * 吃饭方法     */    void eat();}public class Cat implements Animal {    @Override    public void eat() {        System.out.println("吃猫粮");    }}public class SingleDog implements  Animal{    @Override    public void eat() {        System.out.println("撒狗粮");    }}

java实现代理的方式有许多,接下来我们着重学习下

  • 静态代理:继承方式 + 聚合方式
  • 动态代理:jdk动态代理 + cglib动态代理

静态代理

静态代理是指,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。

1. 继承方式

public class CatTimeProxy extends Cat {    @Override    public void eat() {        long startTime = System.currentTimeMillis();        System.out.println("start eat-----------------");        super.eat();        long endTime = System.currentTimeMillis();        System.out.println("end eat--------------------"+ (endTime - startTime) +"ms");    }}public class SingleDogTimeProxy extends SingleDog {    @Override    public void eat() {        long startTime = System.currentTimeMillis();        System.out.println("start eat-----------------");        super.eat();        long endTime = System.currentTimeMillis();        System.out.println("end eat--------------------"+ (endTime - startTime) +"ms");    }}

测试类

    //继承方式    public void testCatTimeProxy(){        //1.记录cat的吃饭时间        Animal animal = new CatTimeProxy();        animal.eat();        //2.记录SingleDog的吃饭时间        animal = new SingleDogTimeProxy();        animal.eat();    }

2. 聚合方式

public class AnimalTimeProxy implements Animal {    private Animal target;    public AnimalTimeProxy(Animal animal) {        this.target = animal;    }    @Override    public void eat() {        long startTime = System.currentTimeMillis();        System.out.println("start eat-----------------");        target.eat();        long endTime = System.currentTimeMillis();        System.out.println("end eat--------------------" + (endTime - startTime) + "ms");    }}

测试类:

    //聚合的方式(较灵活,因为实现了接口)    public void tesAnimalTimeProxy(){        //1.记录cat的吃饭时间        Animal animal = new AnimalTimeProxy(new Cat());        animal.eat();        //2.记录SingleDog的吃饭时间        animal = new AnimalTimeProxy(new SingleDog());        animal.eat();    }

3. 继承与聚合的比较
聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。继承式的实现方式则不够灵活。
举个极端的例子:动物园中有1000中动物, 现在要记录这1000种动物的吃饭时间,如果采用继承,则要为这1000种动物分别创建一个代理类…..


4. 静态代理的劣势

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。(ps:jdk8中,支持接口有default方法,可避免此问题)

动态代理

动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。
动态代理类使用字节码动态生成加载技术,在运行时生成加载类。生成动态代理类的方法很多,如,JDK 自带的动态处理、CGLIB、Javassist 或者 ASM 库。JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱。CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。ASM 是低级的字节码生成工具,使用 ASM 已经近乎于在使用 Java bytecode 编程,对开发人员要求最高,当然,也是性能最好的一种动态代理生成工具。但 ASM 的使用很繁琐,而且性能也没有数量级的提升,与 CGLIB 等高级字节码生成工具相比,ASM 程序的维护性较差,如果不是在对性能有苛刻要求的场合,还是推荐 CGLIB 或者 Javassist。
这里着重介绍下jdk自带的动态代理,和CGLIB的动态代理


jdk动态代理

1.实现步骤
实现jdk动态代理,通常有两种常用的方法,但是最终的步骤都如下所示:
a. 创建一个实现InvocationHandler接口的类,它必须实现invoke()方法
b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象

2.代码示例
首先定义InvocationHandler实现类

public class JvmTimeHandler implements InvocationHandler {    private Object target;    public JvmTimeHandler(Object target) {        super();        this.target = target;    }    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        long startTime = System.currentTimeMillis();        System.out.println("start eat-----------------");        //!!注意proxy为当前调用类即JvmTimeHandler, method.invoke(xxx,zzz)第一个参数不能为proxy,会出现死循环        Object returnVal = method.invoke(target, args);        long endTime = System.currentTimeMillis();        System.out.println("end eat--------------------" + (endTime - startTime) + "ms");        return returnVal;    }}

方法(一)

    public void testTimeHandler() {        Animal target = new SingleDog();        Class clazz = target.getClass();        //通过 classloader,代理类的inteface[] ,以及调用处理器对象 直接生成代理对象        Animal proxy = (Animal) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new JvmTimeHandler(target));        proxy.eat();    }

方法(二)

public void testTimeHandler2() throws Exception {        //a.建动态代理类        Class proxyClass = Proxy.getProxyClass(ProxyTest.class.getClassLoader(), Animal.class);        //b.动态代理类的构造函数        Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);        //c.构造调用处理器对象        Animal target = new SingleDog();        InvocationHandler handler = new JvmTimeHandler(target);        //d.通过构造函数创建动态代理类对象        Animal proxy = (Animal) constructor.newInstance(handler);        proxy.eat();    }

3.源码解析
1)java.lang.reflect.InvocationHandler
这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

2)java.lang.reflect.Proxy
这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

//构造函数  protected Proxy(InvocationHandler h) {        Objects.requireNonNull(h);        this.h = h;    }//查找或生成指定的代理类    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {      //......省略部分代码......        //如果代理类已经通过实现给定接口的类加载器创建了,则返回缓存中的该类的副本;否则将通过ProxyClassFactory创建代理类         return proxyClassCache.get(loader, interfaces);    }//newProxyInstance()public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,                     InvocationHandler h)    {        //......省略部分代码......        final Class<?>[] intfs = interfaces.clone();        //查找或生成指定的代理类        Class<?> cl = getProxyClass0(loader, intfs);        //获得类的构造函数          final Constructor<?> cons = cl.getConstructor(constructorParams);          //newInstance       return cons.newInstance(new Object[]{h});    }

3)java.lang.reflect.Proxy.ProxyClassFactory(内部类)
通过ProxyClassFactory创建代理类.

// 根据给定的类加载器和接口数组生成代理类的工厂类 private static final class ProxyClassFactory      implements BiFunction{   // 所有代理类名称的前缀          private static final String proxyClassNamePrefix = "$Proxy";       //用于生成唯一代理类名称的下一个序号          private static final AtomicLong nextUniqueNumber = new AtomicLong();        @Override        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {            //验证interfaces合法性            //......省略部分代码......            //获取代理类序号            long num = nextUniqueNumber.getAndIncrement();            String proxyName = proxyPkg + proxyClassNamePrefix + num;            //生成代理类字节码            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(                proxyName, interfaces, accessFlags);         //加载代理类字节码.并返回实例化对象                return defineClass0(loader, proxyName,                                    proxyClassFile, 0, proxyClassFile.length);        }}

4)生成的代理对象$Proxy0
目前我们只能使用代理对象,但是却获取不到代理对象的字节码,我们可以通过如下两种方式获取:
a.通过ProxyGenerator.generateProxyClass来获取,会在项目根目录下生成com.sun.proxy.$Proxy0.class文件

    public static void craeteProxyClazzFile(String clazzName , Class<?> ... interfaces) {        byte[] classFile = ProxyGenerator.generateProxyClass(clazzName, interfaces);        try (FileOutputStream out = new FileOutputStream(System.getProperty("user.dir") + File.separator+clazzName+".class");){            out.write(classFile);        } catch (Exception e) {            e.printStackTrace();        }    }

b.在Proxy.newInstance之前,增加如下配置,会在项目根目录下创建com/sun/proxy/$Proxy0.class

    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

接下来我们来看下$Proxy0.class的代码构成:

public final class $Proxy0 extends Proxy implements Animal {    private static Method m1;    private static Method m3;    private static Method m2;    private static Method m0;    public $Proxy0(InvocationHandler var1) throws  {        super(var1);    }    public final void eat() throws  {        //调用父类的InvocationHandler.invoke()方法        super.h.invoke(this, m3, (Object[])null);    }    static {       //......省略部分代码......            m3 = Class.forName("pattern.proxy.beans.Animal").getMethod("eat", new Class[0]);    }}

4.jdk动态代理小结
jdk动态代理必须只针对接口方法的代理,如果业务类没有实现interface则无法使用jdk动态代理。如果业务类实现了接口,在接口新增了方法时,(jdk8中,支持接口default方法,否则会报错)如果代理类么有同步更新则是无法代理的,给之后的维护带来了麻烦。


cglib动态代理

JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,我们可以使用cglib来实现动态代理。
cglib动态代理的原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。—这里可以得知final类是无法被代理的。


1.实现步骤
a.定义自己的回调函数类,通常我们使用实现了callback的子接口MethodInterceptor,InvocationHandler
这里写图片描述

b.enhance设置真实对象.class,设置callback(),生成代理对象;常用的拦截器有:
c.获取代理对象,执行业务方法


2.代码示例
方法(一)

//实现MethodInterceptor接口public class TimeInterceptorFactory implements MethodInterceptor {    private Enhancer enhancer = new Enhancer();    public Object getProxy(Class clazz) {        enhancer.setSuperclass(clazz);        enhancer.setCallback(this);        return enhancer.create();    }    @Override    public Object intercept(Object obj, Method m, Object[] objects, MethodProxy methodProxy) throws Throwable {        long startTime = System.currentTimeMillis();        System.out.println("start eat-----------------");        //注意调用的方法!! methodProxy.invokeSuper()        Object returnVal = methodProxy.invokeSuper(obj, objects);        long endTime = System.currentTimeMillis();        System.out.println("end eat--------------------" + (endTime - startTime) + "ms");        return returnVal;    }}

测试类:

public static void testTimeInterceptorFactory() {        TimeInterceptorFactory interceptor = new TimeInterceptorFactory();        SingleDog proxy = (SingleDog) interceptor.getProxy(SingleDog.class);        proxy.eat();    }

方法(二)

    public static void testMethodInterceptor2() throws Exception {        Enhancer enhancer = new Enhancer();        Animal target = new SingleDog();        enhancer.setSuperclass(target.getClass());        enhancer.setCallback(new net.sf.cglib.proxy.InvocationHandler() {            @Override            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                long startTime = System.currentTimeMillis();                System.out.println("start eat-----------------");                //调用的方法                Object returnVal = method.invoke(target, args);                long endTime = System.currentTimeMillis();                System.out.println("end eat--------------------" + (endTime - startTime) + "ms");                return returnVal;            }        });        SingleDog proxy = (SingleDog) enhancer.create();        proxy.eat();    }

3.源码解析
如何查看cglib动态代理产生的类呢?我们可以在生成代理类之前增加一个系统变量,即可在工程根目录/net/sf/cglib/proxy 获得产生的代理类

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "net/sf/cglib/proxy");

cglib动态代理会产生多个class文件:

MethodWrapper$MethodWrapperKey$$KeyFactoryByCGLIB$$d45e49f7.classEnhancer$EnhancerKey$$KeyFactoryByCGLIB$$7fb24d72.classSingleDog$$EnhancerByCGLIB$$ab95b44d$$FastClassByCGLIB$$5613c2ba.classSingleDog$$EnhancerByCGLIB$$ab95b44d.class //关注这个SingleDog$$FastClassByCGLIB$$47f8b6eb.class

1) SingleDog$$EnhancerByCGLIB$$ab95b44d
命名规则:package.真实对象class+$$EnhancerByCGLIB$$+key

//静态语句块static {        //静态语句中调用        CGLIB$STATICHOOK1();    }static void CGLIB$STATICHOOK1() {        CGLIB$THREAD_CALLBACKS = new ThreadLocal();        CGLIB$emptyArgs = new Object[0];        Class var0 = Class.forName("pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d");        Class var1; //实际调用类为:SingleDog     //....省略部分.....        CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0");    }//MethodProxyfinal void CGLIB$eat$0() {    super.eat();}//重写后的eat方法public final void eat() {    /**     * callback子接口的类型:常见的还有net.sf.cglib.proxy.InvocationHandler;     */    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;    if(this.CGLIB$CALLBACK_0 == null) {        //设置this.CGLIB$CALLBACK_0 为 enhance.callbacks[0]        CGLIB$BIND_CALLBACKS(this);        //ca        var10000 = this.CGLIB$CALLBACK_0;    }    if(var10000 != null) {        /**         * 执行callback_0.intercept         * this == 即代理类SingleDog$$EnhancerByCGLIB$$ab95b44d         * CGLIB$eat$0$Method即 被代理的业务方法 == SingleDog.eat();         *  CGLIB$eat$0$Method = ReflectUtils.findMethods(new String[]{"eat", "()V"}, (var1 = Class.forName("pattern.proxy.beans.SingleDog")).getDeclaredMethods())[0];         * CGLIB$emptyArgs = 参数         * CGLIB$eat$0$Proxy = MethodProxy.create(var1, var0, "()V", "eat", "CGLIB$eat$0");         */        var10000.intercept(this, CGLIB$eat$0$Method, CGLIB$emptyArgs, CGLIB$eat$0$Proxy);    } else {        super.eat();    }}//由enhance.registerCallbacks(Class generatedClass, Callback[] callbacks)  调用此方法设置CGLIB$STATIC_CALLBACKS[]    public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) {        CGLIB$STATIC_CALLBACKS = var0;    }    //设置callback    private static final void CGLIB$BIND_CALLBACKS(Object var0) {        SingleDog$$EnhancerByCGLIB$$ab95b44d var1 = (SingleDog$$EnhancerByCGLIB$$ab95b44d)var0;        //第一次进来时 var1.CGLIB$BOUND = false        if(!var1.CGLIB$BOUND) {            var1.CGLIB$BOUND = true;            //CGLIB$THREAD_CALLBACKS = new ThreadLocal();            Object var10000 = CGLIB$THREAD_CALLBACKS.get();            //第一次进来时var10000 = null;            if(var10000 == null) {                /**                 * CGLIB$STATIC_CALLBACKS 由 CGLIB$SET_STATIC_CALLBACKS(Callback[] var0) 设置                 */                var10000 = CGLIB$STATIC_CALLBACKS;                if(CGLIB$STATIC_CALLBACKS == null) {                    return;                }            }            //设置this.CGLIB$CALLBACK_0 = enhance.setCallbacks()数组中的第一个            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];        }    }

2) .net.sf.cglib.proxy.MethodProxy
methodProxy是在上述代理类中生成的:

    /**     * 在SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$STATICHOOK1()中调用     * c1 : SingleDog     * c2 : pattern.proxy.beans.SingleDog$$EnhancerByCGLIB$$ab95b44d     * desc: "()V",     * name1: "eat"     * name2: "CGLIB$eat$0"     */    public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {        MethodProxy proxy = new MethodProxy();        //SingleDog.eat()        proxy.sig1 = new Signature(name1, desc);        //SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()        proxy.sig2 = new Signature(name2, desc);        proxy.createInfo = new CreateInfo(c1, c2);        return proxy;    }    private void init()    {        if (fastClassInfo == null)        {            synchronized (initLock)            {                if (fastClassInfo == null)                {                    CreateInfo ci = createInfo;                    FastClassInfo fci = new FastClassInfo();                    //真实对象:SingleDog                    fci.f1 = helper(ci, ci.c1);                    //代理对象:SingleDog$$EnhancerByCGLIB$$ab95b44d                    fci.f2 = helper(ci, ci.c2);                    //业务方法SingleDog.eat()                    fci.i1 = fci.f1.getIndex(sig1);                    //SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()                    fci.i2 = fci.f2.getIndex(sig2);                    fastClassInfo = fci;                }            }        }    }//解释:为什么在设置的callback接口实现类中,必须使用methodproxy.invokeSuper。public Object invokeSuper(Object obj, Object[] args) throws Throwable {        try {            init();            FastClassInfo fci = fastClassInfo;            /**             * fci.f2 : SingleDog$$EnhancerByCGLIB$$ab95b44d             * fci.i2 : SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()             * 观察SingleDog$$EnhancerByCGLIB$$ab95b44d.CGLIB$eat$0()的逻辑为:super.eat()即SingleDog.eat()             * 得知等价于调用SingleDog.eat()             */            return fci.f2.invoke(fci.i2, obj, args);        } catch (InvocationTargetException e) {            throw e.getTargetException();        }    }

4.cglib动态代理小结
CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少,但是CGLib在创建代理对象时所花费的时间却比JDK多得多,所以对于单例的对象,因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时,由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理


参考文献

https://www.ibm.com/developerworks/cn/java/j-lo-proxy-pattern/index.html

原创粉丝点击