Java: JDK11前的动态代理方法

Java: JDK11前的动态代理方法

概述

“代理模式” 是设计模式的一种,他的主要作用是处理对某一个对象或者方法的使用,例如权限的控制,日志的输出等,这些操作总是位于其他的操作的 “周围”。

以权限控制为例,每当调用需要权限的操作时,都需要首先验证用户的身份和权限,可能有很多的操作都需要,在通常情况下,这将会导致数量庞大的重复代码,这些重复代码的目的是相同的,即判定用户的“权限”。

代理模式是这一问题的很好的解决方案。

代理模式能够在目标对象之外包裹一层新的逻辑,所有对目标对象的访问必须经过包裹在外的这一个“ 逻辑层 ” ,这就意味着刚刚所说的,位于其他操作的“ 周围 ” 的逻辑,如权限控制,就完全可以在这一个包裹在外部的 “ 逻辑层 ” 中完成,所有需要这个功能的对象,都可以被包裹一层这样的逻辑来完成权限鉴定。

代理模式一般有两种,一种是通过硬编码写在类的结构里面的,叫做静态代理,另外一种是在需要的时候生成的,生成一个临时的类,临时的类中包含代理层的逻辑。

在Java中,有以下几个完成动态代理功能的类库:

  1. Java的接口代理
  2. CGLib的子类代理
  3. Javaassist字节码手动代理
  4. AspectJ代理

JDK代理

JDK代理是java自带的代理功能,它只能代理Java接口,通过实现InvocationHandler来为接口提供具体的功能。

接口的方法被调用的时候,就会触发InvocationHandler里面的invoke方法,这里面有被代理的Object,以及被调用的Method和调用Method时提供的参数。

public interface InvocationHandler {
    
    // 三个参数,第一个是代理对象,第二个是被调用的方法, 第三个是参数列表。
    // 这里通过被调用的方法或者参数等来判断应该怎么做,调用什么或者返回什么。
    // 实际上,通过额外添加的逻辑修改原始的方法的行为这种做法,也叫面向切面编程,例如JDK代理中这个invoke方法:
    // 对于生成的代理对象,它的所有的方法调用都会汇集到invoke方法里面,在invoke中就能根据一些东西在方法执行前后做一些事情,或者修改返回结果。
    // 整个invoke中的逻辑,就叫做切面,把切面的逻辑和原始的逻辑整合为一个对象,这个过程叫做织入。
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

我们可以通过如下方法使用这个接口,通过生成代理对象的方式来拦截对接口的各种方法调用,以此实现代理的功能:

// 使用JDK的Proxy类来创建代理对象
Object proxy = Proxy.newProxyInstance(
    ClassLoader.getSystemClassLoader(),
    new Class[]{ /*被代理的接口数组*/ },
    /*InvocationHandler的实例*/
);

按照上述方法,就会生成一个基于JDK的动态代理对象,调用对象的方法的时候,就会自动触发InvocationHandler的invoke,在这个invoke方法中,应当处理接口的实际的逻辑。

CGLib代理

CGLib是一个十分常用的第三放动态代理类库,和JDK的代理只能代理接口不同,他可以进行子类代理,也就是让代理类继承指定的父类。

CGLib基于java的asm框架,这个框架可以直接生成字节码。

与JDK代理相同的是,CGLib的代理同样需要一个类似的接口,叫做MethodInterceptor,接口是这样的:

public Interface MethodInterceptor {
     
    // 是一个和JDK代理很相似的代理吧?
    // 第一个参数是代理对象,第二个是被调用的方法,第三个是参数数组,第四个是方法代理对象。
    // 通过方法代理对象可以直接调用原始的方法(invokeSuper),
     public Object intercept(Object obj, Method method, Object[] param, MethodProxy methodProxy) throw Throwable;    
}

我们可以通过这种方法来基于CGLib的接口生成代理对象,用以拦截对代理对象的方法调用,以此达到动态带来的目的:

// 借助Enhancer来完成CgLib的代理:
Enhancer enhancer = new Enhancer();
enhancer.setSuperClass(被代理的类);
enhancer.setCallback(MethodInterceptor的对象)
Object proxy = enhancer.create();

按照上述方法,就能通过CGLib得到一个代理对象,代理类通过继承被代理的类,并且借助MethodInterceptor接口达到代理的效果。

Javaassist 字节码手动代理

Javaassist是一个java的字节码处理框架,能够直接通过生成字节码的方式来生成一个类,既然能够生成类,那么自然是可以用来进行动态代理的功能了。

但是和上面两个方法不同,Javaassist需要手动编写一些东西才能达到动态代理的目的。

ClassPool pool = ClassPool.getDefault();
CtClass superClass = pool.get(被代理的类的权限定名);
CtClass proxyTarget = pool.makeClass(代理后的类的权限定名);
// 然后过滤掉superClass的不需要代理的方法。
// 添加过滤后的,也就是需要代理的方法到proxyTarget类中,这个时候就可以织入切面的逻辑了。
// 添加一个private字段,用于存放被代理的那个对象。
// 字段需要一个set方法。
Class createdProxyClass = proxyTarget.toClass();
// 使用这个创建好的代理类吧。

所以说Javaassist是很麻烦的,但是也非常的灵活,极其的灵活,完全可以根据自己的需要来。

AspectJ的编译时代理

没用过,AspectJ提供了一种ajc文件单独定义代理的规则,但是我感觉太麻烦。

Fantastic Soft

风铃之书是个人的工作和生活的总结和分享的站点,欢迎来访和留言,有时也会提供自家软件的发布版本和开源项目。