相关概念
- JoinPoint:拦截点。Spring 中,拦截点只支持方法。
- Pointcut:切入点,指要对哪些 JoinPoint 进行拦截。
- Advice(通知/增强):拦截到 JoinPoint 后执行的代码。
- 通知分为:前置、后置、异常、最终、环绕、引介通知
- Introduction:引介是一种特殊的通知,在不修改类代码的前提下,在运行期为类添加方法或属性。
- Target:目标,代理的目标对象
- Weaving:织入,指 为增强目标对象,创建代理对象的过程。
- Spring 传统 aop 采用动态代理,AspectJ 采用编译期织入和类加载期织入。
- Proxy:一个类被增强后,就产生一个结果代理。
- Aspect:切面,指切入点和通知的统称。
Spring 传统 AOP
- Spring 在运行期间,生成动态代理对象,不需要特殊编译器
- 如果目标类实现了接口,Spring 就采用 JDK 的动态代理,根据接口生成代理
如果目标类没有实现任何接口,Spring 就采用 CGLIB 生成目标类的子类进行增强。
应优先对接口创建代理,便于程序解耦
目标类和方法不要用 final 修饰,因为不能被代理,从而不生效
AspectJ 是基于 Java 语言的 AOP 框架
- Spring2.0 后整合了 AspectJ,较传统 Spring aop 更方便快捷
-
Pointcut 切入点声明
一个切入点表达式决定了哪些方法会真正被增强。
- 一个切入点标签包含一个名称和任意数量的参数。方法的真正内容是不相干的,并且实际上它应该是空的。
下面的示例中定义了一个名为 ‘businessService’ 的切入点,该切入点将与 com.tutorialspoint 包下的类中可用的每一个方法相匹配
import org.aspectj.lang.annotation.Pointcut;
@Pointcut("execution(* com.xyz.myapp.service.*.*(..))") // expression
private void businessService() {} // signature
下面的示例中定义了一个名为 ‘getname’ 的切入点,该切入点将与 com.tutorialspoint 包下的 Student 类中的 getName() 方法相匹配
@Pointcut("execution(* com.tutorialspoint.Student.getName(..))")
private void getname() {}
Advice 通知声明
通知类型:
- @Before 前置通知
- @AfterReturning 后置通知
- @Around 环绕通知
- @AfterThrowing 异常抛出通知
- @After 最终通知
- @DeclareParents 引介通知
假设已经定义了一个Pointcut方法 businessService():
@Before("businessService()")
public void doBeforeTask(){
...
}
也可直接设置 Pointcut
@Before("execution(* com.xyz.myapp.service.*.*(..))")
public doBeforeTask(){
...
}
示例:记录超时 Service 日志
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建 在 aspect 下创建 ServiceLogAspect
@Aspect
@Component
public class ServiceLogAspect {
public static final Logger log = LoggerFactory.getLogger(ServiceLogAspect.class);
/**
* AOP 通知
* 1. 前置通知:在方法调用前执行
* 2. 后置通知:在方法正常调用后执行
* 3. 环绕通知:在方法调用之前和之后,都分别可以执行的通知
* 4. 异常通知:在方法调用过程中发生异常时通知
* 5. 最终通知:在方法调用之后执行
*/
/**
* 切面表达式:
* execution 代表所要执行的表达式主体
* 参数一:方法返回类型 * 代表所有类型
* 参数二:aop 监控的类所在的包
* .. 代表该包及其子包下的所有类 * 代表所有类
* *(..) * 代表类中的方法名,(..) 表示方法中的任何参数
*/
@Around("execution(* com.ylq.service.impl..*.*(..))")
public Object recordTimeLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 输出类名 和方法名
log.info("====开始执行 {}.{}====", joinPoint.getTarget().getClass(), joinPoint.getSignature().getName());
long begin = System.currentTimeMillis();
// 执行目标 service
Object result = joinPoint.proceed();
long end = System.currentTimeMillis();
long takeTime = end - begin;
if (takeTime > 3000) {
log.error("====执行结束,耗时{}毫秒====", takeTime);
} else if (takeTime > 2000) {
log.warn("====执行结束,耗时{}毫秒====", takeTime);
} else {
log.info("====执行结束,耗时{}毫秒====", takeTime);
}
return result;
}
}