• 学习设计模式之动态代理


    上一章我们学习了静态代理,举个栗子比如我想在一批Controller里进行入参和出参的打印。那么静态代理就会创建若干个Controller的代理类。
    再比如我除了要出参入参打印,我还需要在出参入参之后在打印出每个函数的耗时,那么就需要重新在每个函数里在加上耗时的日志打印。动态代理则会帮我们省了很多代码量的编写。

    下面介绍动态代理,动态代理是JDK自有的功能。
    Java 动态代理类位于 Java.lang.reflect 包下,一般主要涉及到以下两个类:

    1. Interface InvocationHandler:该接口中仅定义了一个方法 Object:invoke(Object obj,Method method,Object[] args)。在实际使用时,第一个参数 obj 一般是指代理类,method 是被代理的方法,args 为该方法的参数数组。这个抽象方法在代理类中动态实现。
    2. Proxy:该类即为动态代理类,Static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用。

    代码实现

    public interface Subject {
        void request();
    }
    
    public class RealSubject implements Subject {
        @Override
        public void request() {
            System.out.println("真实的请求!");
        }
    }
    
    public class ProxyHandler implements InvocationHandler {
        private Object obj;
    
        public ProxyHandler(Object obj) {
            this.obj = obj;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("执行前的打印测试!");
            return method.invoke(this.obj, args);
        }
    }
    
    public class TestMain {
        public static void main(String[] args) {
            final Subject realSubject = new RealSubject();
            Subject realSubjectProxy = (Subject) Proxy
                    .newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[] { Subject.class },
                            new ProxyHandler(realSubject));
            realSubjectProxy.request();
        }
    }
    

    运行结果:
    执行前的打印测试!
    真实的请求!

    Process finished with exit code 0

    主要代码是 ProxyHandler 中的这个代理类的作用是可以代理任何类,因为它被传入的对象是Object,而不再是具体的类。

    代理机制及特点

    1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
    2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
    3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
    4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

    第三点也就说明了为什么使用代理类的任意方法都能进入 InvocationHandler 中的 public Object invoke(Object proxy, Method method, Object[] args); 函数中。因为在生成代理类的时候会将 public static Object newProxyInstance(ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h); 中的 h 参数通过反射给代理类赋值,在代理类每个函数中都 h.invoke(this, method, args); 这样子的调用。

    下面是极其简易版的实现,只做参考。写的不好有不对的地方或建议可评论谢谢!

    public static Object newProxyInstance(Class<?>[] interfaces, InvocationHandler h) throws Exception {
            // String 也是方便才写,用 StringBuilder 效率更高
            // interfaces[0].getSimpleName() 只是为了省事其实是一条一条 for 得出
            String str = "";
            str += "public Proxy01 implements " + interfaces[0].getSimpleName() + " {
    ";
            str += "    private InvocationHandler h;
    ";
            str += "    public Proxy01(InvocationHandler h) {
    ";
            str += "        this.h = h;
    ";
            str += "    }";
            for (Method m : interfaces[0].getMethods()) {
                str += "public " + m.getReturnType() + " " + m.getName() + "(参数) {
    ";
                // 下面会抛出异常,为了简单的示范就省略了
                str += "Method m = " + interfaces[0].getSimpleName() + ".getMethod(" + """ + m.getName() + """ + ");";
                str += "h.invoke(this, m);";
                str += "}";
            }
            str += "}";
            // 生成对应的 java 文件
            File file = new File("路径");
            file.createNewFile();
            BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
            bos.write(str.getBytes());
            bos.flush();
            bos.close();
            // 既然生成了 java 文件,那么下一步肯定就是编译成 class 了
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = fileManager.getJavaFileObjects("路径\Proxy01.java");
            JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, iterable);
            task.call();
            fileManager.close();
            // 编译完成那么下一步就是加载到内存了
            URL[] urls = new URL[]{new URL("路径")};
            URLClassLoader classLoader = new URLClassLoader(urls);
            Class<?> aClass = classLoader.loadClass("Proxy01.class");
            Constructor<?> constructor = aClass.getConstructor();
            // 重点就在最后一行
            return constructor.newInstance(h);
        }
    

    优点
    减少代理类的数量,相对降低了应用程序的复杂度,灵活性更高。
    缺点
    目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象。(其实不实现接口也可以)只是看起来很傻。

    勿在浮沙筑高台 ——个人浅见,难免有误导之处,麻烦请指出。
  • 相关阅读:
    OLTP和OLAP区别
    JAVA实现文件树
    商务智能及其实现模型
    Java打印程序设计
    J2EE的昨天,今天,明天
    常用jar包之commonslang使用
    CRM与ERP整合的六个切入点
    常用jar包之commonscollection使用
    软件安全技术
    常用jar包之commonsbeanutils使用
  • 原文地址:https://www.cnblogs.com/liufeichn/p/11961651.html
Copyright © 2020-2023  润新知