CGLIB动态代理
JDK动态代理要求必须针对接口的实现类,才能实现动态代理,并且没有办法对指定的功能进行代理。因此又有了CGLIB动态代理。
CGLIB:通过一个小而快的字节码处理框架ASM,直接操作字节码文件,生成新的class文件。
JDK动态代理:直接生成class文件。
实例
public class CGLIBTest {
public static void main(String[] args) {
//cglib生成的class文件存储到指定文件
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F://temp");
Enhancer en = new Enhancer();
//进行代理
en.setSuperclass(UserReadService.class);
en.setCallback(new UserReadServiceInterceptor());
//生成代理实例
UserReadService userReadService = (UserReadService) en.create();
userReadService.findById();
}
}
class UserReadServiceInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("object=" + o.getClass().getName());
System.out.println("method=" + method.getDeclaringClass().getName());
System.out.println("methodProxy=" + methodProxy.getClass().getName());
return methodProxy.invokeSuper(o, objects);
}
}
输出内容:
object=com.jkf.java.proxy.cglib.UserReadService$$EnhancerByCGLIB$$74a62546
method=com.jkf.java.proxy.cglib.UserReadService
methodProxy=org.springframework.cglib.proxy.MethodProxy
user readService findById
cglib的方法增强,是通过实现MethodInterceptor对外暴露的,因此可以通过自定义MethodIntegerceptor进行方法增强(代理功能)。
MethodInterceptor的参数
Object o:代理对象。
Method:目标对象的方法对象。
Object[]:方法的入参。
MethodProxy:目标对象方法的代理方法, 功能跟Method差不多,但是因为MethodProxy属于FastClass,相对Method通过反射执行速度快一些。当然现在jvm对反射这块支持的够好,现在反射的效率也上来了。
CGLIB的class文件
在文件夹中,可以找到
- UserReadService$$EnhancerByCGLIB$$74a62546.class:UserReadSerivce生成的代理对象的class文件,直接继承UserReadService.class。
- UserReadService$$FastClassByCGLIB$$679f6791.class:UserReadService的FastClass文件,被代理类的所有方法包含其父类的方法建立的索引,继承FastClass。
- UserReadService$$EnhancerByCGLIB$$74a62546$$FastClassByCGLIB$$3333fa2c.class:为代理类中的每个方法建立了索引,继承FastClass。
查看UserReadService的代理类方法,可以发现对所有的public非final方法进行了重写。
代理类和代理对象
- 代理类:代理后,目标类的所有public非final方法都会执行MethodInterceptor,即使目标类中的其他方法调用被代理的方法,仍然可以触发MethodInterceptor。
- 代理对象:代理后,目标类的所有public非final方法都会执行MethodInterceptor,但是目标对象的其他方法调用被代理的方法,却不可以触发MethodInterceptor。
实例:
- 代理类
class UserReadServiceInterceptor3 implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("object=" + o.getClass().getName());
System.out.println("method=" + method.getName());
System.out.println("methodProxy=" + methodProxy.getSignature().getName());
return methodProxy.invokeSuper(o, objects);
}
}
- 代理对象:spring的AOP就是这种形式,因此目标对象的内部方法调用“被代理的方法”,是不会触发MethodInterceptor,SpringAop这么设置,是为了优化拦截,例如:权限拦截,内部访问其实不用进行拦截判断的。
class UserReadServiceInterceptor implements MethodInterceptor {
private Object object;
public UserReadServiceInterceptor(Object obj){
this.object = obj;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("object=" + o.getClass().getName());
System.out.println("method=" + method.getName());
System.out.println("methodProxy=" + methodProxy.getSignature().getName());
return methodProxy.invoke(object, objects);
}
}
网上有更多的MethodProxy的invoke和invokeSuper的区别,大家可以自行搜索,这里只从我们常见的使用出发说明。
callback接口
Enhancer设置的MethodInterceptor的方法,是对所有的方法都有效,但是想对指定的方法不做拦截,例如:readService的find开头的方法放行,权限控制
public class UserPrivilegeTest {
public static void main(String[] args) throws InterruptedException {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F://temp");
Enhancer en = new Enhancer();
en.setSuperclass(UserReadService.class);
//注意callBacks数组的callBack下标
en.setCallbacks(new Callback[]{new UserPrivilegeInterceptor(),NoOp.INSTANCE});
en.setCallbackFilter(new UserPrivilegeFilter());
UserReadService userReadService = (UserReadService) en.create();
//防止多线程争用System.out.println,主线程睡一段,再开下一个线程。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("method=test");
UserFactory.register(new User("boss"));
userReadService.test();
}
}).start();
System.out.println();
System.out.println();
System.out.println();
System.out.println();
System.out.println();
Thread.sleep(1000*4);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("method=test");
UserFactory.register(new User("jkf"));
userReadService.test();
}
}).start();
System.out.println();
System.out.println();
System.out.println();
System.out.println();
System.out.println();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("method=findById");
UserFactory.register(new User("boss"));
userReadService.findById();
}
}).start();
System.out.println();
System.out.println();
System.out.println();
System.out.println();
System.out.println();
Thread.sleep(1000*4);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("method=findById");
UserFactory.register(new User("boss"));
userReadService.findById();
}
}).start();
}
}
//方法拦截器,拦截所有的方法
class UserPrivilegeInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
User user = UserFactory.getUser();
System.out.println("userName:"+user.getName());
if("boss".equals(user.getName())){
return methodProxy.invokeSuper(o,objects);
}else{
System.out.println("没有权限");
return null;
}
}
}
/**
* 放行find开头的方法
* */
class UserPrivilegeFilter implements CallbackFilter{
@Override
public int accept(Method method) {
if(method.getName().startsWith("find")){
//1代表callBack数组中下标为1的callBack,即NoOp.INSTANCE,CallBack是什么都不做,直接调用目标类的方法。
return 1;
}
//0代表callBack数组中下标为0的callBack,即UserPrivilegeInterceptor。
return 0;
}
}
//用户工厂,主要是方便UserPrivilegeFilter中获取User对象,这也是ThreadLocal的一个用法:把部分参数作为上下文进行传递,减少了对接口入参的修改,或者增加隐藏参数
class UserFactory{
private static ThreadLocal<User> userThreadLocal= new ThreadLocal();
public static User getUser(){
return userThreadLocal.get();
}
public static void register(User user){
userThreadLocal.set(user);
}
}
输出:
method=test
userName:boss
user test
user readService findById =12
method=test
userName:jkf
没有权限
method=findById
user readService findById =14
method=findById
user readService findById =15
通过输出内容发现:findById是没有进行权限校验的,但是test方法,只有boss可以访问。
上述的代码运行后,可以查看相关的class文件,发现find方法就没有出现在UserReadService$$EnhancerByCHLIB$$xxxx.class中,这就是NoOp.INSTANCE的功劳。