动态代理导致注解未生效
今天在使用mybatis-plus
作者苞米豆的另一个项目lock4j
用于项目中的分布式锁,解决多实例情况下对接口进行上锁,使得业务上的共享资源在一个时间节点里只会被一个线程执行。
lock4j
和mybatis-plus
差不多,秉承着人性化使用的初衷,lock4j
使用上还是非常简单的,只需要在需要上锁的接口方法上使用@Lock4j
进行标记并设置一些简单的超时参数即可。
使用中发现有一个需要上锁的接口是一个私有方法(原本单机情况下,采用ReentrantLock
),所以改造时仅仅只是对改接口上添加@Lock4j
注解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public void saveInfo(String userId){ .... this.finalSaveEffective(userId); }
@Lock4j(keys = {"#userId"}, expire = "18000", acquireTimeout = "10000", keyBuilder = DefaultKeyBuilder.class) private void finalSaveEffective(String userId) { try{ checkInfo(..); mapper.insert(..); }catch(Exception e){ log.error(e.getMessage(), e); throw new BusinessException(e.getMessage()); } finally{ } }
|
跑起来测试后发现根本没有进入到对@Lock4j
处理的代理方法中,马上反应到这不是和常见@Transaction
的坑一样,不能使用this.xxx
的方式调用,修改代码后启动,果然正常上锁,那么为什么this.xxx
会导致异常?
为了知其所以然,不再盲猜硬记,我决定结合@Transaction
注解去了解底层的机制。
在spring
中的aop
说白了就是通过动态代理实现,而动态代理有两种实现方式(jdk
动态代理和cglib
动态代理)。这里简单模拟一下两种动态代理的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
|
public interface UserFacade {
void insertUserInfo();
void getUserInfo();
}
public class UserService implements UserFacade{
@Override public void insertUserInfo() { getUserInfo(); System.out.println("插入用户信息"); }
@MockAnnotation @Override public void getUserInfo() { System.out.println("查询图书信息"); } }
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface MockAnnotation { }
|
1. jdk动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
public class UserFacadeJdkProxy implements InvocationHandler {
private Object target;
public Object getProxy(Object target){ this.target = target; return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); }
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("进入调用方法");
Annotation annotation = target.getClass() .getDeclaredMethod(method.getName(), method.getParameterTypes()) .getAnnotation(MockAnnotation.class); if(ObjectUtils.isEmpty(annotation)){ return method.invoke(target, args); } System.out.println("aop某方法的前置处理"); Object result = method.invoke(target, args); System.out.println("aop某方法的后置处理"); return result; }
public static void main(String[] args) { UserFacadeJdkProxy proxy = new UserFacadeJdkProxy(); UserFacade userFacade = (UserFacade) proxy.getProxy(new UserService()); userFacade.insertUserInfo(); } }
|
2.cglib动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
public class UserFacadeCglibProxy implements MethodInterceptor {
private Object target;
public Object getProxy(Object target){ this.target = target; Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(this.target.getClass()); enhancer.setCallback(this); return enhancer.create(); }
@Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { System.out.println("进入调用方法");
Annotation annotation = target.getClass().getDeclaredMethod(method.getName(), method.getParameterTypes()) .getAnnotation(MockAnnotation.class); if(ObjectUtils.isEmpty(annotation)){ return methodProxy.invoke(target, args); } System.out.println("aop某方法的前置处理"); Object result = methodProxy.invoke(target, args); System.out.println("aop某方法的后置处理"); return result; }
public static void main(String[] args) { UserFacadeCglibProxy proxy = new UserFacadeCglibProxy(); UserFacade userFacade = (UserFacade)proxy.getProxy(new UserService()); userFacade.insertUserInfo(); } }
|
由以上可得出结论:不论是通过哪一种动态代理实现AOP
,使用this.xxx
的写法都无法使得注解生效。且如果注解标记的方法为final
或者是private
方法也是不能进入代理方法,原因是jdk
动态代理是基于接口代理、cglib
动态代理是基于继承的方式,不论是那种方式的动态代理的代理对象其实都是无法进入target
的私有方法和final
方法。所以根据以上动态代理可以得出结论,通过this.xxx
的方式调用本类接口是通过被代理对象直接调用本类接口,而不是通过代理对象,也就无法进入对应的invoke
方法或者是intercept
方法,从而无法解析到对应的注解,如果觉得将this.xxx
修改为A
类调用B
类,代码需要被迫转移到其他类影响理解,在实际中可以通过ApplicationContextUtil
的方式获取一次代理对象。
1 2 3
| UserFacade userFacade = ApplicationContextUtil.getBean("userService"); userFacade.getUserInfo();
|
但是,好玩的来了,注意看UserFacadeCglibProxy
类中有两行注释,采用的是methodProxy.invokeSuper(o, args);
的方式调用,运行之后可以发现通过this.xxx
的方式竟然可以进入invoice()
方法。
1 2 3 4 5 6 7
| > Task :UserFacadeCglibProxy.main() 进入调用方法 进入调用方法 aop某方法的前置处理 查询图书信息 aop某方法的后置处理 插入用户信息
|
这边简单对这两个调用进行一个区别:
methodProxy.invoke(target, args)
的整个执行过程为:
- 客户端调用了代理对象的
insertUserInfo()
方法
- 进入代理对象的
intercept
方法
- 通过
methodProxy.invoke(target, args)
执行被代理对象的insertUserInfo()
- 这时的
this.getUserInfo()
中的this
是被代理对象,所以调用时不会触发intercept
方法
- 调用结束
methodProxy.invokeSuper(o, args)
的整个执行过程为:
- 客户端调用了代理对象
insertUserInfo()
方法
- 进入代理对象的
intercept
方法
- 通过
methodProxy.invokeSuper(o, args)
进入被代理对象的insertUserInfo()
- 这时的
this.getUserInfo()
中的this
是代理对象,所以getUserInfo()
会再次触发intercept()
- 进入被代理对象的
getUserInfo()
- 调用结束
所以最终作怪的是this
,这个this
代表的是代理对象(proxy
)还是被代理对象(target
)。