代理模式

  1. 云栖社区>
  2. 博客列表>
  3. 正文

代理模式

信~仰 2018-12-05 01:29:37 浏览100 评论1

摘要: 定义 为其它对象提供一种代理以控制这个对这个对象的访问。 不管是静态代理还是动态代理,目的都是要拿到目标对象的引用,并且能够调用到目标类的业务方法。 静态代理 人的抽象接口package com.faith.

定义

为其它对象提供一种代理以控制这个对这个对象的访问。

不管是静态代理还是动态代理,目的都是要拿到目标对象的引用,并且能够调用到目标类的业务方法。

静态代理

  • 人的抽象接口
package com.faith.net.proxy.staticed;

/**
 * 人抽象接口
 */
public interface Person {

    public void drive();
}
  • Boss作为被代理对象
package com.faith.net.proxy.staticed;

/**
 * 老板, 雇佣者
 */
public class Boss implements Person {

    @Override
    public void drive() {
        System.out.println("drive..");
    }
}
  • Employee作为代理对象
package com.faith.net.proxy.staticed;

/**
 * 雇员,可以被任何人雇佣。
 * 这个类的作用就是保持被代理类对象的引用,并保证能* 够调用其方法即可。不需要实现Person类
 */
public class Employee {

    private Person person;

    public Employee(Person person){
        this.person = person;
    }

    public void drive(){
        System.out.println("被雇佣,开始工作:");
        this.person.drive();
        System.out.println("工作结束。");
    }
}
  • 测试类
package com.faith.net.proxy.staticed;

public class StaticProxyTest {

    public static void main(String[] args) {
        Employee employee = new Employee(new Boss());
        employee.drive();
    }
}

静态代理的缺点,当Person添加新的方法,例如work,被代理类Boss需要实现work方法,并且代理类需要提前知道被代理的引用及其需要被代理的方法。

动态代理可以避免这些麻烦。

动态代理

动态代理中,代理类是运行期自动生成的,无需提前了解被代理类的详细情况。

静态代理在代理之前,所有东西都是已知的;动态代理在代理之前,所有东西都是未知的。

动态代理最终都会生成一个新的代理类。

jdk动态代理

jdk动态代理中,代理类必须实现InvocationHandler接口,详细请见代码注释。而代理类通过字节码重组方式实现。

Person及Boss类沿用上例即可。

  • 代理类
package com.faith.net.proxy.jdk;

import com.faith.net.proxy.staticed.Person;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * jdk代理类, 本质上是作为调用处理器的实现,所以必须要实现调用处理器接口
 */
public class JDKEmployee implements InvocationHandler {

    // 被代理对象的引用
    private Person target;

    public JDKEmployee(Person target) {
        this.target = target;
    }

    // 获取动态代理对象
    public Object getInstance() throws Exception{
        /**
         *   三个参数:
         *
         *        1、类加载器将加载进入内存中
         *
         *        2、创建出的动态代理对象需要实现哪几个接口
         *
         *        3、调用处理器,这里可直接指定为this,替换掉new JDKEmployee(target)则为return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target));
         */
        return Proxy.newProxyInstance(JDKEmployee.class.getClassLoader(), new Class[] { Person.class }, new JDKEmployee(target));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        /**
         *  InvocationHandler的invoke()方法的参数有三个:
         *
         *     Object proxy:代理对象,也就是Proxy.newProxyInstance()方法返回的对象,通常不用;
         *
         *     Method method:表示当前被调用方法的反射对象;
         *
         *     Object[] args:表示当前被调用方法的参数,没有参数的args是一个零长数组。
         *
         *   invoke()方法的返回值为Object类型,它表示当前被调用的方法的返回值
         */
        System.out.println("被雇佣,开始工作:");
        Object invoke = method.invoke(this.target, args);
        System.out.println("工作结束。");
        return invoke;
    }
}
  • 测试类
package com.faith.net.proxy.jdk;

import com.faith.net.proxy.staticed.Person;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;

/**
 * 测试类
 */
public class JDKProxyTest {

    public static void main(String[] args) {

        try {
            Person obj = (Person)new JDKEmployee(new Boss()).getInstance();
            System.out.println(obj.getClass());
            obj.drive();

            //打印出$Proxy0类文件,稍后通过反编译工具可以查看源代码
            byte [] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Person.class});
            FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
            os.write(bytes);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 字节码重组过程

1、拿到被代理对象的引用,并且获取到它的所有的接口;

2、JDK Proxy类重新生成一个新的类、同时新的类要实现被代理类所实现的所有接口;

3、动态生成新类的Java代码,把新加的业务逻辑方法由一定的逻辑代码去调用;

4、编译新生成的Java代码,生成.class字节码文件

5、将字节码文件加载到JVM中运行.

这个过程就叫字节码重组。

  • 分析代理类

上面的

System.out.println(obj.getClass());

会输出如下结果:

class com.sun.proxy.$Proxy0

按照JDK规范,$开头的类都是运行时动态生成的,例如内部类。

将$Proxy0类文件输出并拖入idea中,可以得到反编译结果如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.faith.net.proxy.staticed.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void drive() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.faith.net.proxy.staticed.Person").getMethod("drive");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

可以看到该类继承了Proxy类并实现了Person接口:

public final class $Proxy0 extends Proxy implements Person

主要看代理类中的代理方法drive:

    public final void drive() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

其中h是父类的,进入其父类Proxy可得:

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;

在此场景h就是实现了InvocationHandler接口的代理类JDKEmployee。所以$Proxy0类中drive方法调用的即是JDKEmployee的drive方法。

这就是jdk动态代理的原理。

  • 内部类替代代理类

JDKEmployee类还可以使用内部类来替代,例如:

/**
 * 测试类
 */
public class JDKProxyTest {

    public static void main(String[] args) {

        Person obj = (Person) Proxy.newProxyInstance(JDKProxyTest.class.getClassLoader(), new Class[]{Person.class}, new InvocationHandler() {

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //调用方法
                if ("drive".equals(method.getName())) {
                    System.out.println("被雇佣,开始工作:");
                    Object invoke = method.invoke(new Boss(), args);
                    System.out.println("工作结束。");
                    return  invoke;
                } else {
                    return null;
                }
            }
        });
    }
}

因为JDKEmployee存在的意义就是作为调用处理器的实现,那么这个实现当然可以使用内部类来替代。

CGLIB动态代理

jdk动态代理是基于接口实现的,而CGLIB动态代理是通过继承实现的。

同样,CGLIB方式需要代理类实现MethodInterceptor接口,其意义也是作为方法的处理器。示例如下:

  • Boss类
package com.faith.net.proxy.cglib;

/**
 * Boss
 */
public class Boss {

    public void drive(){
        System.out.println("drive..");
    }
}
  • 测试类
package com.faith.net.proxy.cglib;

/**
 * 测试
 */
public class CglibTest {

    public static void main(String[] args) {

        try {
            Boss obj = (Boss)new CglibEmployee().getInstance(Boss.class);
            obj.drive();
            System.out.println(obj.getClass());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
【云栖快讯】阿里云栖开发者沙龙(Java技术专场)火热来袭!快来报名参与吧!  详情请点击

网友评论

1F
李博bluemind

您好,我是阿里云云栖社区的运营编辑,看到您的博客,很有学习价值。麻烦您加下我的联系方式,我来给您认证社区专家,
联系小编钉钉:15810436147、QQ:1105654774 、wechat:15810436147,加的时候说下您的社区昵称