JDK动态代理原理

来源:互联网 发布:oa办公自动化系统源码 编辑:程序博客网 时间:2024/06/05 01:05

静态代理

生活中代理的例子不胜枚举:
张三由于工作很忙,迟迟没有结婚,家里人很着急。他爸爸拿着他的照片帮他张罗着相亲,他爸爸就是他的一个代理。
- 定义接口

public interface People {    /**     * 找对象     */    public void Zhaoduixiang();}
  • 张三
public class ZhangSan implements People{    public void Zhaoduixiang() {        System.out.println(this.getClass().getName()+":要求不多,人美胸大活好!");    }}
  • 张三他爸
public class ZhangSanFather implements People{    /**     * 被代理对象的引用,相当于他爸拿着张三的照片     */    private ZhangSan zhangSan;    public ZhangSanFather(){    }    public ZhangSanFather(ZhangSan zhangSan){        this.zhangSan = zhangSan;    }    /**     * 代理     */    public void Zhaoduixiang() {        before();        this.zhangSan.Zhaoduixiang();        after();    }    /**     *  想跟我儿子好,我先要审核下     */    private void before(){        System.out.println(this.getClass().getName()+":要求人品好,家境好!");    }    /**     * 评价这个菇凉     */    private void after(){        System.out.println(this.getClass().getName()+":这个菇凉阔以!");    }}
  • 测试
    @Test    public void testStaticProxy(){        People zhangSanFather = new ZhangSanFather(new ZhangSan());        zhangSanFather.Zhaoduixiang();    }
proxy.staticProxy.ZhangSanFather:要求人品好,家境好!proxy.staticProxy.ZhangSan:要求不多,人美胸大活好!proxy.staticProxy.ZhangSanFather:这个菇凉阔以!

重要特点:
- 代理类持有被代理类的引用

动态代理

张三没结婚,请个保姆做饭,洗碗,张三自己只用张嘴吃饭就行。保姆就是张三的代理。
- 接口

public interface People {    /**     * 吃东西     */    void eat(String food) throws Throwable;}
  • 张三
public class ZhangSan implements  People{    /**     * 吃     * @param food 食物     */    public void eat(String food) {        System.out.println(this.getClass().getName()+":吃"+food+"!");    }}
  • 代理
public class ProxyHandler implements InvocationHandler{    /**     * 被代理对象的引用     */    private People people;    public ProxyHandler(People people){       this.people = people;    }    /**     * 代理方法     * @param proxy     * @param method     * @param args     * @return     * @throws Throwable     */    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        before();        method.invoke(people,args);        after();        return null;    }    /**     * 执行在代理方法之前     */    private void before(){        System.out.println(this.getClass().getName()+":做饭!");    }    /**     * 执行在代理方法之后     */    private void after(){        System.out.println(this.getClass().getName()+":洗碗!");    }}
  • 测试
    @Test    public void testJDKDynamicProxy() throws Throwable {        //创建代理        People people = (People) Proxy.newProxyInstance(                People.class.getClassLoader(),                new Class[]{People.class},                new ProxyHandler(new ZhangSan()));        System.out.println(people.getClass().getName());        //执行操作        people.eat("炸鸡,喝啤酒!");    }
com.sun.proxy.$Proxy0proxy.dynamicProxy.ProxyHandler:做饭!proxy.dynamicProxy.ZhangSan:吃炸鸡,喝啤酒!!proxy.dynamicProxy.ProxyHandler:洗碗!

到这里,效果虽然看到了,但是为什么要使用动态代理呢?

保姆给张三做饭、洗碗,大多时候不会像张三他爸那样长期固定,可能会因各种原因辞职,而重新帮另外的人做饭、洗碗。她代理的具体事情没有变化,变化的只是被代理的对象,如果使用静态代理,那么每次都要重新定义代理(不符合开闭原则),或者新增代理(代码冗余,不宜维护)。

那么jdk动态代理又是如何工作的呢?

前面代码中

System.out.println(people.getClass().getName());

打印的结果是

com.sun.proxy.$Proxy0
  • 这就是jdk动态生成的,处于内存中的代理。还是一头雾水,必须要看看他具体的内容是什么
/**     * 生成代理的Class文件     */    public static void createProxyClassFile(){        //生成代理类的字节流        byte[] $Proxy0s = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{People.class});        //生成文件        try {            FileOutputStream fout = new FileOutputStream("src/main/java/proxy/dynamicProxy/$Proxy0.class");            fout.write($Proxy0s);            fout.close();        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }    }
  • 重新运行程序,生成代理文件
@Test    public void testJDKDynamicProxy() throws Throwable {        //创建代理        People people = (People) Proxy.newProxyInstance(                People.class.getClassLoader(),                new Class[]{People.class},                new ProxyHandler(new ZhangSan()));        System.out.println(people.getClass().getName());        //执行操作        people.eat("炸鸡,喝啤酒!");        createProxyClassFile();    }

image
如上图,这个$Proxy0.class就是jdk动态生成的,反编译打开该文件,真相就在里面!

public final class $Proxy0 extends Proxy implements People {    private static Method m1;    private static Method m0;    private static Method m3;    private static Method m2;    public $Proxy0(InvocationHandler var1) throws  {        super(var1);    }    public final boolean equals(Object var1) throws  {       ...    }    public final int hashCode() throws  {       ...    }    public final void eat(String var1) throws Throwable {        super.h.invoke(this, m3, new Object[]{var1});    }    public final String toString() throws  {        ...    }    static {        try {            ...            m3 = Class.forName("proxy.dynamicProxy.People").getMethod("eat", new Class[]{Class.forName("java.lang.String")});            ...        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }}

从源码可以看出$Proxy0的真实身份:
1、它实际上实现了People接口,所以可以具体实现eat(String var1)方法。
2、而且它继承了Proxy类,在jdk源码中可以看到它有一个成员变量InvocationHandler,是不是很眼熟,这就是我们自定义代码ProxyHandler实现的接口。

public class Proxy implements java.io.Serializable{    ...    protected InvocationHandler h;    ...}

3、这里逻辑就很清晰了,eat(String var1)的具体实现就是调用InvocationHandler接口的invoke方法,并把真实的eat方法反射生成通过参数传入invoke方法,所以我们才能在ProxyHandler中调用method.invoke(people,args),从而实现动态代理。

动态代理完美的诠释的开闭原则,即对扩展开放,对修改关闭。非常适用于程序扩展,非常非常重要,在各种框架中广泛应用,如大名鼎鼎的SpirngAOP。

0 0
原创粉丝点击