JDK动态代理机制
代理模式
代理模式简单来说就是,使用代理对象代替真实对象的访问。可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能,比如在某个方法执行前后,可以增加一些自定义的操作。
[静态代理](多线程详解.md/#静态代理模式 static proxy)
动态代理
静态代理中,对目标对象的每个方法的扩展都是手动完成的,非常不灵活(比如一旦新增方法,目标对象和代理对象都要进行修改)、麻烦(需要对每个目标类都单独写一个代理类)。日常开发几乎看不到使用静态代理的场景。
相比于静态代理来说,动态代理更加灵活。不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,可以直接代理实现类(CGLIB 动态代理机制)。从JVM角度说,动态代理是在运行时 动态生成类字节码,并加载到JVM中。
Spring AOP、RPC框架的实现都依赖了动态代理。动态代理在日常开发中使用相对较少,但在框架中是必用的一门技术,对于各种框架原理的理解和学习很有帮助。就Java来说,动态代理的实现方式有很多种,比如JDK 动态代理、CGLIB 动态代理。
JDK 动态代理机制
InvocationHandler
接口和Proxy
类是核心。
Proxy.newProxyInstance
,该方法主要用来生成一个代理对象。通过Proxy类的newProxyInstance()
方法创建的代理对象,在调用方法的时候,实际会调用到实现InvocationHandler
接口的类的invoke()
方法。可以在invoke()
方法种自定义处理逻辑,比如在方法执行前后做什么事情。
使用步骤
- 定义一个接口及其实现类。
- 实现
InvocationHandler
接口并重写invoke方法,在其中调用被代理类的方法,并自定义一些处理逻辑。 - 通过
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
方法创建代理对象。ClassLoader loader
:指定用哪个类加载器来加载代理类。Class<?>[] interfaces
:指定代理类需要实现的接口(返回的对象可以被安全地转型为实现了这个接口的类型)InvocationHandler h
:指定第2步创建的调用处理器
示例
定义一个发送短信的接口及其实现类
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方法
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;
}
}
通过工厂类创建代理对象
public class JdkProxyFactory {
public static Object getProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 加载代理类的类加载器
target.getClass().getInterfaces(), // 代理类需要实现的接口,即代理的对象
new JdkInvocationHandler(target) // 调用处理器
);
}
}
实际使用
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
类是核心。
使用步骤
- 定义一个类
- 实现
MethodInterceptor
接口并重写intercept
方法,intercept
用于拦截扩展被代理类的方法,和 JDK 动态代理中的invoke
方法类似。 - 通过
Enhancer
类的create()
方法创建代理类。
<!-- CGLIB 动态代理 -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
示例
定义一个发送短信的类
public class SmsService {
public String send(String message) {
System.out.println("send message: " + message);
return message;
}
}
实现方法拦截器MethodInterceptor
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;
}
}
通过工厂类创建代理对象
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();
}
}
实际使用
public class Main {
public static void main(String[] args) {
SmsService smsService = (SmsService) CglibProxyFactory.getProxy(SmsService.class);
smsService.send("java");
}
}
JDK 动态代理 和 CGLIB 动态代理的对比
- JDK动态代理只能代理实现了接口的类,CGLIB可以代理未实现任何接口的类。另外,CGLIB动态代理是通过生成一个被代理的子类,来拦截被代理类的调用,因此不能代理声明为final类型的类和方法。
- JDK 动态代理的效率更高
静态代理和动态代理的对比
灵活性
动态代理更加灵活,不需要实现接口,可以直接代理实现类。静态代理中,接口一旦新增方法,目标对象和代理对象都需要修改,非常麻烦
JVM层面
静态代理在编译时,就将接口、实现类、代理类这些都变成了实际的class文件;动态代理是在运行时动态生成类字节码,并加载到JVM中。