AspectJ是什么?
使用Java代码进行面向切面编程(AOP),如果类的继承是纵向复用代码,那AOP就是横向复用代码,横向地在多个没有类继承关系的类之间复用代码。
AspectJ是怎么实现的?
通过在编译时,在编译出来的类的字节码文件中,动态地添加我们想要的功能(有些博客说是有三种方式织入字节码,分别是编译时、编译后、加载前,这三种可以统称为运行前),对类和方法进行增强,这就叫做织入,在Java中,是使用一个叫做aspectjweaver的第三方包来使用AspectJ,weaver就是织入的意思。
AspectJ和SpringAOP之间的关系
相比于SpringAOP使用动态代理来对类进行增强,AspectJ有着更好的性能,因为AspectJ在编译时,就直接修改了目标类的字节码文件,运行时就不用再做修改了。
而SpringAOP是JVM运行时再生成代理类的字节码文件,再通过反射(JDK动态代理使用发射创建代理类,或者调用目标类)或者调用父类的方式对目标类的方法进行调用,有着更多的时间和空间开销。
AspectJ也有着更为丰富的功能,比如SpringAOP如何使用CGLIB,则无法对被标注为final的类或者方法进行继承或者重写,而AspectJ因为是运行前织入字节码,则没有这个限制。
AspectJ和JDK动态代理、CGLib之间的关系
AspectJ是一种可以在Java代码中运用AOP的工具,我们一般是通过第三方包aspectjweaver来使用它。
JDK动态代理是Java自带的使用代理模式的JDK官方包,我们可以在不引入任何第三方包的情况下使用它,但是JDK动态代理一般不是使用AOP的直接方式,CGLIB和JDK动态代理一样,都是使用动态代理模式的一种工具或者包,都是在运行时生成代理类,而SpringAOP就是通过动态代理来实现AOP的。
SpringBoot项目中使用AspectJ
- 引入三方依赖
spring-aop:AOP核心功能,例如代理工厂等等。这里不需要引入。
aspectjweaver:简单理解,支持切入点表达式等等。
aspectjrt:简单理解,支持aop相关注解等等。
由于aspectjweaver是包含aspectjrt,所以只需要引入aspectjweaver即可。
在pom.xml文件中引入其他依赖。
<!-- 1. 父工程依赖:父工程设置为springboot,则当前工程就是springboot工程 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.14</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>
</dependencies>
- 创建需要被切面切中的类
- 创建切面
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
//@EnableAspectJAutoProxy // 不需要添加这个注解,就能运行
public class ControllerAspect {
/**
* 此方法只是定义切面,具体切面会怎样对目标类和方法进行增强,由后面的@Before、@Around等注解的方法进行指定。
*/
@Pointcut("execution(* com.qqcr.train.aspectjweaver.controller..*.*(..))")
private void testControllerPointcut() {
}
/**
* 对testControllerPointcut()方法上的注解@Pointcut定义的切面进行前置操作。
*
* @param joinPoint 通过joinPoint可以获取方法的全限定名称、参数等信息
*/
@Before("testControllerPointcut()")
public void doBefore(JoinPoint joinPoint) {
String method = joinPoint.getSignature().getName();
String declaringTypeName = joinPoint.getSignature().getDeclaringTypeName();
log.info("------@Before:class:{},method:{}", declaringTypeName, method);
}
/**
* 对testControllerPointcut()方法上的注解@Pointcut定义的切面进行环绕操作。
* 环绕通知可以调用真正的方法,具体的调用是joinPoint.proceed();
*
* @param joinPoint 通过joinPoint可以获取方法的全限定名称、参数等信息
*/
@Around("testControllerPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
Object obj = joinPoint.proceed();
long end = System.nanoTime();
String method = joinPoint.getSignature().getName();
log.info("------@Around:方法" + method + "执行时间: " + (end - start) + " ns");
return obj;
}
}
- 创建启动类
@SpringBootApplication
public class AspectApplication {
public static void main(String[] args) {
SpringApplication.run(AspectApplication.class, args);
}
}
- 测试
启动测试类,通过页面或者postman访问接口http://localhost:8080/aspect/hello,打印日志如下
可以看到,只有/hello接口所在的方法被切面命中了,而test()方法和staticTest()方法都没有被切面命中,与其他博客中的效果不一样,其他的博客中,test()和staticTest()也被切面命中了。
2024-04-14 21:30:26.948 INFO 4172 --- [nio-8080-exec-1] c.q.t.a.config.ControllerAspect : ------@Before:class:com.qqcr.train.aspectjweaver.controller.MyController,method:hello
2024-04-14 21:30:26.951 INFO 4172 --- [nio-8080-exec-1] c.q.t.a.controller.MyController : ------hello() 开始运行---
2024-04-14 21:30:26.951 INFO 4172 --- [nio-8080-exec-1] c.q.t.a.controller.MyController : test()方法运行了
2024-04-14 21:30:26.951 INFO 4172 --- [nio-8080-exec-1] c.q.t.a.controller.MyController : staticTest()方法运行了
2024-04-14 21:30:26.951 INFO 4172 --- [nio-8080-exec-1] c.q.t.a.controller.MyController : ------hello() 结束运行---
2024-04-14 21:30:26.951 INFO 4172 --- [nio-8080-exec-1] c.q.t.a.config.ControllerAspect : ------@Around:方法hello执行时间: 3799700 ns
参考
Spring基础 - Spring核心之面向切面编程(AOP)