AOP为了将代码中非核心功能代码隔离出来。日志记录,事务管理,性能监测等功能通常分散在多个类中,导致代码重复,分散。这些非核心功能模块被称为横切关注点,而AOP允许将这些关注点集中到一块(切面)。相当于包装在一起,自动植入到相关代码中。
横切关注点:
就是与核心业务代码无关的,但是又贯穿多个模块的功能,例如:
- 日志记录:可以自动在每个方法调用前后记录日志。
- 事务管理:可以在数据库操作时,自动开启,提交,回滚事务。
- 性能测试:可以在方法执行前后记录监控方法的执行性能。
切面:
切面就是横切关注点的模块化实现,可以包换多个“通知”(Advice)和“切入点”(Pointcut),定义了在哪里实现,如何实现横切逻辑。根据发送的时间,分为:
- 前置通知(Before Advice):在方法调用前执行。
- 后置通知(After Advice):在方法调用后执行。
- 返回通知(After Returning Advice):在方法成功返回后执行。
- 异常通知(After Throwing Advice):在方法抛出异常后执行。
- 环绕通知(Around Advice):在方法执行的前后都执行,可以完全控制方法的执行过程。
切入点:
在Spring AOP中,切入点(Pointcut)是用来定义拦截哪些方法的。
连接点:
业务逻辑层中可以插入横切关注点的位置都叫做连接点。
织入:
织入是将切面和目标对象结合的过程。织入一般发送在运行时,通过动态代理来完成。
AOP的具体实现
//pom.xml中添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.2.11</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
切面定义:用”@Aspect”来注解这个类是个切面,再用”@Component”,把这个类交给Spring管理。
切入点的定义:规定需要插入增强的方法。
@Pointcut("execution(* org.cy.store.service.impl.UserServiceImpl.login(String,String))")
public void loginPointcut() {}
//execution后面加上切入点的路径以及方法,如果是同一个类下的很多方法,可以用.UserServiceImpl.*(..)这种方式标记。
//加上这段话之后,后面所有对这些方法的使用都可以用loginPointcut()代替。
@Before("loginPointcut()")
public void beforeLogin(JoinPoint joinPoint) {
System.out.println("beforeLogin");
}
切入点表达式的定义规则
execution表达式:
语法格式:( [修饰符模式] 返回类型 [类全路径] . 方法名(参数列表) [异常模式] )
- 修饰符模式(可选):匹配方法的访问修饰符,如public,private,如果不指定,匹配所有修饰符。
- 返回类型:匹配方法的返回类型,通配符 * 表示任意返回类型。
- 类全路径(可选):可以是类的全路径(如com.example.UserService),也可以是通配符 * 表示包含的子类。
- 方法名:匹配具体的方法名或使用通配符 * 表示任意方法。
- 参数列表:匹配方法的参数,可以是具体的类型(int,String),也可以是通配符 (我个人认为,这里是为了重载方法时,即使方法名不一样,也可以确定到某个具体的方法)。
- 异常模式(可选):匹配抛出的异常类型。
within表达式:
匹配特定类或包中的所有方法,常用于指定切入点的作用范围。无法确定到具体的某个函数,最多到类
args表达式:
匹配方法的参数类型,与execution()的参数部分类似,但是args()可以在运行时获取参数的实际裂隙,而不是编译时确定的类型。
this表达式:
匹配当前代理对象的类型,用于匹配代理类。
target表达式:
匹配目标对象的类型,它和this()类似,但是target()匹配的是目标对象的类型,而不是代理对象。
目标对象的匹配刚好和代理对象反过来,代理对象的匹配是遇到接口的实现类,回头去找接口类,而目标对象的匹配则是遇到接口类,去找该接口类的实现类。
- this() 通常填写接口类型,因为代理对象的类型是接口类型(尤其是在使用 JDK 动态代理时)。
- target() 通常填写实现类类型,因为目标对象的实际类型是实现类,而不管代理对象是什么类型。
@annotation():
匹配所有类上带有特定注解的方法。
@within():
匹配所有带有特定标记的类中的方法。
bean():
匹配Bean的名称
方法中的参数
JoinPoint的常用方法
| 方法 | 说明 |
| getArgs() | 返回目标方法的参数,是一个Object[]数组。 |
| getSignature() | 获取目标方法的签名(包括方法名,返回类型,参数类型等信息) |
| getTarget() | 获取目标对象 |
| getThis() | 获取代理对象 |
| getString() | 返回当前连接点的字符串表示。 |
ProceedingJoinPoint 的常用方法(继承与JoinPoint ,用于@Around通知)
| 方法 | 说明 |
| proceed() | 返回目标方法的参数,是一个Object[]数组。 |
| proceed(Object[] args) | 获取目标方法的签名(包括方法名,返回类型,参数类型等信息) |
| getArgs() | 返回目标方法的参数,是一个Object[]数组。 |
| getSignature() | 获取目标方法的签名(包括方法名,返回类型,参数类型等信息) |
| getTarget() | 获取目标对象 |
| getThis() | 获取代理对象 |
温馨提示(很重要)
由于AOP底层是通过生成代理对象来实施拦截,调用路径要经过代理对象,AOP增强逻辑才能实现。如果直接在内部调用另一个方法,就不会代理对象,而是调用自己本身的方法,所有不会触发AOP拦截。
上面段话简单来说,就是接口里面有的方法可以被拦截到,而实现接口的类里面新加的方法不会被拦截。只要在启动类或者AOP配置类上加上下面这段代码,Spring会强制使用CGLIB代理,就可以拦截到了。
@EnableAspectJAutoProxy(proxyTargetClass = true)
final类不能被代理,final方法不能被拦截
