学习设计模式之动态代理

上一章我们学习了静态代理,举个栗子比如我想在一批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);
    }

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

勿在浮沙筑高台 ——个人浅见,难免有误导之处,麻烦请指出。
原文地址:https://www.cnblogs.com/liufeichn/p/11961651.html