如何手写一个java动态代理

2021-08-21  本文已影响0人  心尘宁静

开门见山,要整下Java语言的动态代理,按照国际惯例,得先来介绍下背景。
为什么需要代理呢? 举个栗子:

在我们写的所有修改数据方法操作里面,基本上都需要事务支持。这个时候正常实现要怎样呢?
首先在方法开始处添加开启事务代码,方法结束的时候添加提交事务代码; 如下面的伪代码:

public void add(Object obj) {
    //----------开启事务代码----------;
    xxxDao.add(msg);
    //----------判断提交或回滚事务代码----------";
}

在每一个需要事务的方法里面都需要有上面的开始事务和提交事务代码。再比如用户如果还需要知道是谁以及在什么时间操作了这方法,那么我就需要做方法的操作日志记录。
正常的又该怎么做呢,基本上就如下伪代码:

public void add(Object obj) {
    //----------开启事务代码----------;
    //----------记录操作日志代码----------;
    xxxDao.add(msg);
   //----------判断提交或回滚事务代码----------";
}

上面的代码可以很清楚的看到,我们需要在每个需要事务或者操作日志记录的方法里面加上一堆的与核心业务无关的流程代码,这样就是重复劳动了。Java程序员是很懒的只要有重复的事情那都不会干,毕竟Java的抽象继承多态是干啥的,不就是为了消除重复,让方法能够复用,少敲几行代码多喝点咖啡。

既然有了问题,我们就得解决问题,要解决问题我们就得使用代理,代理就是在原有的方法上进行增强。说到代理,先给例子:

/**
 * 业务接口
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 12:30
 */
 public interface IUserService {
    void add(String msg);

    void add();

    String get();
 }

/**
 * 业务接口实现类
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 12:30
 */
public class UserService implements IUserService{
    @Override
    public void add(String msg) {
        System.out.println("=============保存============="+msg);
    }

    @Override
    public void add() {
        System.out.println("=============保存=============");
    }

    @Override
    public String get() {
        System.out.println("=============获取=============");
        return "捉到啦";
    }
}

/**
 * 静态代理
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 12:33
 */
public class UserServiceProxy implements IUserService{
    /**代理目标类*/
    private IUserService target;

    public UserServiceProxy(IUserService userService){
        this.target = userService;
    }

    @Override
    public void add(String msg) {
        System.out.println("----------事务开始----------");
        target.add(msg);
        System.out.println("----------事务结束----------");
    }

    @Override
    public void add() {
        System.out.println("***********事务开始***********");
        target.add();
        System.out.println("***********事务结束***********");
    }

    @Override
    public String get() {
        System.out.println("############get事务开始############");
        String result = target.get();
        System.out.println("############get事务结束############");
        return result;
    }
}

/**
 * 代理类测试
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 12:31
 */
public class Main {
    public static void main(String[] args){
        IUserService staticProxy = new UserServiceProxy(new UserService());
        staticProxy.add("静态代理");
    }
}

如上代码所示,就只需要在主方法调用端那里做处理去统一调用代理类,便可以在不改原方法任何代码的情况下进行方法的增强,而原方法就只需要关心核心业务的开发就可以,不用操心其他的事。而这就是一个很普通的静态代理实现。

那么上面的方法实现存在什么样的问题呢?上面只有一个 IUserService 接口,但是现实业务中肯定还有很多其他的 IOrderService、IFinanceService 等几十上百个之类的接口,那么这时候要进行统一的增强,我就需要针对这几十上百个接口都来实现代理方法。那这种苦差事谁愿意干呢,反正我是不愿意的,既然不愿意那就要想办法去解决。

那么我们能不能用一个公共的代理类来代替所有的具体代理类呢,这样就只需调用公共代理类然后传入具体被代理类就可以进行业务代理了。首先仔细看上面的静态代理实现结构,要实现代理,我们必须要有两大核心类:一个代理类和一个被代理类,代理类还有一个需要用户自定义实现的代理增强方法。如上代码 UserServiceProxy 是代理类,UserService 是被代理类。 被代理类这个是现有的,我们现在所需要的就是代理类,使之能够代理任意的类,同时我们还得提供一个接口,让用户能够实现自己的代理方法。好了,说了理论,接下来就要说具体实现。

