相关概念
- 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.*.*(..))") // expressionprivate 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@Componentpublic 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();// 执行目标 serviceObject 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;}}
