本篇我们介绍一下Cglib是如何实现动态代理的。
Cglib是什么?
Cglib是一个强大的代码生成包,广泛地被许多AOP框架使用,用来提供方法的拦截,下图展示了Cglib和一些语言、框架的关系:
根据图总结一下:
(1)最底层是字节码,Java中就是.class文件;
(2)字节码上面是ASM,一种可以直接操作字节码的框架;
(3)ASM上面是CGLIB、Groovy、BeanShell,后两个是脚本语言非Java体系中的内容,它们都通过ASM来操作字节码;
(4)再往上就是我们比较熟悉的Hibernate、Spring AOP等框架;
(5)最上层是具体应用,比如Web项目;
Cglib安装
使用Cglib需要导入以下两个jar包:
cglib包
链接:https://pan.baidu.com/s/1gsavHrvFCqFiw8gGTtxuBA
提取码:k5y6
asm包
链接:https://pan.baidu.com/s/1C2ciBA9tcKTBQ5vS7BKXlA
提取码:v1d1
Cglib实现动态代理流程
1.创建被代理类Person,其中eat()
方法被final修饰。
/**
* @author codeZhao
* @date 2021/1/14 14:22
* @Description 被代理类Person
*/
public class Person{
final public void eat() {
System.out.println("I'm eating...");
}
public void run() {
System.out.println("I'm running...");
}
}
2.创建方法拦截器,需要继承MethodInterceptor
接口,重写intercept()
方法,这里在方法执行前后打印日志。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author codeZhao
* @date 2021/1/14 16:22
* @Description 方法拦截器,实现日志打印
*/
public class LoggerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Logger: enter method " + method.getName() + "() ...");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("Logger: quit method " + method.getName() + "() ...");
return result;
}
}
3.Cglib代理测试
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
/**
* @author codeZhao
* @date 2021/1/14 14:23
* @Description Cglib代理测试
*/
public class CglibTest {
public static void main(String[] args) {
//在指定目录下生成动态代理类,我们可以反编译看一下里面到底是一些什么东西
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaProject");
//创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
//设置被代理类类型
enhancer.setSuperclass(Person.class);
//设置回调函数,即方法拦截器
enhancer.setCallback(new LoggerMethodInterceptor());
//创建代理类
Person personProxy = (Person) enhancer.create();
//调用代理类方法
personProxy.eat();
personProxy.run();
}
}
I'm eating...
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...
可以看到,调用run()
方法时被拦截了,但eat()
并没有被拦截,这是因为代理类要继承被代理类,重写代理方法,而eat()
被修饰为final了,所以不能被代理类重写。
使用Cglib实现不同的拦截策略
在Spring AOP经常遇到这样的场景,我要对类A的B方法实现一种拦截策略,对类A的C方法实现另一种拦截策略或者是不进行拦截,下面我们看下如何实现。
为更好地看到效果,在Person
类中又加了两个方法:
/**
* @author codeZhao
* @date 2021/1/14 14:22
* @Description 被代理类Person
*/
public class Person{
public void run() {
System.out.println("I'm running...");
}
public void say() {
System.out.println("I'm saying...");
}
public void sleep() {
System.out.println("I'm sleeping...");
}
}
我们首先再定义一个不同的方法拦截器TimeMethodInterceptor
,在方法前后输出一下当前时间。
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author codeZhao
* @date 2021/1/15 8:15
* @Description
*/
public class TimeMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("StartTime=" + System.currentTimeMillis());
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("EndTime=" + System.currentTimeMillis());
return result;
}
}
然后还需要定义一个Filter
,继承CallbackFilter
接口,重写accept()
方法即可,accept()
返回int数值,具体有什么用到下面测试时再看。
import net.sf.cglib.proxy.CallbackFilter;
import java.lang.reflect.Method;
/**
* @author codeZhao
* @date 2021/1/15 8:25
* @Description
*/
public class PersonFilter implements CallbackFilter {
@Override
public int accept(Method method) {
if ("run".equals(method.getName())) {
return 0;
}else if ("say".equals(method.getName())) {
return 1;
}
return 2;
}
}
最后修改Cglib测试类,这次回调函数传入一个集合,并且设置CallbackFilter
为上一步创建的PersonFilter
对象。
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
/**
* @author codeZhao
* @date 2021/1/14 14:23
* @Description Cglib代理测试
*/
public class CglibTest {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\ideaProject");
LoggerMethodInterceptor loggerMethodInterceptor = new LoggerMethodInterceptor();
TimeMethodInterceptor timeMethodInterceptor = new TimeMethodInterceptor();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallbacks(new Callback[]{loggerMethodInterceptor, timeMethodInterceptor, NoOp.INSTANCE});
enhancer.setCallbackFilter(new PersonFilter());
Person personProxy = (Person) enhancer.create();
personProxy.eat();
personProxy.run();
personProxy.say();
personProxy.sleep();
}
}
这样设置的意思是,根据PersonFilter
的返回值确定每个方法使用回调函数集合中的哪一个,具体来说,当PersonFilter
返回0时调用集合中的第0个,返回1时调用集合中的第1个...。所以执行结果如下:
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...
StartTime=1610672867924
I'm saying...
EndTime=1610672867924
I'm sleeping...
可以看到对不同方法实现了不同的拦截方法,其中sleep()
方法没有做任何的拦截,因为它对应回调函数集合的最后一个NoOp.INSTANCE
,这是一个空Callback,所以如果不想对某个方法做拦截的话,可以通过NoOp.INSTANCE
实现。
设置构造函数不拦截
另外如果在构造函数中调用了某方法,那在创建类的时候也会对该方法拦截。比如我在Perison
中加一个构造器,调用run()
方法:
/**
* @author codeZhao
* @date 2021/1/14 14:22
* @Description 被代理类Person
*/
public class Person{
public Person() {
run();
}
public void run() {
System.out.println("I'm running...");
}
}
然后Cglib代理测试
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
/**
* @author codeZhao
* @date 2021/1/14 14:23
* @Description Cglib代理测试
*/
public class CglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new LoggerMethodInterceptor());
Person personProxy = (Person) enhancer.create();
System.out.println("-----------------");
personProxy.run();
}
}
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...
-----------------
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...
结果在构造器中调用run()
时也做了拦截,如果不想在构造器调用时被拦截,可以通过Enhancer
的setInterceptDuringConstruction(boolean interceptDuringConstruction)
方法,传入false即可,默认是true。
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.NoOp;
/**
* @author codeZhao
* @date 2021/1/14 14:23
* @Description Cglib代理测试
*/
public class CglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Person.class);
enhancer.setCallback(new LoggerMethodInterceptor());
//设置构造器调用方法时不拦截
enhancer.setInterceptDuringConstruction(false);
Person personProxy = (Person) enhancer.create();
System.out.println("-----------------");
personProxy.run();
}
}
I'm running...
-----------------
Logger: enter method run() ...
I'm running...
Logger: quit method run() ...