首先我们得提供一个让用户实现业务代理方法的接口,如下:

package prox;
/**
 * 自定义动态代理方法规范接口
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 15:38
 */
public interface MyInvocationHandler {
    /**
     * 代理增强方法接口
     * 此处方法及参数设计参照了JDK的设计 和 JDK 的 InvocationHandler接口设计一致
     * 其中第一个参数 obj 代理对象这个参数 在这里其实没有什么用处 完全可以省略掉。
     * 而官方设计该参数的目的及作用如下:
     * 1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
     * 2. 可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
     * @param obj 代理对象
     * @param method 代理方法
     * @param args 代理参数
     * @return 方法调用结果
     * @throws Throwable 抛出异常
     */
    Object invoke(Object obj, Method method,Object[] args) throws Throwable;
}

然后我再来一个业务代理的接口实现:

package prox;

import java.lang.reflect.Method;

/**
 * 自定义动态代理业务方法实现
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 17:28
 */
public class ClassProxyFactory<T> implements MyInvocationHandler{
    private T target;

    public ClassProxyFactory(T t) {
        this.target = t;
    }

    public T getProxyInstance() {
        //此处采用自定义类加载器
        MyClassLoader classLoader = new MyClassLoader();
        Class[] classes;
        /**判断目标被代理对象是否有接口,若无接口则直接用代理对象*/
        if(target.getClass().getInterfaces().length>0){
            classes = target.getClass().getInterfaces();
        }else{
            classes = new Class[]{target.getClass()};
        }
        return (T) MyProxy.newProxyInstance(classLoader, classes,this);
    }

    /**
     * 代理增强方法具体实现
     * 
     * 此处方法及参数设计参照了JDK的设计 和 JDK 的 InvocationHandler接口设计一致
     * 其中第一个参数 obj 代理对象这个参数 在这里其实没有什么用处 完全可以省略掉。
     * 而官方设计该参数的目的及作用如下:
     * 1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
     * 2. 可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
     * @param obj 代理对象
     * @param method 代理方法
     * @param args 代理参数
     * @return 方法返回结果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
        System.out.println("**********开始事务**********");
        Object value = method.invoke(target,args);
        System.out.println("调用目标方法返回:"+ value);
        System.out.println("**********结束事务**********");
        return value;
    }
}

实现了业务代理方法,这个时候就需要进行构建代理对象了。由于我们并不知道需要代理的对象是谁,只有在运行的时候才能确定,所以这个时候就需要利用反射机制在运行期间进行动态构建代理对象。

思路如下:以字符串的形式拼凑出一个 extends 或 implement 被代理对象或接口的Java类 的这个代理对象类。同时该类还需要有 MyInvocationHandler 这个接口的成员变量,这个接口实例就是用来执行用户自定义的代理方法。将该类的构造方法改为要传入MyInvocationHandler 的有参构造,然后将该代理类的所有方法实现为调用 MyInvocationHandler 接口的 invoke() 方法。

说了这么多,用个例子,假设调用端现在要代理的是实现了 IUserService 接口的 UserService 类,那我们此时要动态构建的代理类就如下所示:

public class Proxy implements IUserService {
    MyInvocationHandler handler;

    public Proxy(MyInvocationHandler handler) {
        this.handler = handler;
    }

    public void add(String var1) {
        try {
            Method var2 = IUserService.class.getMethod("add", String.class);
            this.handler.invoke(this, var2, new Object[]{var1});
        } catch (Throwable var3) {
            var3.printStackTrace();
            throw new RuntimeException(var3.getMessage());
        }
    }

    public void add() {
        try {
            Method var1 = IUserService.class.getMethod("add");
            this.handler.invoke(this, var1, (Object[])null);
        } catch (Throwable var2) {
            var2.printStackTrace();
            throw new RuntimeException(var2.getMessage());
        }
    }

    public String get() {
        try {
            Method var1 = IUserService.class.getMethod("get");
            return (String)this.handler.invoke(this, var1, (Object[])null);
        } catch (Throwable var2) {
            var2.printStackTrace();
            throw new RuntimeException(var2.getMessage());
        }
    }
}

是不是和静态代理类基本一致,唯一的区别就是方法实现内容部分,核心就是下面的语句

this.handler.invoke(this, var2, new Object[]{var1});

每个代理方法内部都是直接调用 MyInvocationHandler 实例的 invoke() 方法,而这个方法具体实现又是交由用户自己去自定义实现的。为什么要这样子呢,因为当前这个代理类是系统运行时动态产生的,对于用户是不可见的,所以代理方法的实现必须得委托出去,让用户有地方可以进行自定义实现。而上面的 implements IUserService 语句和成员方法的定义都是动态决定的,如果要代理的是其他的接口或普通类,那么该对象的成员方法定义就得随之而改变。

既然如此,那么我们该如何动态地构建这么一个类呢?实现代码如下:

package prox;

import javax.tools.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

/**
 * 该类可以理解为方法类 其作用就是为了动态构建 代理类并实例化 的一个入口
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 15:03
 */
public class MyProxy {
    //自增数字
    private static final AtomicLong ATOMIC_LONG = new AtomicLong();
    //私有化构造 因为该类作用就是个方法入口 
    private MyProxy(){}

