Java Tutorials-14-代理

什么是代理模式:
用户代码不直接调用某些功能类的方法, 而是通过代调类作为”中间层”去调用”被代理类”. 所有调用都会被代理类拦截, 我们可以利用代理类的这个特性, 在代理类里增加额外的执行代码.

使用代理可以给我们带来如下好处: 用户代码(调用者)和功能类(被调用者)解耦, 第二个好处是通过代理层可以加入一些通用的代码.

Java 代理模式的实现主要有: 静态代理, JDK 动态代理, Cglib 动态代理.

JDK 动态代理

如何使用 JDK 的动态代理:

// 1 接口
public interface YourInterface
{
public void doSomething();
}

// 2 实际类
public class YourClass implements YourInterface
{
public void doSomething() { }
}

// 3 代理类:实现java.lang.reflect.InvocationHandler接口 & 重写 invoke()
public class YourHandler implements InvocationHandler {

private YourInterface target; // 被代理的实例
public YourHandler(YourInterface target){
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在这里可以放一些 @advice 操作
Object result = method.invoke(target, args);
return result;
}
}


// 4 创建代理
YourInterface obj = new YourClass();
InvocationHandler handler = new YourHandler(obj);

// newProxyInstance 这一步是把 YourHandler 生成字节码, 并使用ClassLoader 创建
YourInterface proxy = (YourInterface)Proxy.newProxyInstance(
obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
handler);


// 5 通过代理调用
proxy.doSomething();

通过 proxy 调用 YourClass 实现自接口 YourInterface 的所有方法, 都会调用到 YourHandler 的 invoke 方法,
在 invoke 方法里可以很方便的做一些前置和后置处理(访问控制、远程通信、日志、缓存等), 在 invoke 里再通过反射调用实际类 YourClass 的方法.

  • JDK动态代理的优点是, 当 YourInterface 的实现类有很多的时候, 比如有 YourClassA, YourClassB…
    通过代理调用这些实现类的方法(必须是实现 YourInterface 里的方法), 都会由代理调用到 InvocationHandler.invoke(),
  • 如果用静态代理, 那么代理类(实现了 YourInterface 接口)必须为 YourInterface 的每一个方法都增加单独的代码.

参考: Java 动态代理作用是什么? - 知乎 @ref

实现原理

在调用 Proxy.newProxyInstance() 之后,
又调用了 ProxyGenerator.generateProxyClass() 方法生成最终代理类的字节码, 并通过 ClassLoader 把字节码转化成对象.
在最终代理类里实现了我们的 Interface 定义的所有方法, 在这些方法内部, 都通过反射调用了 InvocationHandler 接口实现类的 invoke() 方法

sun.misc.ProxyGenerator 提供了一个功能, 可以生成 YourInterface 的实现类的字节码:
byte[] data = ProxyGenerator.generateProxyClass(name,new Class[]{YourInterface.class});

CGLIB 代理

CGLIB 是一个功能强大&高性能的字节码生成包。它为没有实现接口的类提供代理,为 JDK 的动态代理提供了很好的补充。通常可以使用 Java 的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB 是一个好的选择。

  • CGLIB 与 JDK 动态代理不同的是, 使用 CGLIB 即使被代理类没有实现任何接口也可以实现动态代理功能。但是不能对 final 修饰的类进行代理。

  • JDK 动态代理通过反射类 Proxy 和 InvocationHandler 回调接口实现,要求委托类必须实现一个接口,只能对该类接口中定义的方法实现代理,这在实际编程中有一定的局限性。

CGLIB 包的底层是通过使用一个小而快的字节码处理框架 ASM,来转换字节码并生成新的类。
ASM 是一个 Java 字节码操控框架。通过分析被代理类的 class 文件, 在内存中创建被代理类的增强子类, 它能被用来动态生成类或者增强既有类的功能。

ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
脚本语言例如 Groovy 和 BeanShell,也是使用 ASM 来生成 java 的字节码。当然不鼓励直接使用 ASM,因为它要求你必须对 JVM 内部结构包括 class 文件的格式和指令集都很熟悉。

下面通过一个例子看看使用 CGLib 如何实现动态代理:

定义业务逻辑:

public class UserServiceImpl {
public void add() {
System.out.println("This is add service");
}
}

实现 MethodInterceptor 接口,定义方法的拦截器:

public class YourMethodInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
System.out.println("Before:" + method); // 自定义前置代码
Object object = proxy.invokeSuper(obj, arg);
System.out.println("After:" + method); // 自定义后置代码
return object;
}
}

利用 Enhancer 类生成 UserServiceImpl 的代理类:

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);
enhancer.setCallback(new YourMethodInterceptor());
UserServiceImpl userService = (UserServiceImpl)enhancer.create();

Enhancer 是 CGLib 的字节码增强类, 可以生成类的字节码( 上面的例子里, 生成的是 UserServiceImpl 的子类),
其作用类似 sun.misc.ProxyGenerator, 区别是 Enhancer 不需要被代理类实现接口, 而 ProxyGenerator 要求被代理类必须实现接口

以上参考:
@ref 说说 cglib 动态代理

Spring AOP 与代理

Spring AOP 中的一些注解 & 概念:

@Aspect: PointCut + Advice
@PointCut: 切点, 在哪里切入
@Advice: 切入的行为(在切点之前还是之后, 或者环绕切点), 以及做什么

Spring AOP 使用的动态代理,所谓的动态代理就是说 AOP 框架不会去修改字节码,而是在内存中临时为方法生成一个AOP 对象,这个 AOP 对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
Spring AOP 中的动态代理主要有两种方式,JDK 动态代理CGLIB 动态代理

  • 如果目标类(被切的类)有统一的实现接口,Spring AOP 使用 JDK 动态代理,
  • 如果目标类没有实现接口,那么 Spring AOP 会选择使用 CGLIB 来动态代理目标类。

因此如果某个类被标记为 final,并且没有实现接口,那么它是无法被动态代理的,也就无法当做切点(CutPoint)