博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java jdk动态代理学习记录
阅读量:6112 次
发布时间:2019-06-21

本文共 15717 字,大约阅读时间需要 52 分钟。

转载自: 

       JDK自带的动态代理主要是指,实现了InvocationHandler接口的类,会继承一个invoke方法,通过在这个方法中添加某些代码,从而完成在方法前后添加一些动态的东西。JDK自带的动态代理依赖于接口,如果有些类没有接口,则不能实现动态代理。

1. 原理源码剖析

     *  首先我们先来讲一下JDK动态代理的实现原理

     1. 拿到被代理对象的引用,然后获取他的接口

     2. JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
     3. 把被代理对象的引用拿到了
     4. 重新动态生成一个class字节码
     5. 然后编译

    *  然后先实现一个动态代理,代码很简单了,就是

          实现java.lang.reflect.InvocationHandler接口,

          并使用java.lang.reflect.Proxy.newProxyInstance()方法生成代理对象

/** * @author mark * @date 2018/3/30 */public class JdkInvocationHandler implements InvocationHandler {    private ProductService target;    public Object getInstance(ProductService target){        this.target = target;        Class clazz = this.target.getClass();        // 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3        return Proxy.newProxyInstance(clazz.getClassLoader(),                clazz.getInterfaces(),                this);    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");        String currentDate  = simpleDateFormat.format(new Date());        System.out.println("日期【"+currentDate + "】添加了一款产品");        return method.invoke(this.target,args);    }}

被代理接口和实现

/** * 模仿产品Service * @author mark * @date 2018-03-30 */public interface ProductService {    /**     * 添加产品     * @param productName     */    void addProduct(String productName);}/** * @author mark * @date 2018/3/30 */public class ProductServiceImpl implements ProductService{    public void addProduct(String productName) {        System.out.println("正在添加"+productName);    }}

测试类

public class Test {    public static void main(String[] args) throws Exception {        ProductService productService = new ProductServiceImpl();        ProductService proxy = (ProductService) new JdkInvocationHandler().getInstance(productService);        proxy.addProduct("iphone");        // 这里我们将jdk生成的代理类输出了出来,方便后面分析使用        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{productService.getClass()});        FileOutputStream os = new FileOutputStream("Proxy0.class");        os.write(bytes);        os.close();    }}

结果输出

日期【2018-03-30】添加了一款产品正在添加iphoneProcess finished with exit code 0

上面我们实现动态动态代理的时候输出了代理类的字节码文件,现在来看一下字节码文件反编译过后的内容

import com.gwf.jdkproxy.ProductServiceImpl;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;// 继承了Proxy类public final class $Proxy0 extends Proxy implements ProductServiceImpl {    private static Method m1;    private static Method m8;    private static Method m2;    private static Method m3;    private static Method m5;    private static Method m4;    private static Method m7;    private static Method m9;    private static Method m0;    private static Method m6;    public $Proxy0(InvocationHandler var1) throws  {        super(var1);    }......../*** 这里是代理类实现的被代理对象的接口的相同方法*/    public final void addProduct(String var1) throws  {        try {            // super.h 对应的是父类的h变量,他就是Proxy.nexInstance方法中的InvocationHandler参数           // 所以这里实际上就是使用了我们自己写的InvocationHandler实现类的invoke方法            super.h.invoke(this, m3, new Object[]{var1});        } catch (RuntimeException | Error var3) {            throw var3;        } catch (Throwable var4) {            throw new UndeclaredThrowableException(var4);        }    }       public final Class getClass() throws  {        try {            return (Class)super.h.invoke(this, m7, (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"));            m8 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notify");            m2 = Class.forName("java.lang.Object").getMethod("toString");            m3 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("addProduct", Class.forName("java.lang.String"));            m5 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE);            m4 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait", Long.TYPE, Integer.TYPE);            m7 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("getClass");            m9 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("notifyAll");            m0 = Class.forName("java.lang.Object").getMethod("hashCode");            m6 = Class.forName("com.gwf.jdkproxy.ProductServiceImpl").getMethod("wait");        } catch (NoSuchMethodException var2) {            throw new NoSuchMethodError(var2.getMessage());        } catch (ClassNotFoundException var3) {            throw new NoClassDefFoundError(var3.getMessage());        }    }}

补充一下上面代码注释中的super.h

protected InvocationHandler h;protected Proxy(InvocationHandler h) {        Objects.requireNonNull(h);        this.h = h;    }// 这个方法是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
() { public Void run() { cons.setAccessible(true); return null; } }); }// 重点看这里,将我们传来的InvocationHandler参数穿给了构造函数 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); } }
以上就是jdk动态代理的内部实现过程,最后再次将上面的原理声明一遍,强化记忆
1.拿到被代理对象的引用,然后获取他的接口 (Proxy.getInstance方法)
2.JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口 (上面的反编译文件中实现了同样的接口)
3.把被代理对象的引用拿到了(上面被代理对象中在静态代码块中通过反射获取到的信息,以及我们实现的JdkInvocationHandler中的target)
4.重新动态生成一个class字节码
5.然后编译
 

2.  自己手写一个动态代理

(声明:本代码只用作实例,很多细节没有考虑进去,比如,多接口的代理类,Object类的其他默认方法的代理,为确保原汁原味,一些模板引擎和commons工具类也没有使用;觉得不足的老铁们可以随意完善,记得评论区留言完善方法哦)

我们使用jdk代理的类名和方法名定义,已经执行思路,但是所有的实现都自己来写;

首先先定义出类结构

/** * 自定义类加载器 * @author gaowenfeng * @date 2018/3/30 */public class MyClassLoader extends ClassLoader {    /**     * 通过类名称加载类字节码文件到JVM中     * @param name 类名     * @return 类的Class独享     * @throws ClassNotFoundException     */    @Override    protected Class
findClass(String name) throws ClassNotFoundException { return super.findClass(name); }}
/** * @desc 自己实现的代理类,用来生成字节码文件,并动态加载到JVM中 * @author gaowenfeng * @date 2018/3/30 */public class MyProxy {    /**     * 生成代理对象     * @param loader 类加载器,用于加载被代理类的类文件     * @param interfaces 被代理类的接口     * @param h 自定义的InvocationHandler接口,用于具体代理方法的执行     * @return 返回被代理后的代理对象     * @throws IllegalArgumentException     */    public static Object newProxyInstance(MyClassLoader loader,                                          Class
[] interfaces, MyInvocationHandler h) throws IllegalArgumentException{ /** * 1.生成代理类的源代码 * 2.将生成的源代码输出到磁盘,保存为.java文件 * 3.编译源代码,并生成.java文件 * 4.将class文件中的内容,动态加载到JVM中 * 5.返回被代理后的代理对象 */ return null; }}
/** * 自定义类加载器 * @author gaowenfeng * @date 2018/3/30 */public class MyClassLoader extends ClassLoader {    /**     * 通过类名称加载类字节码文件到JVM中     * @param name 类名     * @return 类的Class独享     * @throws ClassNotFoundException     */    @Override    protected Class
findClass(String name) throws ClassNotFoundException { return super.findClass(name); }}
/** * @author gaowenfeng * @date 2018/3/30 */public class CustomInvocationHandler implements MyInvocationHandler {    private ProductService target;    public Object getInstance(ProductService target){        this.target = target;        Class clazz = this.target.getClass();        // 参数1:被代理类的类加载器 参数2:被代理类的接口 参数3        // 这里的MyClassLoader先用new的方式保证编译不报错,后面会修改        return MyProxy.newProxyInstance(new MyClassLoader(),                clazz.getInterfaces(),                this);    }    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");        String currentDate  = simpleDateFormat.format(new Date());        System.out.println("日期【"+currentDate + "】添加了一款产品");        return method.invoke(this.target,args);    }}

接下来我们来按照步骤一步一步的完善我们的类

  1. 生成代理类的源文件
/**     * 生成代理类的源代码     * @return     */    private static String genSesource(Class
interfaces){ StringBuilder src = new StringBuilder(); src.append("package com.gwf.custom;").append(ln) .append("import java.lang.reflect.Method;").append(ln) .append("public class $Proxy0 implements ").append(interfaces.getName()).append("{").append(ln) .append("private MyInvocationHandler h;").append(ln) .append("public $Proxy0(MyInvocationHandler h){").append(ln) .append("this.h=h;").append(ln) .append("}").append(ln); for(Method method:interfaces.getMethods()){ src.append("public ").append(method.getReturnType()).append(" ").append(method.getName()).append("() {").append(ln) .append("try {").append(ln) .append("Method m = ").append(interfaces.getName()).append(".class.getMethod(\"").append(method.getName()).append("\");").append(ln) .append("this.h.invoke(this, m, new Object[]{});").append(ln) .append("}catch (Throwable e){").append(ln) .append("e.printStackTrace();").append(ln) .append("}").append(ln) .append("}").append(ln); } src.append("}"); return src.toString(); }

      2.  将源文件保存到本地

// 1.生成代理类的源代码            String src = genSesource(interfaces);            // 2.将生成的源代码输出到磁盘,保存为.java文件            String path = MyProxy.class.getResource("").getPath();            File file = new File(path+"$Proxy0.java");            FileWriter fw = new FileWriter(file);            fw.write(src);            fw.close();

   3.  编译源代码,并生成.java文件

// 3.编译源代码,并生成.java文件            // 获取java编译器            JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();            // 标注java文件管理器,用来获取java字节码文件            StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null);            Iterable iterable = manager.getJavaFileObjects(file);            // 创建task,通过java字节码文件将类信息加载到JVM中            JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable);            // 开始执行task            task.call();            // 关闭管理器            manager.close();

4. 将class文件中的内容,动态加载到JVM中

public class MyClassLoader extends ClassLoader {    private String baseDir;    public MyClassLoader(){        this.baseDir = MyClassLoader.class.getResource("").getPath();    }    /**     * 通过类名称加载类字节码文件到JVM中     * @param name 类名     * @return 类的Class独享     * @throws ClassNotFoundException     */    @Override    protected Class
findClass(String name) throws ClassNotFoundException { // 获取类名 String className = MyClassLoader.class.getPackage().getName()+"."+name; if(null == baseDir) { throw new ClassNotFoundException(); } // 获取类文件 File file = new File(baseDir,name+".class"); if(!file.exists()){ throw new ClassNotFoundException(); } // 将类文件转换为字节数组 try( FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream(); ){ byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer))!=-1){ out.write(buffer,0,len); } // 调用父类方法生成class实例 return defineClass(className,out.toByteArray(),0,out.size()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }

5.  返回被代理后的代理对象

Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);            return c.newInstance(h);

最后看一下总体的MyProxy类 的 newProxyInstance方法

public static Object newProxyInstance(MyClassLoader loader,                                          Class
interfaces, MyInvocationHandler h) throws IllegalArgumentException{ /** * 1.生成代理类的源代码 * 2.将生成的源代码输出到磁盘,保存为.java文件 * 3.编译源代码,并生成.java文件 * 4.将class文件中的内容,动态加载到JVM中 * 5.返回被代理后的代理对象 */ try { // 1.生成代理类的源代码 String src = genSesource(interfaces); // 2.将生成的源代码输出到磁盘,保存为.java文件 String path = MyProxy.class.getResource("").getPath(); File file = new File(path+"$Proxy0.java"); FileWriter fw = new FileWriter(file); fw.write(src); fw.close(); // 3.编译源代码,并生成.java文件 JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager manager = javaCompiler.getStandardFileManager(null,null,null); Iterable iterable = manager.getJavaFileObjects(file); JavaCompiler.CompilationTask task = javaCompiler.getTask(null,manager,null,null,null,iterable); task.call(); manager.close(); // 4.将class文件中的内容,动态加载到JVM中 Class proxyClass = loader.findClass("$Proxy0"); // 5.返回被代理后的代理对象 Constructor c = proxyClass.getConstructor(MyInvocationHandler.class); return c.newInstance(h); } catch (Exception e) { e.printStackTrace(); } return null; }

测试运行

public class CustomClient {    public static void main(String[] args){        ProductService productService = new ProductServiceImpl();        ProductService proxy = (ProductService) new CustomInvocationHandler().getInstance(productService);        proxy.addProduct();    }}

运行结果

日期【2018-03-30】添加了一款产品正在添加iphoneProcess finished with exit code 0

总结:以上通过理解jdk动态代理的原理,自己手写了一个动态代理,里面涉及到的重点主要是代理类字节码的生成(这里采用通过反射强行生成源文件并编译的方法,其实应该可以直接生成字节码文件的,有兴趣的同学可以尝试)和将生成的类动态加载到JVM中(本次试验由于测试,比较简单,直接将类名硬编码到了系统里,正常应该是自动加载),虽然还不完善,但是对于理解原理应该是有狠多帮助了,欢迎同学们评论区留言评论给出更好的建议

最后附上源码地址:

作者:Meet相识_bfa5
链接:
來源:简书

 

转载于:https://www.cnblogs.com/huaixiaonian/p/9830876.html

你可能感兴趣的文章
PHP生成word的三种方式
查看>>
设计模式(九)——桥接模式
查看>>
xen 创建本地存储
查看>>
TCP三次握手/四次挥手 | NAT介绍 |OSI与TCP/IP模型
查看>>
jQuery UI dialog 的使用
查看>>
ABP实战--集成Ladp/AD认证
查看>>
存储过程
查看>>
phpcms v9栏目列表调用每一篇文章内容方法
查看>>
python 自定义信号处理器
查看>>
我只是轻奢 40万内入门豪车最高让利7万!-搜狐汽车
查看>>
曲演杂坛--隐式转换
查看>>
远程桌面连接技巧--与主机拷贝文本及拷贝文件(转)
查看>>
MVC中下拉框显示枚举项
查看>>
Linux基础精华
查看>>
SqlServer2008第一次安装后连接问题
查看>>
cocos2d-x Schedule详解
查看>>
sdut 2163:Identifiers(第二届山东省省赛原题,水题)
查看>>
C++ 容器:顺序性容器、关联式容器和容器适配器
查看>>
mysql 常用语句集
查看>>
Atitit.软件开发提升稳定性总结
查看>>