CGLIB 动态代理
JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中 MethodInterceptor
接口和 Enhancer
类是核心。
你需要自定义 MethodInterceptor
并重写 intercept
方法,intercept
用于拦截增强被代理类的方法。
package net.sf.cglib.proxy;
import java.lang.reflect.Method;
public interface MethodInterceptor extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
MethodProxy proxy) throws Throwable;
}
- obj :被代理的对象(需要增强的对象)
- method :被拦截的方法(需要增强的方法)
- args :方法入参
- methodProxy :用于调用原始方法
你可以通过 Enhancer
类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor
中的 intercept
方法。
前提条件:
- 需要引入cglib的jar文件,cglib-full.jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
/*
* 接口
*/
public interface ISinger {
void sing();
}
/**
* 目标对象Singer1实现了某一接口
*/
public class Singer1 implements ISinger{
public void sing(){
System.out.println("唱1首歌");
}
}
/**
* 直接是个类,不是接口
*/
public class Singer3 {
public String sing() {
String string = "我是一个类";
System.out.println(string);
return string;
}
}
CGLIB代理类
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor 方法拦截器
*/
public class CgLibProxyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
before();
Object object = methodProxy.invokeSuper(o, args);
//调用方法之后,我们同样可以添加自己的操作
after();
return object;
}
public void before() {
System.out.println("代理之前要执行一些逻辑。。。");
}
public void after() {
System.out.println("代理之后要执行一些逻辑。。。");
}
}
CGLIB代理工厂类:
import net.sf.cglib.proxy.Enhancer;
/**
* 代理工厂类
*/
public class CgLibProxyFactory {
/**
* 为目标对象生成代理对象
*
* @return
*/
public Object getProxy(Class<?> clazz) {
// 创建动态代理增强类
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置被代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new CgLibProxyMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
测试:
/**
* 测试类
*/
public class CgLibTest {
public static void main(String[] args) {
// 代理实现接口的类
ISinger singer = (ISinger) CgLibProxyFactory.getProxy(Singer1.class);
singer.sing();
// 代理未实现接口的类
Singer3 singer3 = (Singer3) CgLibProxyFactory.getProxy(Singer3.class);
singer3.sing();
}
}
JDK 动态代理和 CGLIB 动态代理对比
- JDK 动态代理只能代理实现了接口的类,而 CGLIB 可以代理未实现任何接口的类。 另外CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和final或static方法。
- 就二者的效率来说,大部分情况都是JDK动态代理更优秀,随着 JDK 版本的升级,这个优势更加明显。
- CGLIB底层采用ASM字节码生成框架,使用字节码技术生成代理类,而JDK采用的是Java反射来生成的类
Spring在选择用JDK还是CGLib的依据
- 当Bean实现接口时,Spring就会用JDK的动态代理
- 当Bean没有实现接口时,Spring使用CGLib来实现
- 可以强制使用CGLib(在Spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)