1 简介
AOP,即面向切面编程是很常用的技术,特别是在Java Web开发中。而最流行的AOP框架分别是Spring AOP和AspectJ。
2 Spring AOP vs AspectJ
Spring AOP是基于Spring IoC实现的,它解决大部分常见的需求,但它并不是一个完整的AOP解决方案。对于非Spring容器管理的对象,它更没有办法了。而AspectJ旨在提供完整的AOP方案,因此也会更复杂。
2.1 织入方式
两者织入方式有极大的不同,这也是它们的本质区别,它们实现代理的方式不同。
AspectJ是在运行前织入的,分为三类:
编译时织入
编译后织入
加载时织入
因此需要AspectJ编译器(ajc)的支持。
而Spring AOP是运行时织入的,主要使用了两种技术:JDK动态代理和CGLIB代理。对于接口使用JDK Proxy,而继承的使用CGLIB。
![](https://i-blog.csdnimg.cn/blog_migrate/05eaf910d1a5d7ddb727b0991bc167d5.png)
2.2 Joinpoints
因为织入方式的区别,两者所支持的Joinpoint也是不同的。像final的方法和静态方法,无法通过动态代理来改变,所以Spring AOP无法支持。但AspectJ是直接在运行前织入实际的代码,所以功能会强大很多。
Joinpoint | Spring AOP Supported | AspectJ Supported |
Method Call | No | Yes |
Method Execution | Yes | Yes |
Constructor Call | No | Yes |
Constructor Execution | No | Yes |
Static initializer execution | No | Yes |
Object initialization | No | Yes |
Field reference | No | Yes |
Field assignment | No | Yes |
Handler execution | No | Yes |
Advice execution | No | Yes |
2.3 性能
编译织入会比较运行时织入快很多,Spring AOP是使用代理模式在运行时才创建对应的代理类,效率没有AspectJ高。
3 Spring Boot使用AspectJ
因为AspectJ比较强大,在项目中应用会更多,所以这里只介绍它与Spring Boot的集成。
3.1 引入依赖
引入以下依赖,在Spring Boot基础上加了Lombok和aspectj:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>${aspectj.version}</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>${aspectj.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><scope>provided</scope></dependency></dependencies>
3.2 被AOP的对象
为了验证AOP的功能,我们添加一个TestController,它有一个处理Get请求的方法,同时会调用private的成员方法和静态方法:
@RestController@RequestMapping("/test")@Slf4jpublicclassTestController {
@GetMapping("/hello")public String hello() {
log.info("------hello() start---");
test();
staticTest();
log.info("------hello() end---");
return"Hello, pkslow.";
}
privatevoidtest() {
log.info("------test() start---");
log.info("test");
log.info("------test() end---");
}
privatestaticvoidstaticTest() {
log.info("------staticTest() start---");
log.info("staticTest");
log.info("------staticTest() end---");
}
}
3.3 配置Aspect
配置切面如下:
@Aspect@Component@Slf4j//@EnableAspectJAutoProxypublicclassControllerAspect {
@Pointcut("execution(* com.pkslow.springboot.controller..*.*(..))")privatevoidtestControllerPointcut() {
}
@Before("testControllerPointcut()")publicvoiddoBefore(JoinPoint joinPoint){
log.info("------pkslow aop doBefore start------");
Stringmethod= joinPoint.getSignature().getName();
StringdeclaringTypeName= joinPoint.getSignature().getDeclaringTypeName();
log.info("Method: {}.{}" ,declaringTypeName, method);
log.info("------pkslow aop doBefore end------");
}
@Around("testControllerPointcut()")public Object doAround(ProceedingJoinPoint joinPoint)throws Throwable {
log.info("------pkslow aop doAround start------");
longstart= System.nanoTime();
Objectobj= joinPoint.proceed();
longend= System.nanoTime();
log.info("Execution Time: " + (end - start) + " ns");
log.info("------pkslow aop doAround end------");
return obj;
}
}
@Pointcut定义哪些类和方法会被捕抓来代理,这里配置的是controller下的所有方法。
而@Before和@Around则定义了一些处理逻辑。@Before是打印了方法名,而@Around是做了一个计时。
注意:是不需要配置@EnableAspectJAutoProxy的。
3.4 maven插件
因为是需要编译时织入代码,所以需要maven插件的支持:https://github.com/mojohaus/aspectj-maven-plugin
配置好pom.xml文件即可。
然后执行命令打包:
mvn clean package
这时会显示一些织入信息,大致如下:
[INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:14) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(java.lang.String com.pkslow.springboot.controller.TestController.hello())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:14) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.test())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:22) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:28) advised by around advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
[INFO] Join point 'method-execution(void com.pkslow.springboot.controller.TestController.staticTest())'in Type 'com.pkslow.springboot.controller.TestController' (TestController.java:28) advised by before advice from 'com.pkslow.springboot.aop.ControllerAspect' (ControllerAspect.class(from ControllerAspect.java))
看到以上信息,说明成功织入了代码,具体可以查看生成的class文件。
![](https://i-blog.csdnimg.cn/blog_migrate/fe6edcb0558c49cfa3033fa6dbeac270.png)
可以看到有许多代码都不是我们写的,而是织入生成。
3.5 执行及测试
编译成功后,我们就执行代码。如果是通过IDEA来执行,则在运行前不需要再build了,因为已经通过maven build过了包。通过IDEA自带的编译器build,可能无法织入。或者选择ajc作为编译器。具体请参考:IDEA启动Springboot但AOP失效
访问如下:
GET http://localhost:8080/test/hello
则日志如下,成功实现AOP功能:
![](https://i-blog.csdnimg.cn/blog_migrate/a48e3c2ae5b062c6bba5c722e9b39629.png)
3.6 一些遇到的错误
遇到错误:
ajc Syntax error, annotations are only available ifsource level is 1.5 or greater
需要配置插件:
<complianceLevel>${java.version}</complianceLevel><source>${java.version}</source><target>${java.version}</target>
可能还会遇到无法识别Lombok的错误,配置如下则解决该问题:
<plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.14.0</version><configuration><complianceLevel>${java.version}</complianceLevel><source>${java.version}</source><target>${java.version}</target><proc>none</proc><showWeaveInfo>true</showWeaveInfo><forceAjcCompile>true</forceAjcCompile><sources/><weaveDirectories><weaveDirectory>${project.build.directory}/classes</weaveDirectory></weaveDirectories></configuration><executions><execution><goals><goal>compile</goal></goals></execution></executions></plugin>
4 总结
AOP场景应用特别多,还是需要掌握的。