0%

this调用导致注解未生效问题

动态代理导致注解未生效

​ 今天在使用mybatis-plus作者苞米豆的另一个项目lock4j用于项目中的分布式锁,解决多实例情况下对接口进行上锁,使得业务上的共享资源在一个时间节点里只会被一个线程执行。

lock4jmybatis-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) {
//reentrantLock.lock();
try{
//
checkInfo(..);
mapper.insert(..);
}catch(Exception e){
log.error(e.getMessage(), e);
throw new BusinessException(e.getMessage());
} finally{
//reentrantLock.unlock();
}
}

​ 跑起来测试后发现根本没有进入到对@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
/**
* 顶层接口
* @Author: xiaocainiaoya
* @Date: 2021/04/16 22:30:50
**/
public interface UserFacade {

void insertUserInfo();

void getUserInfo();

}

/**
* 具体实现
* @Author: xiaocainiaoya
* @Date: 10:28 下午
**/
public class UserService implements UserFacade{

@Override
public void insertUserInfo() {
getUserInfo();
System.out.println("插入用户信息");
}

@MockAnnotation
@Override
public void getUserInfo() {
System.out.println("查询图书信息");
}
}


/**
* 模拟注解
*
* @Author: xiaocainiaoya
* @Date: 2021/04/16 22:32:27
**/
@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
/**
* jdk动态代理的形式
*
* @Author: xiaocainiaoya
* @Date: 2021/04/16 22:33:07
**/
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();
//userFacade.getUserInfo();
}
}
// 执行结果
//> Task :UserFacadeJdkProxy.main()
//进入调用方法
//查询图书信息
//插入用户信息
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
/**
* cglib方式代理
*
* @Author: xiaocainiaoya
* @Date: 2021/04/16 22:34:15
**/
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);
//return methodProxy.invokeSuper(o, args);
}
System.out.println("aop某方法的前置处理");
Object result = methodProxy.invoke(target, args);
//Object result = methodProxy.invokeSuper(o, 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.getUserInfo();
userFacade.insertUserInfo();
}
}
// 执行结果
//> Task :UserFacadeCglibProxy.main()
//进入调用方法
//查询图书信息
//插入用户信息

由以上可得出结论:不论是通过哪一种动态代理实现AOP,使用this.xxx的写法都无法使得注解生效。且如果注解标记的方法为final或者是private方法也是不能进入代理方法,原因是jdk动态代理是基于接口代理、cglib动态代理是基于继承的方式,不论是那种方式的动态代理的代理对象其实都是无法进入target的私有方法和final方法。所以根据以上动态代理可以得出结论,通过this.xxx的方式调用本类接口是通过被代理对象直接调用本类接口,而不是通过代理对象,也就无法进入对应的invoke方法或者是intercept方法,从而无法解析到对应的注解,如果觉得将this.xxx修改为A类调用B类,代码需要被迫转移到其他类影响理解,在实际中可以通过ApplicationContextUtil的方式获取一次代理对象。

1
2
3
// this.getUserInfo(); 修改为一下写法
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)的整个执行过程为:

  1. 客户端调用了代理对象的insertUserInfo()方法
  2. 进入代理对象的intercept方法
  3. 通过methodProxy.invoke(target, args)执行被代理对象的insertUserInfo()
  4. 这时的this.getUserInfo()中的this是被代理对象,所以调用时不会触发intercept方法
  5. 调用结束

methodProxy.invokeSuper(o, args)的整个执行过程为:

  1. 客户端调用了代理对象insertUserInfo()方法
  2. 进入代理对象的intercept方法
  3. 通过methodProxy.invokeSuper(o, args)进入被代理对象的insertUserInfo()
  4. 这时的this.getUserInfo()中的this是代理对象,所以getUserInfo()会再次触发intercept()
  5. 进入被代理对象的getUserInfo()
  6. 调用结束

所以最终作怪的是this,这个this代表的是代理对象(proxy)还是被代理对象(target)。

-------------本文结束感谢您的阅读-------------