JDK动态代理机制

代理模式

代理模式简单来说就是,使用代理对象代替真实对象的访问。可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能,比如在某个方法执行前后,可以增加一些自定义的操作。

[静态代理](多线程详解.md/#静态代理模式 static proxy)

动态代理

静态代理中,对目标对象的每个方法的扩展都是手动完成的,非常不灵活(比如一旦新增方法,目标对象和代理对象都要进行修改)、麻烦(需要对每个目标类都单独写一个代理类)。日常开发几乎看不到使用静态代理的场景

相比于静态代理来说,动态代理更加灵活。不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,可以直接代理实现类(CGLIB 动态代理机制)。从JVM角度说,动态代理是在运行时 动态生成类字节码,并加载到JVM中

Spring AOP、RPC框架的实现都依赖了动态代理。动态代理在日常开发中使用相对较少,但在框架中是必用的一门技术,对于各种框架原理的理解和学习很有帮助。就Java来说,动态代理的实现方式有很多种,比如JDK 动态代理、CGLIB 动态代理

JDK 动态代理机制

InvocationHandler接口和Proxy类是核心。

Proxy.newProxyInstance,该方法主要用来生成一个代理对象。通过Proxy类的newProxyInstance()方法创建的代理对象,在调用方法的时候,实际会调用到实现InvocationHandler接口的类的invoke()方法。可以在invoke()方法种自定义处理逻辑,比如在方法执行前后做什么事情。


使用步骤

  1. 定义一个接口及其实现类。
  2. 实现InvocationHandler接口并重写invoke方法,在其中调用被代理类的方法,并自定义一些处理逻辑。
  3. 通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法创建代理对象。

示例

定义一个发送短信的接口及其实现类

1
2
3
4
5
6
7
8
9
10
11
public interface SmsService {
String send(String message);
}

public class SmsServiceImpl implements SmsService {
@Override
public String send(String message) {
System.out.println("send message: " + message);
return message;
}
}

实现InvocationHandler接口并重写invoke方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class JdkInvocationHandler implements InvocationHandler {
/**
* 代理类中的真实对象
*/
private final Object target;

public JdkInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}

通过工厂类创建代理对象

1
2
3
4
5
6
7
8
9
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 目标类的类加载器
target.getClass().getInterfaces(), // 代理需要实现的接口,可指定多个
new JdkInvocationHandler(target) // 动态代理类
);
}
}

实际使用

1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");
}
}

CGLIB 动态代理机制

JDK 动态代理有一个最致命的问题:只能代理实现了接口的类。CGLIB 动态代理机制可以避免这个问题。

CGLIB(Code Generation Library) 是一个字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。Spring中的AOP模块,如果目标对象实现了接口,则默认采用JDK动态代理,否则采用CGLIB动态代理。

在CGLIB动态代理机制中,MethodInterceptor接口和Enhancer类是核心。


使用步骤

  1. 定义一个类
  2. 实现MethodInterceptor接口并重写intercept方法,intercept用于拦截扩展被代理类的方法,和 JDK 动态代理中的invoke方法类似。
  3. 通过Enhancer类的create()方法创建代理类。
1
2
3
4
5
6
<!-- CGLIB 动态代理 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

示例

定义一个发送短信的类

1
2
3
4
5
6
public class SmsService {
public String send(String message) {
System.out.println("send message: " + message);
return message;
}
}

实现方法拦截器MethodInterceptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CglibMethodInterceptor implements MethodInterceptor {
/**
* 扩展代理对象
*
* @param obj 被扩展的代理对象
* @param method 被拦截的方法(需要扩展的方法)
* @param args 方法入参
* @param methodProxy 用于调用原始方法
* @return 被代理方法调用后的结果
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("before method " + method.getName());
Object result = methodProxy.invokeSuper(obj, args);
System.out.println("after method " + method.getName());
return result;
}
}

通过工厂类创建代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz) {
// 创建动态代理扩展类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new CglibMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}

实际使用

1
2
3
4
5
6
public class Main {
public static void main(String[] args) {
SmsService smsService = (SmsService) CglibProxyFactory.getProxy(SmsService.class);
smsService.send("java");
}
}

JDK 动态代理 和 CGLIB 动态代理的对比

  1. JDK动态代理只能代理实现了接口的类,CGLIB可以代理未实现任何接口的类。另外,CGLIB动态代理是通过生成一个被代理的子类,来拦截被代理类的调用,因此不能代理声明为final类型的类和方法。
  2. JDK 动态代理的效率更高

静态代理和动态代理的对比

  1. 灵活性

    动态代理更加灵活,不需要实现接口,可以直接代理实现类。静态代理中,接口一旦新增方法,目标对象和代理对象都需要修改,非常麻烦

  2. JVM层面

    静态代理在编译时,就将接口、实现类、代理类这些都变成了实际的class文件;动态代理是在运行时动态生成类字节码,并加载到JVM中。