Java静态、动态代理学习

267 阅读3分钟

使用场景,对已有类、或接口的方法进行扩展,比如前后添加日志代码。

比如已有如下接口

public interface IProduct {
    void produce();
}
public class FoodProduct implements IProduct {
    @Override
    public void produce() {
        System.out.println("FoodProduct");
    }
}

方式一 利用继承

public class LogProduct extends FoodProduct {
    @Override
    public void produce() {
        System.out.println("produce before");
        super.produce();
        System.out.println("produce after");
    }
}

方式二 利用聚合,即静态代理

实现被代理类的接口,并引用被代理类,在代理类中,调用被代理类的方法即可

public class LogProxy implements IProduct {

    private IProduct mProduct;
    public LogProxy(IProduct product) {
        this.mProduct = product;
    }

    @Override
    public void produce() {
        System.out.println("produce before");
        mProduct.produce();
        System.out.println("produce after");
    }
}

下面比较下,继承和聚合方式的优缺点

有个需求,需要有2个代理类,分别是日志和时间,但两个代理的先后顺序可以变化。

继承,需要让日志和时间组件相互继承,来保证代码的先后执行顺序,即需要无限增加继承类。

public class TimeProduct extends LogProduct {
    @Override
    public void produce() {
        long start = System.currentTimeMillis();
        super.produce();
        long stop = System.currentTimeMillis();
        System.out.println("produce time " + (stop - start));
    }
}

静态代理,只需要将接口作为构造参数即可,再实现时可以任意更改顺序。

public class TimeProxy implements IProduct {

    private IProduct mProduct;
    public TimeProxy(IProduct product) {
        this.mProduct = product;
    }

    @Override
    public void produce() {
        long start = System.currentTimeMillis();
        mProduct.produce();
        long stop = System.currentTimeMillis();
        System.out.println("produce time " + (stop - start));
    }
}

看下调用方式

public class ProductClient {
    public static void main(String[] args) {
        IProduct logProxy = new LogProxy(new FoodProduct());
        IProduct timeProxy = new TimeProxy(logProxy);
        timeProxy.produce();
        // 也可以更改LogProxy和TimeProxy顺序
        // IProduct timeProxy = new TimeProxy(new FoodProduct());
        // IProduct logProxy = new LogProxy(timeProxy);
        // timeProxy.produce();
    }
}

方式三 动态代理

为什么需要有动态代理,由上面的静态代理,只能完成特定接口的,特定方法,如果需要对其他接口、或方法做代理,则需要增加新的静态代理类,会导致代理类无线膨胀,不利于代码的通用和扩展。

比如还有如下接口

public interface IAnimal {
    void eat();
}
public class DogAnimal implements IAnimal {
    @Override
    public void eat() {
        System.out.println("Dog eats");
    }
}

如果使用静态代理,则需要新增日志代理类

public class LogAnimalProxy implements IAnimal {

    private IAnimal mAnimal;
    public LogAnimalProxy(IAnimal animal) {
        this.mAnimal = animal;
    }
    @Override
    public void eat() {
        System.out.println("eat before");
        mAnimal.eat();
        System.out.println("produce after");
    }
}

动态代理,要求所有的被代理类,必须实现了一个接口。

使用Java动态代理代码如下:

通过定义InvocationHandler的子类对象,来实现对被代理类的统一处理,如对所有代理类添加日志等。

public class Main {
    public static void main(String[] args) {
        IProduct product = new Product();
        IProduct productProxy = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader(),
                IProduct.class, new ProductInvocationHandler(product));
        productProxy.produce();
    }
}

四、Java动态代理的实现流程

会用到Java的反射机制,参考 juejin.cn/post/684490…

第一步:动态生成代理类的Java代码

(1) 使用字符串编写Java代码结构。

(2) 创建.java文件

(3) 将字符串代码写入到.java文件中

第二步:编译代理类的Java代码

(1) 设置编译后的.class文件目录

(2) 利用JavaCompiler编译

第三步:将.class加载到ClassLoader中,并创建代理对象

(1) 使用ClassLoader或URLClassLoader加载.class

(2) 使用getConstructor()方法,获取构造函数对象

(3) 利用Constructor对象,使用newInstance()创建代理对象

下面看具体代码:

public class Proxy {

    private static final String rt = "\r\n";
    private static final String proxyNm = "$Proxy1";

    /**
     *
     * @param classLoader 被代理类的ClassLoader
     * @param interfaces 被代理类实现的接口
     * @param h 在生成的代理类中回调该接口的invoke方法,方便使用者,实现自己的代理方法。
     * @return
     */
    public static Object newProxyInstance(ClassLoader classLoader, Class<?> interfaces, InvocationHandler h) {

        /**********第一步*********************
         * 动态生成代理类的Java代码
         * (1) 使用字符串编写Java代码结构。
         * (2) 创建.java文件
         * (3) 将字符串代码写入到.java文件中
         ************************************/

        // 接口里的方法默认都是public abstract,所以继承时,只需要使用public即可
        // TODO 方法返回类型和参数类型的设置
        String methodStr = "";
        Method[] methods = interfaces.getMethods();
        for (Method m : methods) {
            methodStr = "@Override" + rt
                    + "public " + m.getReturnType().toGenericString() + " " + m.getName() + "() {" + rt
                    + "try {" + rt
                    + "java.lang.reflect.Method md = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt
                    + "h.invoke(this, md, null);" + rt
                    + "} catch(Exception e) {" + rt
                    + "} catch(Throwable e) {" + rt
                    + "}" + rt
                    + "}" + rt;
        }

        String classStr =
                "package com.outman.example.proxy;" + rt
                        + "public class " + proxyNm + " implements " + interfaces.getName() + " {" + rt
                        + "com.outman.example.proxy.InvocationHandler h;" + rt
                        + "public " + proxyNm + "(InvocationHandler h) {" + rt
                        + "this.h = h;" + rt
                        + "}" + rt
                        + methodStr
                        + "}";

        System.out.println("begin generate java file");

        String srcRoot = System.getProperty("user.dir");
        System.out.print(srcRoot);

        String srcPath = srcRoot + "/src/com/outman/example/proxy/" + proxyNm + ".java";
        File file = new File(srcPath);
        FileWriter writer = null;
        try {
            writer = new FileWriter(file);
            writer.write(classStr);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println("finish generate java file");


        /**********第二步*********************
         * 编译代理类的Java代码
         * (1) 设置编译后的.class文件目录
         * (2) 利用JavaCompiler编译
         ************************************/


        System.out.println("begin compile java file");

        String classOut = srcRoot + "/out/production/JavaTest/";

        StandardJavaFileManager fileManager = null;
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            System.out.println(compiler);

            fileManager = compiler.getStandardFileManager(null, null, null);

            JavaFileManager.Location oLocation = StandardLocation.CLASS_OUTPUT;
            // 设置编译的后的class路径
            fileManager.setLocation(oLocation, Arrays.asList(new File[] { new File(classOut) }));

            Iterable iterable = fileManager.getJavaFileObjects(file);

            JavaCompiler.CompilationTask compilationTask = compiler.getTask(null, fileManager, null, null, null, iterable);
            compilationTask.call();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (fileManager != null) {
                try {
                    fileManager.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        System.out.println("finish compile java file");

        /**********第三步*********************
         * 将.class加载到ClassLoader中,并创建代理对象
         * (1) 使用ClassLoader或URLClassLoader加载.class
         * (2) 使用getConstructor()方法,获取构造函数对象
         * (3) 利用Constructor对象,使用newInstance()创建代理对象
         ************************************/

        try {
            if (classLoader != null) {

                System.out.println("begin load java class");

//                URL[] urls = new URL[] {new URL("file:/" + classOut)};
//                URLClassLoader urlClassLoader = new URLClassLoader(urls);
//                Class clz = urlClassLoader.loadClass("com.outman.example.proxy." + proxyNm);

                Class clz = classLoader.loadClass("com.outman.example.proxy." + proxyNm);

                System.out.println("finish load java class");

                Constructor constructor = clz.getConstructor(InvocationHandler.class);

                return constructor.newInstance(h);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

下面看下自动生成的代理类的代码

/**
 * 生成的代理类
 * 和被代理类实现相同的接口
 * 有一个InvocationHandler对象,用来回调给使用者
 */
public class $Proxy1 implements com.outman.example.proxy.IProduct {

    com.outman.example.proxy.InvocationHandler h;

    public $Proxy1(InvocationHandler h) {
        this.h = h;
    }

    /**
     * 实现被代理类接口的方法
     */
    @Override
    public void produce() {
        try {
            // 获取到被代理类的接口的方法对象
            java.lang.reflect.Method md = com.outman.example.proxy.IProduct.class.getMethod("produce");
            // 利用传进来的InvocationHandler对象,回调给使用者,代理的方法对象。
            // 使用者可以利用回调中的Method对象反射调用该方法
            h.invoke(this, md, null);
        } catch(Exception e) {
        } catch(Throwable e) {
        }
    }
}