美文网首页
JFinal的Proxy实现原理

JFinal的Proxy实现原理

作者: AlienJunX | 来源:发表于2020-03-23 14:53 被阅读0次

    了解Java的动态代理后,动态创建新的代理类通过反射执行。 那JFinal的代理实现有什么不一样的地方呢,据作者介绍是为了

     * 追求性能极致:
     * 1:禁止使用 JDK 的 Method.invoke(...) 调用被代理方法
     * 2:method 存在有效的拦截器才生成代理方法 ProxyMethod
     * 3:目标类 target 存在 ProxyMethod 才生成代理类 ProxyClass
    

    作者为了追求性能极致自己设计了一套方式完成了动态代理功能。依据上面的三点意图去看代码。


    例如对User.class进行代理

    作者利用Enjoy、Class Loader、Dynamic Compile 美妙结合在一起,及其精简的代码量实现代理功能。
    其核心功能动态生成代理类是通过ProxyGenerator结合Enjoy生成Java源代码,再通过ProxyCompiler进行编译,最后ProxyClassLoader进行加载。Enjoy生成源代码部分完全可以手工编写,但由于Enjoy的好用程度太高,用模板的方式生成代码太省事了。

    ProxyClass: 代理类描述信息,生成的代理类是不可见的,相关的属性由本类管理

        private Class<?> target; // 被代理类
        private String pkg; // 报名
        private String name; // 类名
        private String sourceCode; // 生成的Java源代码
        private Map<String, byte[]> byteCode; // 编译后的字节码
        private Class<?> clazz; // 字节码被 loadClass 后的 Class
        private List<ProxyMethod> proxyMethodList = new ArrayList<>(); // 被代理的方法列表
    

    ProxyGenerator: 代理生成器,将ProxyClass中的描述信息以及被代理类分析后通过Enjoy生成Java源代码,这里通过类的注解,给有标注的类和方法生成代理。通过@Before进行注解,

    public ProxyClass generate(Class<?> target) {
        //1. 用一个Kv对象管理需要在模板中渲染的数据
        //2. 给Kv对象填充数据
        //2.1 获取被代理类的注解,getMethodUpperInterceptors()
        //3 遍历被代理类中的方法,判断此方法是否需要被代理hasInterceptor()
        //3.1 生成一个Kv存储被代理的方法信息,用于模板渲染数据
        //3.2 产生一个ProxyMethod对象存储到ProxyClass的被代理方法列表中
        //4. 通过Enjoy进行渲染
        //4.1 生成的源代码设置到ProxyClass中,为后续编译好调用
    }
    

    这里会用到一个模板,里面会有个Invocation类,这个是调用信息类,

    public class Invocation {
        private static final Object[] NULL_ARGS = new Object[0];
    
        private Object target;//代理
        private Method method;//被代理的方法
        private Object[] args;//方法参数
        private Callback callback;//回调
        private Interceptor[] inters;//拦截器组
        private Object returnValue;//方法返回值
       public void invoke() {
       }
    }
    
    package #(pkg);
    import com.alienjun.aop.Invocation;
    public class #(name)#(classTypeVars) extends #(targetName)#(targetTypeVars) {
    #for(x : methodList)
        
        public #(x.methodTypeVars) #(x.returnType) #(x.name)(#for(y : x.paraTypes)#(y) p#(for.index)#(for.last ? "" : ", ")#end) #(x.throws){
            #if(x.singleArrayPara)
            #@newInvocationForSingleArrayPara()
            #else
            #@newInvocationForCommon()
            #end
            
            inv.invoke();
            #if (x.returnType != "void")
            
            return inv.getReturnValue();
            #end
        }
    #end
    }
    
    #--
       一般参数情况
    --#
    #define newInvocationForCommon()
            Invocation inv = new Invocation(this, #(x.proxyMethodKey)L,
                args -> {
                    #(x.frontReturn) #(name).super.#(x.name)(
                            #for(y : x.paraTypes)
                            (#(y.replace("...", "[]")))args[#(for.index)]#(for.last ? "" : ",")
                            #end
                        );
                    #(x.backReturn)
                }
                #for(y : x.paraTypes), p#(for.index)#end);
    #end
    #--
       只有一个参数,且该参数是数组或者可变参数
    --#
    #define newInvocationForSingleArrayPara()
            Invocation inv = new Invocation(this, #(x.proxyMethodKey)L,
                args -> {
                    #(x.frontReturn) #(name).super.#(x.name)(
                            p0
                        );
                    #(x.backReturn)
                }
                , p0);
    #end
    

    会生成一个这样的类:

    package com.alienjun;
    import com.alienjun.aop.Invocation;
    public class User$$EnhancerByJFinal extends User {
        
        public  void showName() {
            Invocation inv = new Invocation(this, 1L,
                args -> {
                     User$$EnhancerByJFinal.super.showName(
                        );
                    return null;
                }
                );
            
            inv.invoke();
        }
    }
    
    

    ProxyCompiler: 自定义编译器,同时自定义了MyJavaFileManager,MyJavaFileObject 便于管理编译好的字节码存放位置,不需要存放到磁盘。

    
    // 继承ForwardingJavaFileManager
    // 的目的是将编译后的字节码存在内存中,不用写到磁盘
    public static class MyJavaFileManager extends ForwardingJavaFileManager<JavaFileManager> {
    public Map<String, MyJavaFileObject> fileObjects = new HashMap<>();
    
            public MyJavaFileManager(JavaFileManager fileManager) {
                super(fileManager);
            }
    
            // 编译器编译完成后ClassWriter.writeClass()回调此方法,kind 为 CLASS
            // 这里返回一个JavaFileObject接口的对象,writeClass()会问他要OutputStream,所以会调用它的openOutputStream
            // 返回一个ByteArrayOutputStream 给它。
            /*
            public JavaFileObject writeClass(ClassSymbol var1) throws IOException, ClassWriter.PoolOverflow, ClassWriter.StringOverflow {
            JavaFileObject var2 = this.fileManager.getJavaFileForOutput(StandardLocation.CLASS_OUTPUT, var1.flatname.toString(), Kind.CLASS, var1.sourcefile);
            OutputStream var3 = var2.openOutputStream();
            this.writeClassFile(var3, var1);
            var3.close();
            * */
            @Override
            public JavaFileObject getJavaFileForOutput(Location location, String qualifiedClassName, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
                MyJavaFileObject javaFileObject = new MyJavaFileObject(qualifiedClassName, kind);
                fileObjects.put(qualifiedClassName, javaFileObject);
                return javaFileObject;
            }
    
            // 是否在编译时依赖另一个类的情况下用到本方法 ?
            @Override
            public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
                JavaFileObject javaFileObject = fileObjects.get(className);
                if (javaFileObject == null) {
                    javaFileObject = super.getJavaFileForInput(location, className, kind);
                }
                return javaFileObject;
            }
    }
    
    
        // java文件对象,包含源文件内容、编译后的字节码
        public static class MyJavaFileObject extends SimpleJavaFileObject {
    
            private String source;
            // 这里巧妙的利用了 内存方式存储字节码,
            private ByteArrayOutputStream outPutStream;
    
            // 构建源文件,定义URI文件位置
            public MyJavaFileObject(String name, String source) {
                super(URI.create("String:///" + name + Kind.SOURCE.extension), Kind.SOURCE);
                this.source = source;
            }
    
            // 构建编译后的字节码文件位置
            public MyJavaFileObject(String name, Kind kind) {
                super(URI.create("String:///" + name + kind.extension), kind);
                source = null;
            }
    
            @Override
            public CharSequence getCharContent(boolean ignoreEncodingErrors) {
                if (source == null) {
                    throw new IllegalStateException("source field can not be null");
                }
                return source;
            }
    
            // 编译器编译完成后会调用此方法,将字节码写入到outputStream 中
            // MyJavaFileManager的getJavaFileForOutput返回的是MyJavaFileObject
            // 故此方法会被回调,
            // 同时MyJavaFileManager持有fileObjects对本对象的引用,本对象中的outPutStream写入数据后即在内存中
    
            @Override
            public OutputStream openOutputStream() throws IOException {
                outPutStream = new ByteArrayOutputStream();
                return outPutStream;
            }
    
            public byte[] getByteCode() {
                return outPutStream.toByteArray();
            }
        }
    
    

    编译:

        public void compile(ProxyClass proxyClass) {
            // 获取jdk提供的Java编译器
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            if (compiler == null) {
                throw new RuntimeException("Can not get javax.tools.JavaCompiler, check whether \"tools.jar\" is in the environment variable CLASSPATH \n" +
                        "Visit https://jfinal.com/doc/4-8 for details \n");
            }
    
            // 收集诊断信息列表
            DiagnosticCollector<JavaFileObject> collector = new DiagnosticCollector<>();
    
            // 创建Java文件管理者
            try (MyJavaFileManager javaFileManager = new MyJavaFileManager(compiler.getStandardFileManager(collector, null, null))) {
    
                // 构建一个Java源文件
                MyJavaFileObject javaFileObject = new MyJavaFileObject(proxyClass.getName(), proxyClass.getSourceCode());
    
                // 进行编译
                Boolean result = compiler.getTask(null, javaFileManager, collector, getOptions(), null, Arrays.asList(javaFileObject)).call();
                // 错出提示
                outputCompileError(result, collector);
    
                Map<String, byte[]> ret = new HashMap<>();
                // 从Java文件管理者中的引用fileObjects中取出 编译好的字节码
                for (Entry<String, MyJavaFileObject> e : javaFileManager.fileObjects.entrySet()) {
                    ret.put(e.getKey(), e.getValue().getByteCode());
                }
    
                // 设置到包装类中
                proxyClass.setByteCode(ret);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    

    ProxyClassLoader:类加载器,自定义的目的在于方便从内存中加载编译后的字节码

        // 当调用父类的loadClass()方法就会触发查找
        // 继承ClassLoader后重写此方法,去哪里找字节码数组
        // 这里从byteCodeMap中找到对应的字节码数组然后通过defineClass将字节码数组转为Class实例
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] bytes = byteCodeMap.get(name);
            if (bytes != null) {
                Class<?> ret = defineClass(name, bytes, 0, bytes.length);
                // 转换完成后,删掉完成的字节数组
                byteCodeMap.remove(name);
                return ret;
            }
            return super.findClass(name);
        }
    

    示例:

    public class User {
        private String name;
        private int age;
    
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        //这个方法需要代理
        @Before(MyInterceptor.class)
        public void showName() {
            System.out.println("我的名字是:"+name);
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public User() {
        }
    }
    
    public class MyInterceptor implements Interceptor {
        @Override
        public void intercept(Invocation inv) {
            System.out.println("拦截方法:"+inv.getMethodName());
            inv.invoke();
        }
    }
    
    public static void main(String[] args) {
        User s = com.alienjun.proxy.Proxy.get(User.class);
        s.setName("小明");
        s.showName();
    }
    
    输出:
    拦截方法:showName
    我的名字是:小明
    

    此设计非常巧妙,这样就不需要cglib 、asm和jdk动态代理机制了。

    相关文章

      网友评论

          本文标题:JFinal的Proxy实现原理

          本文链接:https://www.haomeiwen.com/subject/dpyiyhtx.html