    /** 以字符串的形式拼凑出一个 实现了 interfaces 接口的代理类  同时该类还需要有 MyInvocationHandler 成员变量,
     * MyInvocationHandler 该接口 就是执行代理的操作的接口
     将该类的构造方法改为要传入handle的有参构造 然后该代理类实现的所有方法实现为 调用 handler接口的 invoke方法
     将上述字符串类编译成class类, 然后利用ClassLoader进行加载
     最后用反射将该class进行实例化 并返回 */
    public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler handler){

        String className = "$Proxy"+ATOMIC_LONG.getAndIncrement();
        try {
            String javaFileName = MyProxy.class.getResource("").getPath() + className + ".java";

            // 以下 2,3,4 步骤目的就是构建一个代理类的class文件   JDK此处就是直接构造成 class 字节码然后保存到文件中。
            // 由于Class文件结构较为复杂 所以此处是先用字符串拼接一个Java文件,
            // 再用JavaCompiler去编译这个java文件,生成一个class文件。
            // 最后通过自定义的MyClassLoader类加载器把这个生成好的class文件加载到内存,得到Class对象。

            // 1.生成源代码字符串
            String javaSrc = generateJavaSrc(className, interfaces);

            // 2.把源代码字符串作为java类文件保存到磁盘
            File javaFile = new File(javaFileName);
            generateJavaFile(javaSrc, javaFile);

            // 3.将磁盘的代理Java类编译成class文件
            compilerJavaFile(javaFile);

            // 4.使用自定义的 classLoad 将刚才编译的代理class加载到jvm中
            Class<?> proxyClass = classLoader.loadClass(className);

            // 5.利用反射 将代理class实例化 并返回该代理对象
            Object obj = proxyClass.getDeclaredConstructor(MyInvocationHandler.class).newInstance(handler);
            if (javaFile.exists()) {
                //删除操作时生成的Java源代码文件
                javaFile.delete();
            }
            return obj;
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 构建Java源码字符串
     * @param className java类名
     * @param interfaces 要继承或实现的类或接口
     * @return String 源码字符串
     */
    public static String generateJavaSrc(String className, Class<?>[] interfaces) {
        // 定义换行符
        String ln = "\r\n";
        String packageName = MyProxy.class.getPackage().getName();
        StringBuffer sb = new StringBuffer();
        //类所在包
        sb.append("package " + packageName + ";" + ln);
        //导包代码
        sb.append("import java.lang.reflect.Method;" + ln);
        //类定义代码
        sb.append("public class " + className);
        //此处判断若是普通类则用继承
        if(!interfaces[0].isInterface()){
            sb.append(" extends ").append(interfaces[0].getName());
        }else{
            sb.append(" implements ");
            sb.append(Arrays.stream(interfaces).map(Class::getName).collect(Collectors.joining(",")));
        }
        sb.append("{" + ln);
        //属性
        sb.append("MyInvocationHandler handler;" + ln);
        // 构造方法
        sb.append("public " + className + "(MyInvocationHandler handler){" + ln);
        sb.append("this.handler = handler;" + ln);
        sb.append("}").append(ln);

        //拼接代理类的方法
        for(Class<?> intface:interfaces){
            for (Method method : intface.getDeclaredMethods()) {
                //方法参数列表
                Class<?>[] parameterTypes = method.getParameterTypes();
                //用于拼接形参列表
                StringBuffer formalArgs = new StringBuffer();
                //用于拼接实参列表
                StringBuffer realArgs = new StringBuffer();
                //用于拼接实参Class类型的字符串
                StringBuffer realArgsClass = new StringBuffer();
                Iterator<Class<?>> iterator = Arrays.stream(parameterTypes).iterator();
                while (iterator.hasNext()){
                    Class<?> parameterType = iterator.next();
                    String argName = "arg" + ATOMIC_LONG.getAndIncrement();
                    formalArgs.append(parameterType.getName()).append(" ").append(argName);
                    realArgs.append(argName);
                    realArgsClass.append(parameterType.getName()).append(".class");
                    if(iterator.hasNext()){
                        formalArgs.append(",");
                        realArgs.append(",");
                        realArgsClass.append(",");
                    }
                }

                //如果参数长度为0,那么传null
                String arg = parameterTypes.length > 0 ? "new Object[]{" +realArgs+"}" : "null";

                //拼接成员方法体代码
                sb.append("    @Override");
                sb.append("    public ").append(method.getReturnType().getName()).append(" ").append(method.getName()).append("(").append(formalArgs).append("){").append(ln);
                sb.append("        try{ " + ln);
                sb.append("            Method method = " + intface.getName() + ".class.getMethod(\"" + method.getName() + "\"");
                //根据参数长度,确定是否添加参数
                if (parameterTypes.length > 0) {
                    sb.append("," + realArgsClass);
                }
                sb.append(");" + ln+"            ");
                //返回值 如果为"void",则生成没有返回值的方法调用
                if (!method.getReturnType().getName().equals("void")) {
                    sb.append("return (" + method.getReturnType().getName() + ")");
                }
                sb.append("handler.invoke(this,method," + arg + ");" + ln);

                //捕获所有异常,转换成RuntimeException异常
                sb.append("        }catch (Throwable e){e.printStackTrace();throw new RuntimeException(e.getMessage());}" + ln);
                sb.append("}"+ln);
            }
        }
        sb.append("}" + ln);

        return sb.toString();
    }

    /**
     * 在磁盘生成.java文件
     * @param javaSrc java源码字符串
     * @param javaFile java文件
     */
    public static void generateJavaFile(String javaSrc, File javaFile) throws IOException {
        try (FileWriter fw = new FileWriter(javaFile)){
            fw.write(javaSrc);
        }
    }
    
    /**
     * 编译.java文件
     */
    public static void compilerJavaFile(File javaFile) {
        //运行时编译器  编译代理类的Java源代码代码->class文件
        JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
        try (StandardJavaFileManager fileManager = javaCompiler.getStandardFileManager(null, null, null)){
            Iterable<? extends JavaFileObject> iterable = fileManager.getJavaFileObjects(javaFile);
            JavaCompiler.CompilationTask task = javaCompiler.getTask(null, fileManager, null, null, null, iterable);
            task.call();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

通过以上代码我们就能动态的构建任意目标类的代理类。基本实现代码注释都很清楚,就不再赘述,相信都能看懂。由于这里是我们自己定义的动态构建,所以加载这动态class文件也得用我们自己的Classload 实现。自定义 ClassLoad 代码如下:

package prox;

import java.io.*;

/**
 * 自定义类加载器
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 17:42
 */
public class MyClassLoader extends ClassLoader {
    private File baseDir;

    public MyClassLoader() {
        String basePath = MyClassLoader.class.getResource("").getPath();
        this.baseDir = new File(basePath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        File file = new File(this.baseDir, name.replace(".", "//") + ".class");
        try (FileInputStream in = new FileInputStream(file); ByteArrayOutputStream out = new ByteArrayOutputStream()){
            in.transferTo(out);
            return defineClass(className, out.toByteArray(), 0, out.size());
        } catch (Exception e) {
            throw new ClassNotFoundException();
        }
    }

}

以上代码就实现了动态代理,最后调用方法如下:

package prox;

/**
 * 代理类测试
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 12:31
 */
public class Main {
    public static void main(String[] args){
        //静态代理
        IUserService staticProxy = new UserServiceProxy(new UserService());
        //自定义接口类动态代理
        IUserService diyProxy = new ClassProxyFactory<>(new UserService()).getProxyInstance();
        //自定义 非接口类动态代理
        NoInterfaceService diyNoProxy = new ClassProxyFactory<>(new NoInterfaceService()).getProxyInstance();

        staticProxy.add("静态代理");
        diyProxy.add("自定义动态代理");
        diyProxy.add();
        diyNoProxy.save("非接口自定义代理");
        System.out.println(diyProxy.get());
    }

}

执行输出结果如下:

----------事务开始----------
=============保存=============静态代理
----------事务结束----------
**********开始事务**********
=============保存=============自定义动态代理
调用目标方法返回:null
**********结束事务**********
**********开始事务**********
=============保存=============
调用目标方法返回:null
**********结束事务**********
**********开始事务**********
=============保存=============非接口自定义代理
调用目标方法返回:null
**********结束事务**********
**********开始事务**********
=============获取=============
调用目标方法返回:捉到啦
**********结束事务**********
捉到啦

上面实现的动态代理既可以代理接口类 也可以代理普通类,上述主方法执行的被代理普通类代码如下:

package prox;

/**
 * 无接口的普通类
 * @author ngcly
 * @version V1.0
 * @since 2021/8/20 23:19
 */
public class NoInterfaceService {
    public void save(String msg) {
        System.out.println("=============保存============="+msg);
    }
}

这样一个动态代理就完整实现了,当然上面的都是自定义实现。而JDK官方也是提供了动态代理,其基本实现原理跟上面没什么太大差别。但是JDK动态代理有个缺陷就是只能针对接口方法进行代理,其原因就是因为JDK生成的代理类都继承了Proxy类,由于Java单继承自然就不能再对普通类进行代理。

通过我上面的自定义实现代码可以发现,我自定义实现的代理是可以对普通类进行动态代理的,原因自然是我没有去继承一个所谓的 Proxy 类,所以我就能够去继承被代理的普通类从而可以实现代理了,当然针对 final 类还是不能代理的,原因是final类是不可继承。

那么从上可知,明明不继承Proxy类也可以实现动态代理,那为什么JDK实现要去继承Proxy类而不去支持普通类的代理呢?只能说一句这可能就是乌龟的屁股 龟腚吧!但是我想这种规定的用意应该就是规范吧。JDK官方认为代理都应该是针对接口进行代理,而对普通类代理这是不规范的。同时普通类存在final等不可继承等因素,而接口则不存在以上问题,所以这也是一种规范,避免一些奇奇怪怪的操作。

由于JDK动态代理不支持对普通类的代理,所以对于普通类动态代理的实现则是由 三方类库 Cglib 库提供,而 Cglib 的实现本质也是基于继承,但是实现方面却有很大的区别,感兴趣的小伙伴可以自己查阅,这里就不多介绍。

通过自己实现一个动态代理,可以从底层理解动态代理的实现原理。举一反三也就知道官方实现的一个大致方式,这样去阅读源码就可以知道官方实现在哪些方面做了优化,以及在哪些方面实现的更加优雅完美,这样对自己的技术方面帮助还是很大的。

上一篇下一篇

猜你喜欢

热点阅读