目录
1. 以增加方法执行时间为例使用AOP
1.1 引入AOP依赖
1.2 编写AOP程序
2. AOP的重要概念
3. AOP通知类型与通知方法标注
3.1 在通知方法前使用对应注解
3.2 使用@Pointcut注解提取公共切点表达式
3.3 跨类使用切点
3.4 切面类排序
1. 以增加方法执行时间为例使用AOP
1.1 引入AOP依赖
在pom.xml中增加关于AOP的配置:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
1.2 编写AOP程序
以图书管理系统为例,为每个方法增加耗时计算与日志打印。
创建aspect包,在其下创建TimeAspect类:
package com.example.bookmanagementsystem.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class TimeAspect {@Around("execution(* com.example.bookmanagementsystem.controller.*.*(..))")public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {long start=System.currentTimeMillis();// 执行目标方法Object result=joinPoint.proceed();long end=System.currentTimeMillis();log.info(joinPoint+"消耗时间:"+(end-start)+"ms");return result;}
}
关于注解:
1、@Aspect:表示该类是一个切面类;
2、@Around:用于指明切面类的作用域与作用方式(在哪个环节,对哪些方法);
关于切面类成员方法:
1、timeCost方法的参数是一个ProceedingJoinPoint类型的对象,表示目标方法;
2、代码被分为三大部分:
2. AOP的重要概念
1、切点:一组通过表达式描述的规则;
2、连接点:切面作用/描述的方法,即被AOP控制的目标方法;
3、通知:具体要做处理的逻辑;
4、切面:切点+通知即切面,一个类可以有多个切面;
以上述切面类为例:
3. AOP通知类型与通知方法标注
1、@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行;(最常用)
2、@Before:前置通知,此注解标注的通知方法在目标方法前被执行;
3、@After:
后置通知,此注解标注的通知方法在目标方法后被执行,无论是否发生异常都会执行;
4、@AfterReturning:
返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行;
5、@AfterThrowing:异常后通知,此注解标注的通知方法在异常后被执行;
3.1 在通知方法前使用对应注解
现在controller包下创建一个HelloController类,编写方法作为目标方法:
由于@AfterReturning与@AfterThrowing标注的方法在异常发生与否时返回不同,故编写两个方法使其满足一个正常运行,一个运行时报异常:
package com.zhouyou.demos.controller;import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class HelloController {@RequestMapping("/test1")public String test1(){return "hello";}@RequestMapping("/test2")public int test2(){return 10/0;}
}
在aspect包下创建TestAspect类,用于编写通知方法:
package com.zhouyou.demos.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class AspectDemo {@Around("execution(* com.zhouyou.demos.controller.*.*(..))")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("AspectDemo around 前");Object result=joinPoint.proceed();log.info("AspectDemo around 后");return result;}@Before("execution(* com.zhouyou.demos.controller.*.*(..))")public void doBefore(){log.info("AspectDemo before");}@After("execution(* com.zhouyou.demos.controller.*.*(..))")public void doAfter(){log.info("AspectDemo after");}@AfterReturning("execution(* com.zhouyou.demos.controller.*.*(..))")public void doAfterReturning(){log.info("AspectDemo afterReturning");}@AfterThrowing("execution(* com.zhouyou.demos.controller.*.*(..))")public void doAfterThrowing(){log.info("AspectDemo afterThrowing");}
}
启动项目,依次根据路由映射访问test1方法和test2方法,可查看日志观察通知方法执行顺序:
3.2 使用@Pointcut注解提取公共切点表达式
上述使用方法中,在使用对应注解标注通知方法时,需要重复编写公共切点表达式,这很不方便;
可使用@Pointcut提取公共切点表达式,在后续使用公共切点表达式时则直接使用@Pointcut标注的方法名即可;
修改3.1中的代码
package com.zhouyou.demos.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class AspectDemo {@Pointcut("execution(* com.zhouyou.demos.controller.*.*(..))")private void pc(){}@Around("pc()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {log.info("AspectDemo around 前");Object result=joinPoint.proceed();log.info("AspectDemo around 后");return result;}@Before("pc()")public void doBefore(){log.info("AspectDemo before");}@After("pc()")public void doAfter(){log.info("AspectDemo after");}@AfterReturning("pc()")public void doAfterReturning(){log.info("AspectDemo afterReturning");}@AfterThrowing("pc()")public void doAfterThrowing(){log.info("AspectDemo afterThrowing");}
}
3.3 跨类使用切点
现在aspect包中再创建一个切面类aspectDemo2,以@Before为例使用aspectDemo的切点。
package com.zhouyou.demos.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Slf4j
@Component
public class AspectDemo2 {@Before("com.zhouyou.demos.aspect.AspectDemo.pc()")public void doBefore(){log.info("AspectDemo2 doBefore");}
}
启动项目,根据方法路由映射即可在日志处观察到通知方法的具体执行顺序情况;
使用方法注意事项:
(1)在跨类使用公共切点时,需要使用全限定类名;
(2)在当前类中,若在其类中实现的切点需要在其他类中使用,则切点必须以public修饰。
使用private修饰的切点只能在当前类中使用;
3.4 切面类排序
现aspect包下由AspectDemo、AspectDemo1、AspectDemo2三个切面类,并在每个切面类中实现@Before通知和@After通知。
创建目标方法并指定路由映射,并进行访问,查看日志输出:
在采取默认排序的情况下,默认采取按照切面类的类名字母排序:
对于@Before通知,排名越靠前的先执行;
对于@After通知,排名越靠后的先执行;
但这并不便于管理,Spring提供了@Order注解,用于给切面类设置优先级:
对于@Before通知,@Order中的数字越小越先执行;
对于@After通知,@Order中的数字越大越先执行;
现对AspectDemo切面类使用Order(3)、AspectDemo1切面类使用Order(2)、AspectDemo2切面类使用Order(1),启动程序查看日志输出: