Spring AOP 嵌套使用时内部方法注解失效问题

排污权做登录日志时遇到的一个问题,
错误代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@LoginSuccessLog
public JSONObject login(JSONObject user) {
/*......多余代码省略......*/
//该处有问题
SysUser sysUser = validate(loginCode, password);
/*......多余代码省略......*/
return result;
}
 
@LoginErrorLog
public SysUser validate(String loginCode, String password) {
/*......多余代码省略......*/
return sysUser;
}

由于三端登录时,校验用户名密码等信息与实际登录终端处于不同的步骤,所以我定义了两个切点分别指向的是实际登录方法与用户合法性校验的方法,采用的是注解的方式。
但由于企业端不属于三端登录,所以之前的代码结构是login()方法中嵌套了vaildate()方法,及以上代码块中的代码。
之后再测试时发现vaildate()方法的注解失效了。

查阅资料后得知,方法嵌套调用时是调用的当前对象而非代理对象,及实际调用方式为this.vaildate(),故不生效。

==spring获取的bean都是其代理对象,而不是bean对象本身。嵌套方法中的内部方法都是对其bean对象本身的方法的调用,所以都不会被代理拦截。==
如:

1
2
3
4
5
6
7
8
private void outerMethod(){
innerMethod();
}

@annotationExample
private void innerMethod(){

}

该innerMethod()方法是不会被AOP捕捉的。
所以解决问题的思路就很明了了:
一、生成新的代理对象(建立新类)
二、获取当前类的实际代理对象并进行调用
三、不使用SpringAOP提供的JDK或者Cglib的方式进行织入,改用AspectJ(编译期织入)

1
2
3
4
5
AOP织入方式简介  
织入方式共三种:
1. 编译期织入
2. 类加载期织入
3. 运行期织入

我们一般用的Spring AOP都属于运行期织入
Spring AOP会根据你是否是属于JDK标准的动态代理来决定是否采用CGlib的方式实现(及目标类是接口等抽象或是真实实现,如果是接口则属于标准的JDK动态代理实现,如果是实现类则采用Cglib生成子类并覆盖方法。)

AspectJ则采用的是编译期织入,不属于动态代理,所以不受嵌套方法的影响


实际可行的解决方法有以下三种:

  1. 将vaildate()方法单独放到一个类中去(推荐)

本来校验业务与登录业务就不应该耦合在一起,其实我觉得当初这种用户校验操作也可以用AOP的方式,登录就是登录,校验就是校验。不过时间问题就不重构了,修改后的代码如下:
LoginService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service("ETSLoginService")
public class LoginService {
//注入校验类
@Autowired
private LoginValidator loginValidator;
/**
* [简述]:用户登陆
* @param user
* @return
*/
@LoginSuccessLog
public JSONObject login(JSONObject user) {
/*......多余代码省略......*/
//改为调用其他类的方式,来代替原有this.validate()的调用方式。
SysUser sysUser = loginValidator.validate(loginCode, password);
/*......多余代码省略......*/
return result;
}
}

LoginValidator.java

1
2
3
4
5
6
7
8
@Service
public class LoginValidator {
@LoginErrorLog
public SysUser validate(String loginCode, String password) {
/*......多余代码省略......*/
return sysUser;
}
}
  1. 通过AopContext获得本类代理类对象

1
SysUser sysUser = validate(loginCode, password);  

替换为

1
SysUser sysUser = ((LoginService)AopContext.currentProxy()).validate(loginCode, password);  

同时

1
<aop:aspectj-autoproxy proxy-target-class="true"/>

修改为

1
<aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
  1. 通过ApplicationContext对象的getBean()获取当前类的代理对象
    1
    ((LoginService)applicationContext.getBean("loginService")).validate(loginCode, password);