AOP:面向切面编程
注意:AOP并不是Spring原创的技术,也不是Spring独家技术,而是源自AspectJ,只是Spring很好的支持了AOP。
AOP技术只要解决了“横切关注”的相关问题,也就是“若干个不同的方法都需要执行相同的任务”的问题!
@Slf4j
@Aspect // 标记当前类是一个切面类
@Component // 标记当前类是一个组件类,则Spring会进行处理
public class TimerAspect {
// 连接点(JoinPoint):程序执行过程中的某个节点,可能是某个方法的调整,或是某个方法抛出了异常
// 切入点(PointCut):匹配连接点的表达式
// -------------------------------------------------------------------
// 通知(Advice)注解:
// @Around:环绕,可以包裹连接点的执行,所以,你可以在执行连接点之前和之后编写你所需的代码
// @Before:在执行连接点之前
// @After:在执行连接点之后
// @AfterThrowing:在抛出异常之后
// @AfterReturning:在成功返回之后
// -------------------------------------------------------------------
// 各通知的执行点大致是以下这样:
// @Around开始
// try {
// @Before
// 执行连接点
// @AfterReturning
// } catch (Throwable e) {
// @AfterThrowing
// } finally {
// @After
// }
// @Around结束
// -------------------------------------------------------------------
// 切入点表达式:匹配某些方法
// 可以使用通配符:
// - 星号(*):任意有且仅有1次匹配
// - 2个连接的小数点(..):任意n次匹配,仅能用于包名和参数列表
// ↓ 返回值类型
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 包名
// ↓ 类名
// ↓ 方法名
// ↓↓ 参数列表
// 在切入点表达式中,在返回值类型声明的左侧,还可以指定修饰符,修饰符是可选的
// 如果没有显式的指定修饰符,则无论什么修饰符都可以匹配
// 其实,其它部分也是如此,例如,如果没有指定包名,则无视匹配的类在哪个包下
// 注意:注解是典型的修饰符
// 注意:无论是注解,还是返回值、参数,只要不是基本数据类型、java.lang包下的类型,都必须使用全限定名
@Around("execution(* cn.*.tea.admin.server..service.*.*(..))")
// ↓↓↓↓↓↓ 使用@Around时必须声明为ProceedingJoinPoint对象调用proceed()返回的结果
// ↓↓↓↓↓ 自定义的方法名
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 使用@Around时必须是ProceedingJoinPoint类型
// ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 必须抛出
public Object timer(ProceedingJoinPoint pjp) throws Throwable {
log.trace("TimerAspect.timer()开始执行");
// 获取连接点信息
String targetClassName = pjp.getTarget().getClass().getName(); // 类型名
String signatureName = pjp.getSignature().getName(); // 方法名
Object[] args = pjp.getArgs(); // 参数列表
System.out.println("连接点(方法)所属的类的名称:" + targetClassName);
System.out.println("连接点(方法)的名称:" + signatureName);
System.out.println("连接点(方法)的参数列表:" + Arrays.toString(args));
// 获取起始时间
long start = System.currentTimeMillis();
// 调用ProceedingJoinPoint参数对象的proceed()方法,表示执行连接点,即调用了匹配到的方法
// 注意-1:调用proceed()方法的异常,必须抛出,不可以简单的try...catch(除非在catch中再抛出异常)
// 注意-2:调用proceed()方法时返回的结果,表示对应的Service方法的返回值,必须作为当前切面方法的返回值
Object proceedResult = pjp.proceed();
// 获取结束时间
long end = System.currentTimeMillis();
// 输出
System.out.println("执行耗时:" + (end - start));
log.trace("TimerAspect.timer()执行结束,即将返回");
return proceedResult;
}
}