论JDK动态代理的重要性及自定义自己的动态代理

1、JDK的动态代理

动态代理中的几个角色:

1、被代理类

在jdk中被代理类必须实现接口

2、代理类

在jdk中代理类是运行时动态生成的,所以你在工程中看不到代理类

3、通知类也叫advice

在jdk中通知类必须实现invocationHandler接口

 

在这里我们举个例子帮助理解

比如,张三是一个IT狗,到了适婚年龄但是没有对象,平时工作忙又没有时间找对象,这时候张三的父母就着急了张罗着给张三介绍对象,这时候张三的父母就拿着张三的照片、身份证等等可以代表张三的资料去媒婆那。这个就是最典型的代理,张三的父母代张三找对象,而不是张三本人,其实张三本人就只需要结婚就行了。找对象和照顾孩子的事情就有张三的父母代理了。

 

从这个例子中,我们提取出几个点:

1、张三,肯定是被代理对象,他要做的一件事情就是结婚。这里我们抽象下,张三是一个人,所以他需要实现people接口

2、张三的父母,张三的父母是具体的执行者,比如,代理张三找对象,张三结婚后帮忙张三照顾孩子,所以我们这里抽象出一个parent类,parent中有一个before方法,一个after方法。parent需要实现invocationHandler接口,它是一个通知类,负责具体执行

3、代理类,这是动态代理的难点,因为它是看不到摸不着的,它是存在在内存中的,且听博主慢慢道来。

OK~~~看代码吧。

people接口:

public interface People {
    public void zhaoduixiang() throws Throwable;
}

 

张三类:

public class ZhangSan implements People {
    
    /* 
     * 张三这个人,平时加班很忙,
     * 他有生理问题,要解放双手
     * 只关心有没有一个帮他解放双手这个人
     * 至于怎么去找,他不关心
     */
    @Override
    public void zhaoduixiang() {
        System.out.println("=============================找到对象,解放双手(你们懂的),然后结婚=====================");
    }
    
}

 

parent类:

/** 
 * @Description TODO 
 * @ClassName   Parent 
 * @Date        2018年9月26日 下午8:12:35 
 * @Author      zg-jack
 * 
 * 这个就是张三的父母类
 * 这个类的目的就是为了增强张三的zhaoduixiang()方法
 * 
 * 
 * 张三的父母需要持有张三这个人的引用
 * 
 * 
 */

public class Parent implements InvocationHandler {
    
    private People people;
    
    public Parent(People people) {
        this.people = people;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //前置增强
        before();
        
        //2、调用张三的这个人的zhaoduixiang方法
        method.invoke(people, args);
        
        //3、调用after()
        after();
        return null;
    }
    
    /** 
     * @Description TODO 
     * @param  参数 
     * @return void 返回类型  
     * @throws 
     * 
     * 在张三找到对象之前,帮助张三找对象
     */
    
    public void before() {
        System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三找对象&&&&&&&&&&&&&&&&");
    }
    
    /** 
     * @Description TODO 
     * @param  参数 
     * @return void 返回类型  
     * @throws 
     * 
     * 找到对象要以后,张三父母帮他操持结婚
     */
    
    public void after() {
        System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三操持结婚的事情&&&&&&&&&&&&&&&&");
    }
    
}

测试类:

public class MyTest {
    
    public static void main(String[] args) throws Throwable {
        //这个返回的对象就是张三这个人的代理对象
        People proxyPeople = (People)Proxy.newProxyInstance(MyTest.class.getClassLoader(),
                new Class<?>[] {People.class},
                new Parent(new ZhangSan()));
        createProxyClassFile();
        
        proxyPeople.zhaoduixiang();
    }
    
    /** 
     * @Description TODO 
     * @param  参数 
     * @return void 返回类型  
     * @throws 
     * 
     * 用流的方式把内存中的代理对象的字节码输出到.class文件中
     */
    
    public static void createProxyClassFile() {
        byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",
                new Class[] {People.class});
        
        try {
            FileOutputStream out = new FileOutputStream("$Proxy0.class");
            out.write(data);
            out.close();
        }
        catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
}

测试结果:

&&&&&&&&&&&&&&&张三的父母,帮助张三找对象&&&&&&&&&&&&&&&&
=============================找到对象,解放双手(你们懂的),然后结婚=====================
&&&&&&&&&&&&&&&张三的父母,帮助张三操持结婚的事情&&&&&&&&&&&&&&&&

从这个测试结果,我们仅仅用了一行调用代码

proxyPeople.zhaoduixiang();就出现了这个结果,而这个结果明显又是parent类中的invoke方法的打印结果,那么这里问题来了,谁调用了parent中的invoke方法???OK,带着这个问题,我们看一下图,从图中了解动态代理的调用过程,回答前面的代理类问题,如图:

论JDK动态代理的重要性及自定义自己的动态代理

从图中我们可以看出,proxypeople.zhaoduixiang()调用的实际上是内存中的代理对象,这个对象是程序在运行时的时候动态加载到内存里的,所以程序员看不到,那么有什么办法可以看到这个内存中的代理对象的代码呢,有办法~~~这个博主待会儿讲。从图中我们可以看出,带代理对象中,有一个属性,invocationHandler h属性,而h属性的值就是parent对象,而在代理对象中的方法zhaoduixiang中就只有一行代理,h.invoke(.......),其实就是掉到了parent对象中的invoke方法。。。这里就回答了刚刚那个问题,谁调了parent对象的invoke方法? 这下我们知道了,是内存中的代理对象调到了invoke方法。。。

那么还是回到前面的问题,内存中的代理对象,我们能拿到吗??OK,贴代码

/** 
     * @Description TODO 
     * @param  参数 
     * @return void 返回类型  
     * @throws 
     * 
     * 用流的方式把内存中的代理对象的字节码输出到.class文件中
     */
    
    public static void createProxyClassFile() {
        byte[] data = ProxyGenerator.generateProxyClass("$Proxy0",
                new Class[] {People.class});
        
        try {
            FileOutputStream out = new FileOutputStream("$Proxy0.class");
            out.write(data);
            out.close();
        }
        catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    

我们可以拿到字节码,然后用流的方式输出到.class文件中,然后用反编译工具反编译一下就可以了。。

 

为了让大家彻底领悟动态代理技术,我打算自己定义一个动态代理,不用jdk的proxy类和invocationHandler接口,这样才能彻底领悟

贴代码吧:

自定义的JackInvocationHandler接口,类似于JDK中的InvocationHandler接口

public interface JackInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

最关键的是获取代理对象,因为这个代理对象在内存里面,在JDK中我们用Proxy这个类获取代理对象

People proxyPeople = (People)Proxy.newProxyInstance(MyTest.class.getClassLoader(),
                new Class<?>[] {People.class},
                new Parent(new ZhangSan()));

那么我们自己定义的动态代理里面,这个代理对象是如何获取呢??

MyParent类,类似于parent类

public class MyParent implements JackInvocationHandler {
    
    private People people;
    
    public MyParent(People people) {
        this.people = people;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //前置增强
        before();
        
        //2、调用张三的这个人的zhaoduixiang方法
        method.invoke(people, args);
        
        //3、调用after()
        after();
        return null;
    }
    
    /** 
     * @Description TODO 
     * @param  参数 
     * @return void 返回类型  
     * @throws 
     * 
     * 在张三找到对象之前,帮助张三找对象
     */
    
    public void before() {
        System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三找对象&&&&&&&&&&&&&&&&");
    }
    
    /** 
     * @Description TODO 
     * @param  参数 
     * @return void 返回类型  
     * @throws 
     * 
     * 找到对象要以后,张三父母帮他操持结婚
     */
    
    public void after() {
        System.out.println("&&&&&&&&&&&&&&&张三的父母,帮助张三操持结婚的事情&&&&&&&&&&&&&&&&");
    }
    
}

MyProxy类,这个是最关键的核心代码,其功能类似于JDK中的Proxy类

public class MyProxy {
    
    private static String rt = "\r\n";
    
    /** 
     * @Description TODO 
     * @param @param loader
     * @param @param interfaces
     * @param @param h
     * @param @return 参数 
     * @return Object 返回类型  
     * @throws
     * 
     *  这个方法最终是需要返回一个代理对象
     *  1、运行时动态生成
     *  2、它是一个类
     *  3、它要实现people接口
     *  4、是以字节码的方式加载到内存中
     *    
     */
    public static Object newProxyInstance(ClassLoader loader,
            Class<?>[] interfaces, JackInvocationHandler h) {
        
        String fileName = "F:/workspace/proxy/src/main/java/com/zhuguang/jack/myproxy/$Proxy0.java";
        
        //1、我们要生成一个类,以字符串拼凑的方式,拼凑出一个类
        String javaClassStr = getJavaStr(interfaces);
        
        //2、通过流的方式生成java文件
        createJavaFile(javaClassStr, fileName);
        
        //3、动态编译java文件,生成.class文件
        compilerJava(fileName);
        
        //4、要把磁盘里面的.class文件内容加载到jvm的内存
        Object instance = LoadClass(h);
        return instance;
    }
    
    /** 
     * @Description TODO 
     * @param @param h
     * @param @return 参数 
     * @return Object 返回类型  
     * @throws 
     * 
     * 自定义一个类加载器
     */
    
    private static Object LoadClass(JackInvocationHandler h) {
        JackClassLoader jackClassLoader = new JackClassLoader(
                "F:/workspace/proxy/src/main/java/com/zhuguang/jack/myproxy");
        try {
            //返回的是一个被代理对象的Class对象
            Class<?> findClass = jackClassLoader.findClass("$Proxy0");
            Constructor<?> constructor = findClass.getConstructor(JackInvocationHandler.class);
            Object newInstance = constructor.newInstance(h);
            return newInstance;
        }
        catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
    
    private static void compilerJava(String fileName) {
        JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();
        
        StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null,
                null,
                null);
        
        Iterable<? extends JavaFileObject> javaFileObjects = standardFileManager.getJavaFileObjects(fileName);
        
        CompilationTask task = systemJavaCompiler.getTask(null,
                standardFileManager,
                null,
                null,
                null,
                javaFileObjects);
        task.call();
        try {
            standardFileManager.close();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    private static void createJavaFile(String javaClassStr, String fileName) {
        
        try {
            File f = new File(fileName);
            FileWriter fw;
            fw = new FileWriter(f);
            fw.write(javaClassStr);
            fw.flush();
            fw.close();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    
    private static String getJavaStr(Class<?>[] interfaces) {
        
        Method[] methods = interfaces[0].getMethods();
        
        String proxyClassStr = "package com.zhuguang.jack.myproxy;" + rt
                + "import java.lang.reflect.Method;" + rt
                + "public class $Proxy0 implements " + interfaces[0].getName()
                + "{" + rt + "JackInvocationHandler h;" + rt
                + "public $Proxy0(JackInvocationHandler h) {" + rt
                + "this.h=h;" + rt + "}"
                + getMethodString(methods, interfaces[0]) + rt + "}";
        
        return proxyClassStr;
    }
    
    private static String getMethodString(Method[] methods, Class intf) {
        
        String proxyMe = "";
        
        for (Method method : methods) {
            proxyMe += "public void " + method.getName()
                    + "() throws Throwable {" + rt + "Method md = "
                    + intf.getName() + ".class.getMethod(\"" + method.getName()
                    + "\",new Class[]{});" + rt
                    + "this.h.invoke(this,md,null);" + rt + "}" + rt;
            
        }
        
        return proxyMe;
    }
}

在这个类中有几个步骤:

1、生成代理类,我们用字符串拼凑的方式把类中的内容拼凑成String类型

2、通过流的方式把String写到java文件中

3、动态编译java文件,把它编译成.class文件

4、把磁盘中的.class文件加载到内存中并返回代理类的对象

通过这几个步骤,我们就可以拿到内存中的代理类,如此一个自己定义的动态代理就搞定了,是不是没有那么难?

 

希望博主的整理能够帮助到大家,如果大家觉得OK的话,麻烦点个赞,或者写个评论什么的,好歹我也要士气来 继续坚持我的原创博客之路啊,哈哈~~~~~