SpringBoot中AspectJ的使用
AspectJ作为语言级别的AOP框架,功能相比于SpringAOP更加强大。SpringAOP旨在提供给用户一个轻量级的AOP实现方案,它只能应用在SpringIOC容器中管理的bean。而AspectJ旨在提供给用户一个完整的AOP解决方案,它可以应用在所有的域对象中。
- AspectJ织入代码方式
AspectJ在织入代码时,有三种不同类型的编织:
官网对于织入时机的解释
- 编译时织入(CTW)
- 编译后织入 (BTW)
- 类加载时织入 (LTW)
- SpringAOP织入代码方式
SpringAOP是基于动态代理来实现的,在运行期通过接口或者子类的方式来实现AOP。在SpringAOP中主要有两种:
- JDK动态代理(基于接口实现)
- CGLib动态代理(基于类实现)
本文主要介绍AspectJ在SpringBoot中的两种实现(CTW、LTW)
1. CTW的实现
使用CTW的方式织入时,需要采用特殊的编译器(ajc)来进行编译
- IDEA的相关配置
在Setting将Java Complier替换为Ajc编译器
注意:必须现在项目中导入aspectjtools.jar
在Project Structure中导入依赖包aspectjrt.jar
在facets中添加AspectJ
- 相关设置
aop.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver>
<include within="com.zakary.qingblog..*" />
</weaver>
<aspects>
<aspect name="com.zakary.qingblog.aop.LoginAspect"/>
<aspect name="com.zakary.qingblog.aop.InterceptorAspect"/>
<aspect name="com.zakary.qingblog.aop.ParamsCheckAspect"/>
</aspects>
</aspectj>
- 切面实现
切面
@Aspect
@DeclarePrecedence("InterceptorAspect,LoginAspect,ParamsCheckAspect")
public class LoginAspect {
private Logger logger= LoggerFactory.getLogger(QingblogApplication.class);
/**
* 匹配规则
* execution: 用于匹配方法执行的连接点;
* execution(public * *(..)) ==> 匹配所有目标类的public方法,第一个*代表返回类型,第二个*代表方法名,而..代表任意入参的方法。
* execution(* com.oysept.springboot.controller..*.*(..)) ==> 该包及所有子包下任何类的任何方法。
* execution(* com.oysept.springboot.controller.*(..)) ==> 该包下任何类的任何方法。
* execution(* com.oysept.springboot.controller.AspectJController.*(..)) ==> 该包下AspectJController类的任何方法。
* execution(* com..*.*Controller.method*(..)) ==> 匹配包名前缀为com的任何包下类名后缀为Controller的方法,方法名必须以method为前缀。
* execution(* *To(..)) ==> 匹配目标类所有以To为后缀的方法。
* 注: 该方法只是为了声明一个公共的环绕通知,也可以直接在具体方法配置,如: @Around("execution(* com.oysept.springboot.controller..*.*(..))")
*/
@Pointcut("execution(* com.zakary.qingblog.controller.LoginController.userLogin(..))")
public void loginAop() {}
@Before("loginAop()")
public void before(JoinPoint point) throws Throwable {
Object[] objArgs = point.getArgs();
String mail= AnalysisUtils.getObjectToMap(objArgs[0]).get("userMail").toString();
String password=AnalysisUtils.getObjectToMap(objArgs[0]).get("userPassword").toString();
logger.info("User Login : [ userMail : "+mail+"\t , password : "+password+"\t ]");
}
}
切入点
@Controller
public class LoginController {
@Autowired
private LoginService loginService;
@RequestMapping("/userLogin")
@ResponseBody
public JSONResult userLogin(@RequestBody @Validated({ValidationGroups.LoginGroup.class}) User user, HttpServletRequest request){
User user1=loginService.login(user);
HttpSession session=request.getSession();
session.setAttribute("userId",user1.getUserId());
return JSONResult.ok("success");
}
}
编译后的class文件
@Controller
public class LoginController {
@Autowired
private LoginService loginService;
public LoginController() {
}
@RequestMapping({"/userLogin"})
@ResponseBody
public JSONResult userLogin(@RequestBody @Validated({LoginGroup.class}) User user, HttpServletRequest request) {
JoinPoint var5 = Factory.makeJP(ajc$tjp_0, this, this, user, request);
JSONResult var9;
try {
LoggerAspect.aspectOf().before(var5);
LoginAspect.aspectOf().before(var5);
ParamsCheckAspect.aspectOf().beforeLogin(var5);
User user1 = this.loginService.login(user);
HttpSession session = request.getSession();
session.setAttribute("userId", user1.getUserId());
var9 = JSONResult.ok("success");
} catch (Throwable var10) {
LoggerAspect.aspectOf().releaseResource(var5);
throw var10;
}
LoggerAspect.aspectOf().releaseResource(var5);
return var9;
}
static {
ajc$preClinit();
}
}
图中执行了多个AspectOf方法,因为我在此处实现了多个切面
2. LTW的实现
- IDEA的相关配置
RUN->Edit Configurations->Configuration->Main Class->Environment->VM options填入
-javaagent:"C:\Users\Zakary.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar"
-javaagent:"C:\Users\Zakary.m2\repository\org\springframework\spring-instrument\5.1.9.RELEASE\spring-instrument-5.1.9.RELEASE.jar"
此包在maven仓库中可以下载 - application.yml
spring:
aop:
auto: false
关闭Springaop
设置ltw CustomLtwConfig.java
@Configuration
@ComponentScan("com.zakary.qingblog.controller")
@EnableLoadTimeWeaving(aspectjWeaving=ENABLED)
public class CustomLtwConfig{
}
注:aspectjWeaving有三个值,ENABLED为强制使用LTW的方式,DISENABLED不使用,AUTODETECT为查找META-INF下有无aop.xml文件,如果没有,不使用LTW,有的话使用LTW
- 切面实现同上
3. 多切面执行顺序的解决方案
- 如果采用SpringAop时,同一个切点含有多个advice时,使用@order注解来决定每个advice的执行顺序,其中value值越小越先执行。但@order不能使用在aspectj的切面中,@order只能决定SpringIOC容器中的bean。
- 采用AspectJ时,使用注解@DeclarePrecedence,参数为切面类,
例如
@DeclarePrecedence("InterceptorAspect,LoginAspect,ParamsCheckAspect")