- 在知道使用那些动态代理技术之前,我们需要知道动态代理技术是什么?
动态代理技术就是指通过反射机制自动创建代理类。而代理对象拦截目标对象的方法调用,可在执行前后插入额外逻辑,做代码增强。
那这里我们会有一个疑问,代理对象是怎么知道他要拦截那个目标对象的?换句话说,就是代理类怎么和目标类产生关联的?反射机制是基于什么创建代理类的?保持这个疑问,因为代理类和目标类产生联系的方式,将会是区分不同代理技术的关键。往下看你就恍然大明白了。
- 在Spring框架中,所涉及到的动态代理技术分为两种:
- JDK动态代理 :生成的代理类基于同一个接口实现!!!
- CGLIB动态代理:生成的代理类继承目标类实现!!!
我们先来了解一下JDK动态代理
1.目标类与其实现的接口
//目标对象,为该对象的方法添加日志输出
public class ServiceImpl implements Service {
@Override
public void serviceRun() {
System.out.println("服务正在运行中");
}
}
//目标对象所实现的接口
public interface Service {
void serviceRun();
}
2.handler处理器(日志输出的代码增强的逻辑就写在处理的invoke方法里)
public class LoggingHandler implements InvocationHandler {
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
/**
主要方法,增强代码的逻辑将写在这个方法里面
第一个参数:代理对象
第二个参数:被增强的方法
第三个参数:调用方法的参数args
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前: " + method.getName());
/**
通过反射调用目标对象的方法,并返回该方法的返回值
*/
Object result = method.invoke(target, args);
System.out.println("方法执行后: " + method.getName());
return result;
}
}
3.Porxy代理对象的实现
//创建处理器对象
LoggingHandler handler = new LoggingHandler(target);
Service proxy = (Service) Proxy.newProxyInstance(
//通过目标类的字节码文件获得绑定的类加载器
target.getClass().getClassLoader(),
//通过字节码文件获得目标类实现的接口的数组
target.getClass().getInterfaces(),
//创建的进行业务增强的处理器
handler
);
这里为什么需要同一个加载器?这里涉及到JVM中类加载器的知识,就不详细解释了。那么我们所创建的代理类就应该如下面这样。我们创建的处理器也就在代理类中调用。
//实现了service接口,并重写了所有方法,他的父类Proxy会为他传递一个InvocationHandler处理器对象
public class $Proxy0 extends Proxy implements Service {
public $Proxy0(InvocationHandler h) {
super(h);
}
//当我们调用代理类的serviceRun()方法时,我们会调用我们重写的处理器的invoke方法,实现方法增强
public void serviceRun() {
super.h.invoke(this, method, args);
}
}
最后我们调用代理类的实现方法,即可。这就是JDK动态代理技术的整个流程,创建代理对象基于同一个接口。我们的业务增强代码则需要写在继承于InvocationHandler类的子类中的invoke()方法。这个对象会在创建代理对象的时候传递给代理类的父类Proxy的InvocationHandler属性,并通过继承传递给代理类,代理类调用方法时,实则时调用了处理器的invoke()方法。完成代码增强。
在来了解一下CGLIB动态代理技术
1.创建Enhancer对象,并设置属性(这个对象将是创建代理类的关键)
// 创建Enhancer实例(用于生成代理对象的类)
Enhancer enhancer = new Enhancer();
// 设置父类为目标类
enhancer.setSuperclass(Service.class);
// 设置回调方法拦截器
enhancer.setCallback(new LoggingInterceptor());
// 创建代理对象
Service proxy = (Service) enhancer.create();
2.设置方法拦截器(对应JDK中的处理器)
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("Before method: " + method.getName());
Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
System.out.println("After method: " + method.getName());
return result;
}
3.那么我们通过上述代码创建出的代理类如下。调用代理对象的实现方法,这就是CGLIB的使用流程。
public class ServiceImpl$$EnhancerByCGLIB$$12345678 extends ServiceImpl {
private MethodInterceptor interceptor;
// 构造函数(接收拦截器)
public ServiceImpl$$EnhancerByCGLIB$$12345678(MethodInterceptor interceptor) {
this.interceptor = interceptor;
}
// 重写的父类方法
@Override
public void serviceRun() {
// 静态预生成的 MethodProxy(非运行时创建!)
MethodProxy methodProxy = CGLIB$serviceRun$0$Proxy;
interceptor.intercept(this, CGLIB$serviceRun$0$Method, CGLIB$emptyArgs, methodProxy);
}
// 直接调用父类的方法(静态预生成)
final void CGLIB$serviceRun$0() {
super.serviceRun();
}
// --- 以下为静态初始化块,类加载时执行 ---
static {
// 预生成 Method 对象和 MethodProxy
CGLIB$serviceRun$0$Method = ServiceImpl.class.getMethod("serviceRun");
CGLIB$serviceRun$0$Proxy = MethodProxy.create(
ServiceImpl.class,
ServiceImpl$$EnhancerByCGLIB$$12345678.class,
"serviceRun",
"()V",
"CGLIB$serviceRun$0"
);
}
}
在JDK动态代理技术中,大家能很明白的看懂代理类是怎么做到方法增强的,但是在CGLIB中大家对这个重写的方法是不是就有点不明所以了。在这里,我们需要详细解释一下这个
MethodProxy methodProxy = MethodProxy.create( )方法。
首先,我们要了解方法的参数
在代理类加载的时候,会加载静态代码块的内容,会预生成代理类的增强方法method对象,也会预生成MethodProxy对象。在调用代理类的方法的时候会调用拦截器的拦截方法,将预生成的MethodProxy对象传递进去,由这个对象调用目标类的原始方法。
讲到这里,我们就可以看出JDK动态代理和CGLIB动态代理的区别了
- JDK的代理类和目标类必须实现同一接口,而CGLIB只需要继承目标类即可。
- JDK在实现目标增强的时候需要通过反射机制调用目标类的原始方法,CGLIB因为在代理类加载的时候就已经通过反射预生成了目标类的原始方法,因此,在方法增强的时候不需要在通过反射调用而是直接调用。