本文已参与「新人创作礼」活动,一起开启掘金创作之路。
动态代理技术
作为Spring核心特点AOP中涉及到的前置知识,理解其使用过程与原理非常重要。动态代理技术在程序运行期间,可以不修改目标源代码的前提下,为目标方法进行增强,常用于权限验证,添加日志,缓存功能等。
JDK动态代理 -- 基于接口的动态代理技术
作为Java内置的代理技术,该技术只能作用于有实现接口的类,存在一定的局限性。核心类涉及:Proxy,Method与InovationHandler接口。
| 涉及类/接口 | 作用 |
|---|---|
| Proxy | 生成增强方法后的代理对象 |
| Method | 起到调用目标对象的目标方法的作用 |
| InovationHandler | 调用处理器,负责增强目标方法。 |
JDK动态代理的实现
为了实现JDK的动态代理,我们创建ITarget接口, TargetImpl类实现ITarget接口与Advice通知类(用于增强方法的类)。下面的代码阅读需要一些反射的知识。
// ITarget.java
public interface ITarget {
void hello();
}
// Target.java
public class TargetImpl implements ITarget {
@Override
public void hello() {
System.out.println("Hello World!");
}
}
// Advice.java
public class Advice {
public void before() {
System.out.println("前置方法...");
}
public void afterRunning() {
System.out.println("后置方法...");
}
}
// 测试类
public class TestDynamicProxy {
@Test
public void testJDKProxy() {
// 1. 创建目标对象
ITarget target = new TargetImpl();
// 2. 创建通知
Advice advice = new Advice();
// 3. 创建代理对象
ITarget proxy = (ITarget) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
// 4. 在这里增强所调用的方法
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.before();
// 这句话可以理解为 --> object invoke = target.method(args);
// 简单来说就是调用了target对象的指定方法
Object invoke = method.invoke(target, args);
advice.afterRunning();
// 这里一般返回方法执行后的结果
return invoke;
}
}
);
// 5. 调用方法
proxy.hello();
}
}
主要说说Proxy.newProxyInstance()方法,该方法需要传入三个参数ClassLoader loader, Class<?>[] interfaces, InvocationHandler h。第一个参数为类加载器,第二参数为代理对象的实现接口列表,第三个参数为InvocationHandler接口的实现类即可。这里关于接口列表的话,没有尝试过多接口,有兴趣的话,可以自己试一试。
cglib动态代理 -- 基于父类的动态代理技术
cglib在实现动态代理的时候,通过Enhancer增强器类实现,其实需要传入回调接口,使用其子接口MethodInterceptor实现。因为目标类不需要再需要接口继承,所以只有三个文件TargetImpl类实现ITarget接口与Advice通知类。其他文件不变,只改动测试代码。
// 测试类
public class TestDynamicProxy {
@Test
public void testCglibProxy() {
// 1. 创建目标对象
TargetImpl target = new TargetImpl();
// 2. 创建通知
Advice advice = new Advice();
// 3. 创建增强器
Enhancer enhancer = new Enhancer();
// 4. 创建代理
// TargetImpl proxy = (TargetImpl) enhancer.create(
// TargetImpl.class,
// new MethodInterceptor() {
// @Override
// public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// // 与JDK中InvocationHandler同理,增强指定方法
// advice.before();
// Object invoke = proxy.invokeSuper(obj, args);
// advice.afterRunning();
// return invoke;
// }
// });
// 函数式接口实现
TargetImpl proxy2 = (TargetImpl) enhancer.create(
TargetImpl.class,
(MethodInterceptor) (obj, method, args, proxy) -> {
// 与JDK中InvocationHandler同理,增强指定方法
advice.before();
Object invoke = proxy.invokeSuper(obj, args);
advice.afterRunning();
return invoke;
}
);
// 5. 调用方法
proxy2.hello();
}
}
使用cglib代理调用原来目标对象的目标方法的时候,使用了
proxy.invokeSuper(obj, args),与JDK动态代理类似,在intercept()方法中,obj对象为代理对象,不过多了一个MethodProxy proxy对象。该对象的主要作用同样是进行方法的调用,但是在方法调用的时候,因为我们没有使用目标对象,而是使用了代理对象,所以这里我们需要利用proxy.invokeSuper()方法进行调用目标对象的原始方法。
查阅invokerSuper()方法的官方注解Invoke the original (super) method on the specified object.,我们知道cglib代理是基于父类实现的,所以这里指的super method就是原目标对象的method。但如果我们在这里调用的proxy.invoke(obj, args),那么就相当于我们一直调用代理对象的目标方法,会导致无限循环。
我们会无限的调用
hello()方法,而代理对象的hello()方法又要经过增强,导致栈内存溢出。
最近在学习Spring框架,希望能从中多理解框架背后的设计理念。