动态代理

Java 中,实现动态代理的方式主要有两种方式:JDK 动态代理和 CGLIB 动态代理,这两种代理方式在 Java 实现代理模式时最为常见。

代理模式的一般 UML 图如下:

proxy-pattern.png

客户端在调用 Subject 类型的类时,将会将这个请求转发到 Proxy 类中,在 Proxy 类中再调用 ReadlSubject 类中的具体实现,在 Proxy 类中就可以在调用这些方法时自定义一些额外的行为

代理模式的实现

首先,按照上文的 UML 类图关系,首先定义一个 Subject 接口:

// 自定义的 Subject 接口,接口定义行为 
public interface MineSubject {
    String getMessage();
}

定义一个具体的实现类 RealMineSubject

public class RealMineSubject implements MineSubject {
    public String getMessage() {
        return "This is RealMineSubject Message";
    }
}

定义具体的代理类 MineSubjectProxy

public class MineSubjectProxy implements MineSubject {
    private final MineSubject mineSubject; // 接口定义行为,因此只需要具有这种行为的具体实现类即可

    public MineSubjectProxy(MineSubject mineSubject) {
        this.mineSubject = mineSubject;
    }

    public String getMessage() {
        before();
        String message = mineSubject.getMessage();
        System.out.println("Get Message: " + message);
        after();

        return message;
    }

    private void before() {
        System.out.println("Before Method invoke.....");
    }

    private void after() {
        System.out.println("After Method invoke.....");
    }
}

现在在客户端(现在就是 main 方法所在的类),调用相关的接口方法:

public class MainApplication {
    public static void main(String[] args) {
        MineSubject realSubject = new RealMineSubject();
        MineSubject mineSubject = new MineSubjectProxy(realSubject);
        mineSubject.getMessage();
    }
}

对应的输出结果如下:

2021-11-13 10-53-00 的屏幕截图.png

以上,就是一个简单的静态代理的实现,静态代理的缺点很明显,每次都需要重新再去定义相关的 Proxy 类,在实际使用中将会产生大量的代理类,导致维护起来特别麻烦,为此,引入了动态代理的方式来简化这些任务。

JDK 动态代理

基本使用

依旧使用上文的静态代理的例子,来说明 JDK 动态代理的使用

现在将 Proxy 换成 JDK 的动态代理,首先需要定义一个实现 java.lang.reflect.InvocationHandler 接口的类,来定义一些在方法调用时要采取的行为:

public class JdkDynamicProxy implements InvocationHandler {
    private final MineSubject mineSubject;

    public JdkDynamicProxy(MineSubject mineSubject) {
        this.mineSubject = mineSubject;
    }

    /**
    * @param proxy: 表示当前代理执行 method 的代理对象,这个对象由 JDK 来管理
    * @param method: 当前 proxy 代理的要执行的方法
    * @param args: 执行的方法的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        // invoke 需要两个参数,一个是具体的实现类,一个是方法的参数
        Object result = method.invoke(mineSubject, args);
        System.out.println("Get Result: " + result.toString());
        after();
        return result;
    }

    private void before() {
        System.out.println("Before Method invoke.....");
    }

    private void after() {
        System.out.println("After Method invoke.....");
    }
}

调用时通过 java.lang.reflect.Proxy 的静态工厂方法来创建对应的代理对象,这个代理对象与上文的 MineSubjectProxy 类是想对应的,具体的调用方式如下:

public static void main(String[] args) {
    // 具体的实现类依旧是需要的
    RealMineSubject realMineSubject = new RealMineSubject();
    JdkDynamicProxy proxy = new JdkDynamicProxy(realMineSubject);
    // 获取实现类的类加载器,这是为了在实例化 Proxy 对象时使用的类加载器
    ClassLoader loader = realMineSubject.getClass().getClassLoader();
    
    /**
    * 生成代理类对象
    */
    MineSubject subject = (MineSubject) Proxy.newProxyInstance(loader, new Class[]{MineSubject.class}, proxy);

    // 可以直接使用了。。。
    subject.getMessage();
}

是不是看起来似乎 JDK 的动态代理并没有减少工作量,反而需要定义一些额外的实现类 ?实际上,通过上文的 JdkDynamicProxy 类,能够对传入的不同接口和不同的具体实现类,能够更加富有弹性地对不同的实现类进行代理,因此相比较上文提到的静态代理,还是要减少了工作量

实现原理

主要的实现在生成 Proxy 实例对象中,具体的源代码如下:

public static Object newProxyInstance(
    ClassLoader loader, // 类加载器
    Class<?>[] interfaces, // 要代理的接口列表
    InvocationHandler h // 实际的处理对象,通过调用 invoke 方法来实现
) {
    // 省略一部分代码
    
    // 通过传入的接口得到一个代理类的构造函数对象
    Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
    
    // 通过得到的构造函数和传入的 InvocationHandler 对象,生成一个新的 Proxy 实例
    return newProxyInstance(caller, cons, h);
}

