前言
学习Spring aop的时候,一直都知道是基于动态代理实现的,那么到底什么是动态代理,又该如何自己实现一个动态代理呢,对此做一个记录。
所谓动态代理,一般是为了给需要实现的方法添加预处理或者添加后续操作,但是不干预实现类的正常业务,把一些基本业务和主要的业务逻辑分离。
基于JDK的动态代理
两个核心的类Proxy、InvocationHandler(接口);基于JDK的动态代理是需要被代理的类实现某个接口。下面贴一下代码应用:
首先要有一个接口和实现类,实现类就是需要被代理的类;业务很简单,就是打印一句话。
public interface Subject {
void hello(String param);
void coding();
void debug();
}
public class SubjectImpl implements Subject {
@Override
public void hello(String param) {
System.out.println("hello " + param);
}
@Override
public void coding() {
System.out.println("I am is coding");
}
@Override
public void debug() {
System.out.println("I am is debugging");
}
}
还需要一个代理类,实现InvocationHandler接口,重写invoke方法;
说明一下invoke方法:method.invoke(target, args);就是利用发射调用被代理类中的方法(了解反射的话,这个方法就很好理解)
public class SubjectProxy implements InvocationHandler {
// 目标类,也就是被代理对象
private Object target;
public SubjectProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("------我是前置处理------");
// 执行原本方法
Object invoke = method.invoke(target, args);
System.out.println("------我是后置处理------");
return invoke;
}
}
最后进入测试,利用Proxy这个类来帮助我们创建代理之后的Subject类。
说明下newProxyInstance方法:第一个参数是类加载器;第二个参数是被代理类的接口,如果有多个就以数组形式传入;第三个就是代理对象的实例。这里要注意实际代理的对象是SubjectImpl而不是Subject 这个接口,这个一定要清晰。
SubjectImpl subject = new SubjectImpl();
Subject s = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
subject.getClass().getInterfaces(), new SubjectProxy(subject));
s.hello("world");
输出结果:
------我是前置处理------
hello world
------我是后置处理------
基于CGLIB的动态代理
因为基于JDK的动态代理一定要继承一个接口,而绝大部分情况是基于POJO类的动态代理,那么CGLIB就是一个很好的选择,而且CGLIB可以对类中的方法有选择性的代理。
ps:这里需要添加的依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
两个核心的类:MethodInterceptor(接口)、Enhancer
下面贴一下代码应用:
首先有一个需要被代理的类:
public class SubjectClass {
public void hello(String param) {
System.out.println("hello " + param);
}
public void coding() {
System.out.println("I am is coding");
}
public void debug() {
System.out.println("I am is debugging");
}
}
然后需要一个拦截器,实现MethodInterceptor接口,重写intercept方法。
说明下intercept方法,第一个参数是被代理对象的示例;第四个参数是代理的方法。这里要注意用methodProxy去调用invokeSuper方法,不能调用invoke方法;也不能用method去调用。
public class MyInterceptor implements MethodInterceptor {
/***
* Object obj:这是代理对象,也就是[目标对象]的子类
* Method method:[目标对象]的方法
* Object[] arg:参数
* MethodProxy methodProxy:代理对象的方法
*/
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 因为代理对象是目标对象的子类
// 该行代码,实际调用的是父类目标对象的方法
System.out.println("------我是前置处理------");
// 通过调用子类[代理类]的invokeSuper方法,去实际调用[目标对象]的方法
// 代理对象调用代理对象的invokeSuper方法,而invokeSuper方法会去调用目标类的 invoke方法完成目标对象的调用
return methodProxy.invokeSuper(obj, objects);
}
}
最后测试:这里就是通过Enhancer来创建被代理之后的类,拦截到类中的方法,添加处理的代码。
public class main {
public static void main(String[] args) {
// 创建增强器
Enhancer enhancer = new Enhancer();
// 设置需要增强的类的类对象
enhancer.setSuperclass(SubjectClass.class);
// 设置回调函数
enhancer.setCallback(new MyInterceptor());
// 获取增强之后的代理对象
SubjectClass subject = (SubjectClass) enhancer.create();
// 调用方法
subject.hello("world");
subject.coding();
subject.debug();
}
}
最后扩展下:如果我想要对SubjectClass类中的三个方法执行不同的额外处理业务,该如果实现呢?CGLIB也提供了相关api,核心类是CallbackFilter(接口),贴下代码:
/***
* @Description: 回调的过滤函数,返回的数字是Callback[]数组的下标
* 下面的意思是:通过方法名来判断,如果方法名是'hello',则在数组第一个位置;
* 如果方法名是'coding',则在数组第二个位置;
* 如果方法名是'debug',则在数组的第三个位置
* @Date: 2020/6/28 9:18
* @version: v1.0
*/
public class MyCallbackFilter implements CallbackFilter {
@Override
public int accept(Method method) {
String name = method.getName();
int num = 0;
switch (name) {
case "hello":
num = 0;
break;
case "coding":
num = 1;
break;
case "debug":
num = 2;
break;
default:
break;
}
return num;
}
}
重新写下测试代码:
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(SubjectClass.class);
//enhancer.setCallback(new MyInterceptor());
CallbackFilter callbackFilter = new MyCallbackFilter();
enhancer.setCallbackFilter(callbackFilter);
Callback callback = new MyInterceptor();
// 构造一个Callback[]数组,第一个位置'NoOp.INSTANCE'表示不做任务处理,结合callbackFilter来看,也就是说方法名为'hello'的方法不做任务处理;
// 第二个位置是一个自定义的Callback,也就是说方法名为'coding'的方法按照这个来处理,做了代理;
// 同理第三个位置的方法也不做任何处理。这样就能对类中特定的方法按照特定的逻辑做代理。
Callback[] callbacks = new Callback[]{NoOp.INSTANCE, callback, NoOp.INSTANCE};
enhancer.setCallbacks(callbacks);
SubjectClass subject = (SubjectClass) enhancer.create();
subject.hello("world");
subject.coding();
subject.debug();
}
输出结果:
hello world
------我是前置处理------
I am is coding
I am is debugging
可以看到,我们只对coding方法做了拦截,只给这个方法添加了额外的处理逻辑,这样就能做到选择性的对需要代理的类中的方法做特定拦截。
总结下两个动态代理的区别:
- JDK中所要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中具有一定的局限性,而且使用反射的效率也并不是很高。
- 使用CGLib实现动态代理,完全不受代理类必须实现接口的限制,而且CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。