2.4定义通知(Advice)
切点和通知的关系
Spring 切⾯类中,可以在⽅法上使⽤以下注解,设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
前置通知使⽤ @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
后置通知使⽤ @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
环绕通知使⽤ @Around:通知包裹了的⽅法(集合中的连接点),在被通知的⽅法收到通知之前和调⽤之后执⾏⾃定义的⾏为。
实现通知方法:在什么时机执行什么方法。
下面,我们以前置方法为例,演示一下。
三、实例展示(计时器)
有的人学的不错,说:
我们可以在 其值方法中 加一行代码,记录 开始时间。
然后,再在 后置方法中 记录 结束时间。
最后,两者相减,不就得到了 拦截到的方法的执行时间了嘛!
这样做,真的对吗? 是不对。
这得看情况。
如果是在单线程的环境下(同一时刻,只有一个线程在访问该方法),使用上述方式,没有问题。
但是!
在多线程的情况下,有多个用户访问 会被拦截下来的方法,每一次访问,都会调用 前置方法。
这会导致, 前置方法记录的开始时间,会不停被刷新(覆盖),最终记录的是 最后一个线程访问的时间。
后置方法,也是同样的情况。
也就是说我们最终相减的情况:
哪一次的开始时间 减去 哪一次 结束时间,我们都是无从获知的!
而且,得出非常多,数量取决访问的线程有多少。
那么,问题来了!
前面我不是说: AOP 可以统⼀⽅法执⾏时间的统计嘛。
但是,遇到问题了、
那么,我们该怎么做呢?
.有的人可能会说:这是线程安全问题,加锁呗!
对不起,不行!这就是全局的问题,你加锁也解决不了问题。
但是!我们不是剩一个 环绕通知吗?
解决的办法,就在这里。
下面,我们就来先了解一下 环绕通知。
环绕通知使⽤@Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为。
形象来说:环绕通知,就是把 整个连接点(方法)包裹起来了,那我们就可以“为所欲为”了。
比如说:
我们执行的方法 是在当前通知里面去执行的,所以,我们就可以针对每一个方法去记录开始时间和结束时间。
因为在每一次在执行目标方法(连接点)和 通知 的时候,它们是在一块的。给人的感觉就像是具有了 事务的原子性
代码实现
LoginAop代码
package com.example.demo.aop; import com.sun.corba.se.impl.ior.OldJIDLObjectKeyTemplate; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; // 切面表示我们要统一出来的问题——比如验证用户是否登录(LoginAop类) // 切点则是是否进行Aop拦截的规则(哪些页面不需要进行登录验证,哪些需要,这种规则) // 连接点则是具体到哪些页面需要进行拦截(登录验证) // 通知则是验证用户是否登录的那个具体方法实现(代码细节)——》前置通知,后置通知 // 验证当前是否登录 的aop实现类 @Component @Aspect // 表示当前类是一个切面 public class LoginAop { // 定义切点 @Pointcut("execution(* com.example.demo.controller.UserController.*(..))") public void pointcut() { // 标记切点 } // 前置通知 @Before("pointcut()") // 参数说明这个前置方法是针对与那个切点的 public void doBefore() { System.out.println("前置通知"); } // 后置通知 @After("pointcut()") public void doAfter() { System.out.println("后置通知"); } // 环绕通知 @Around("pointcut()") public Object doAround(ProceedingJoinPoint joinPoint) { StopWatch stopWatch = new StopWatch(); Object o = null; System.out.println("环绕通知开始执行"); try { stopWatch.start(); o = joinPoint.proceed(); stopWatch.stop(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("环绕通知结束执行"); System.out.println("执行花费的时间: " + stopWatch.getTotalTimeMillis() + "ms"); return o; } }