获取代理类的构造函数

  • 获取代理类的构造函数之前的准备

    具体地,获取代理接口的构造函数的源代码如下:

    private static Constructor<?> getProxyConstructor(
        Class<?> caller,
        ClassLoader loader,
        Class<?>... interfaces
    ){
        // optimization for single interface
        if (interfaces.length == 1) {
            Class<?> intf = interfaces[0];
            
            // 核心代码,通过构建者模式的方式来创建对应的 Constructor 对象
            return proxyCache.sub(intf).computeIfAbsent(
                loader,
                (ld, clv) -> new ProxyBuilder(ld, clv.key()).build()
            );
        }
        
        // 省略一部分当传入的 interface 列表长度大于 1 时的额外处理代码,处理逻辑与单个的 interface 类似
    }
    

    创建 ProxyBuilder 的源代码如下:

    // 当传入的 interface 的数量为 1 时,将会将其包装成一个列表在进入此方法
    ProxyBuilder(ClassLoader loader, List<Class<?>> interfaces) {
        //省略一部分条件检查的代码
        
        // 这个方法的核心部分,主要的任务是获取 interfaces 的所有非静态引用类型:包括返回类型、参数类型、异常类型
        Set<Class<?>> refTypes = referencedTypes(loader, interfaces);
    
        this.interfaces = interfaces;
        this.module = mapToModule(loader, interfaces, refTypes);
        assert getLoader(module) == loader;
    }
    

    获取引用类型的源代码:

    private static Set<Class<?>> referencedTypes(
        ClassLoader loader,
        List<Class<?>> interfaces
    ) {
        var types = new HashSet<Class<?>>();
        // 遍历每个 Interface,获取使用到的类型
        for (var intf : interfaces) {
            for (Method m : intf.getMethods()) {
                // 注意,这里的引用类型不包括基本数据类型,如 int、long 等
                if (!Modifier.isStatic(m.getModifiers())) {
                    addElementType(types, m.getReturnType()); // 返回类型
                    addElementTypes(types, m.getSharedParameterTypes()); // 方法参数类型
                    addElementTypes(types, m.getSharedExceptionTypes()); // 抛出的异常类型
                }
            }
        }
        return types;
    }
    
  • 正式构建 Proxy 的构造函数对象

    ProxyBuilder 对象的 build() 方法如下:

    Constructor<?> build() {
        // 定义 Proxy 类,重点代码。。。。
        Class<?> proxyClass = defineProxyClass(module, interfaces);
        final Constructor<?> cons;
        try {
            // constructorParams = InvocationHandler.class
            cons = proxyClass.getConstructor(constructorParams);
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
        
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                cons.setAccessible(true);
                return null;
            }
        });
        return cons;
    }
    

    defineProxyClass(moudle, interfaces) 对应的源代码:

    private static Class<?> defineProxyClass(Module m, List<Class<?>> interfaces) {
        String proxyPkg = null;     // package to define proxy class in
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
    
        /**
        * 记录所有的非 public 代理接口的包,这些接口的代理类将会被定义在这些包下面
        */
        for (Class<?> intf : interfaces) {
            int flags = intf.getModifiers();
            if (!Modifier.isPublic(flags)) {
                accessFlags = Modifier.FINAL;  // non-public, final
                String pkg = intf.getPackageName();
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
    
        // 完善要创建的类的包名
        if (proxyPkg == null) {
            // 默认的包名:com.sun.proxy'.模块名'(如果存在)
            proxyPkg = m.isNamed() ? PROXY_PACKAGE_PREFIX + "." + m.getName()
                : PROXY_PACKAGE_PREFIX;
        } else if (proxyPkg.isEmpty() && m.isNamed()) {
            throw new IllegalArgumentException(
                "Unnamed package cannot be added to " + m);
        }	
    
        if (m.isNamed()) {
            if (!m.getDescriptor().packages().contains(proxyPkg)) {
                throw new InternalError(proxyPkg + " not exist in " + m.getName());
            }
        }
    
        /*
        * 为新定义的 Proxy 类定义一个全限定名(上文已经完善了包名,因此这里定义的类名将会是全限定名)
        * 生成的简单类名为 Proxy + 序列号,这个序列号是通过全局的原子整型增加来得到的
        */
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg.isEmpty()
            ? proxyClassNamePrefix + num
            : proxyPkg + "." + proxyClassNamePrefix + num;
    
        ClassLoader loader = getLoader(m); // 获取当前模块下的类加载器
        trace(proxyName, m, loader, interfaces);
    
        /**
        * 这里会产生对应的 Proxy 类的字节码,涉及到 .class 文件的一系列内容,知道这个过程即可
        */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces.toArray(EMPTY_CLASS_ARRAY), accessFlags);
        
        // 将得到的 Proxy 类的字节码加载到 JVM 中,得到这个 Proxy 类的类对象
        Class<?> pc = UNSAFE.defineClass(proxyName, proxyClassFile,
                                         0, proxyClassFile.length,
                                         loader, null);
        reverseProxyCache.sub(pc).putIfAbsent(loader, Boolean.TRUE);
        // 省略一部分一场捕获代码
    
        return pc;
    }
    

    至此,已经得到了生成的 Proxy 的 Class 对象,再回到 ProxyBuilderbuild() 方法:

    Constructor<?> build() {
        // 定义 Proxy 类,重点代码。。。。
        Class<?> proxyClass = defineProxyClass(module, interfaces);
        final Constructor<?> cons;
        try {
            // 通过反射的方式,加入对应的参数类型来找到对应的构造函数
            cons = proxyClass.getConstructor(constructorParams); 
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
        // 修改定义的 Proxy 的访问权限,将它定义为是可以访问的,这样在别的地方就能够实例化一个新的 Proxy 对象
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                cons.setAccessible(true);
                return null;
            }
        });
        
        return cons;
    }
    

    获取 Proxy 对象的构造函数完成

