• java jdk动态代理学习记录


    转载自: https://www.jianshu.com/p/3616c70cb37b

           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】添加了一款产品
    正在添加iphone
    
    Process 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<Void>() {
                        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】添加了一款产品
    正在添加iphone
    
    Process finished with exit code 0

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

    最后附上源码地址:https://github.com/MarkGao11520/my-proxy



    作者:Meet相识_bfa5
    链接:https://www.jianshu.com/p/3616c70cb37b
    來源:简书

  • 相关阅读:
    操作系统-存储管理(6)buffer/cache/swap
    RTMP推流摄像机搭配EasyDSS流媒体直播方案,适用场景幼儿园/明厨亮灶/商超水利等
    企业远程办公视频会议系统EasyRTC-SFU下侧边栏边框超限问题如何解决?
    TSINGSEE青犀视频流媒体音视频系列产品解决方案(汇总篇)
    Easy系列开源与免费流媒体音视频方案汇总(持续更新)
    TSINGSEE青犀视频Easy系列视频平台是如何输出HLS流的?HLS协议详解
    为什么RTMP视频推流网关EasyRTMPLive,拉流至EasyDSS视频平台却失败了?
    视频监控系统搭建为什么要使用流媒体服务器做视频分发?
    网页无插件视频流媒体播放器EasyPlayerPro-IOS版如何解决有声音无画面的问题?
    网页视频流媒体播放器EasyPlayer.JS开发web H5网页播放H.265视频支持FLV与HLS直播与点播
  • 原文地址:https://www.cnblogs.com/huaixiaonian/p/9830876.html
Copyright © 2020-2023  润新知