通过构造函数获取实例对象

这一步比较简单,由于之前已经通过反射设置了 Proxy 类的构造函数为可访问的,因此,很容易通过反射的方式对构造函数进行调用以得到对应的实例对象

private static Object newProxyInstance(
    Class<?> caller, // null if no SecurityManager
    Constructor<?> cons,
    InvocationHandler h
) {
    // 省略一部分许可检查和异常捕获的代码
    
    return cons.newInstance(new Object[]{h}); // 创建实例对象的代码
}

CGLIB 动态代理

使用 JDK 动态代理的一个特点是代理时必须存在相对应的要代理的接口(即必须存在实现类的父接口),如果想取消这个限制,可以考虑使用 CGLIB 的动态代理

基本使用

由于 CGLIB 是一个第三方库,因此使用时需要首先引入这个库:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

现在,定义一个实体类 RealMine

public class RealMine {
    public String sayMessage() {
        return "This is RealMine Message";
    }
}

通过 CGLIB 可以直接对这个类采取进行相关的代理方法,再次之前,需要定义一个实现了 net.sf.cglib.proxy.MethodInterceptor 接口的具体类,用于定义一些相关的行为

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    /**
     * @param o : 对应 UML 图中的 RealSubject 对象
     * @param method : 代理的方法
     * @param objects : 传入代理代理方法的参数
     * @param methodProxy : 实际代理对象
     */
    public Object intercept(
        Object o,
        Method method,
        Object[] objects,
        MethodProxy methodProxy
    ) throws Throwable {
        before();
        Object message = methodProxy.invokeSuper(o, objects);
        System.out.println("Get Message: " + message.toString());
        after();
        return null;
    }

    private void before() {
        System.out.println("Before Method invoke.....");
    }

    private void after() {
        System.out.println("After Method invoke.....");
    }
}

客户端调用:

Enhancer enhancer = new Enhancer(); // 在代理时会生成一个子类
enhancer.setSuperclass(RealMine.class); // 设置这个代理对象的父类
enhancer.setCallback(new CglibProxy()); // 代理对象会采取的行为

RealMine realMine = (RealMine) enhancer.create(); // 创建实际代理对象
realMine.sayMessage(); // 调用这个代理对象的方法

得到的输出如下:

2021-11-13 17-14-42 的屏幕截图.png

因为 CGLIB 是通过反射的方式直接获取相关的属性,而这种方式自 JDK 9 开始就已经被禁止使用了,因此会看到以上的警告

实现原理

CGLIB 是通过 ASM 字节码处理框架在运行期间扩展 Java 类与实现接口,相比较与 JDK 的动态代理,CGLIB 不需要一个公共的父接口即可对现有的类进行扩展来实现代理,更加简单方便。但是它也有以下的一些缺点:

  • 由于是通过反射的方式来直接获取属性的,而这种方式在 JDK 9 开始就已经被禁止了,因此会看到上文中出现的警告信息
  • CGLIB 对于类的扩展也是有限制的,对于 final 修饰的类和方法是无法完成代理的(因为 final 修饰的类和方法无法被继承)
  • CGLIB 属于第三方的依赖包,因此在使用时对于环境就有一定的限制

具体的实现原理与 JDK 的动态代理类似,因此在此不在这里详细介绍

两者的比较

  • JDK 动态代理利用拦截器(拦截器必须实现 InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用 InvokerHandler 来处理。只能对实现了接口的类生成代理
  • CGLIB 利用 ASM 字节码处理框架,将代理对象类的 Class 文件加载进来,通过修改字节码生成子类的方式来进行处理。主要是对指定的类生成一个子类,覆盖其中的方法并且实现增强,但是由于采用的方法是继承的方式,因此对于 final 修饰的类和方法,无法被继承并增强
  • 对于运行效率,这两者之间没有太大的区别,但是由于 JDK 的动态代理在执行时首先会调用 Proxy 类的 invoke 方法,由于这一次的调用可能会降低运行效率,但是这点消耗是完全可以忽略的。
posted @ 2021-11-13 18:00  FatalFlower  阅读(604)  评论(0编辑  收藏  举报