面向切面编程
AOP = 面向切面编程
AOP并不是Spring框架特有的功能,不依赖于Spring也可以实现AOP,只是Spring很好的支持了AOP!
常规的数据处理流程大致是:
注册:前端页面 -----> Controller -----> Service -----> Mapper
登录:前端页面 -----> Controller -----> Service -----> Mapper
改密:前端页面 -----> Controller -----> Service -----> Mapper
假设,需要在处理每种/每次请求时,都需要执行相同的某个任务,例如“统计业务层代码的执行耗时”,在传统的做法中,可以将“统计耗时”的代码封装在某个方法中,然后,在业务层的reg()
、login()
、changePassword()
中均调用这个“统计耗时”的方法即可!但是,在比较复杂的项目中,涉及的业务可能有成百上千个,就需要在成百上千个业务方法中都添加调用“统计耗时”的方法,不便于统一管理!
面向切面编程,是在数据处理流程中,假设存在某个切面,这个切面可以在任何位置,例如在Controller
与Service
之间,或在Service
与Mapper
之间,甚至可以同时存在多个切面,每个切面中都可以添加方法,当数据处理流程执行到切面时,就会执行切面中的方法!
在使用面向切面的编程时,只需要确定切面的位置,及切面中需要执行的代码即可,对原有的Controller
、Service
等组件中的代码不需要进行任何调整!
首先,需要添加aspectjtools
和aspectjweaver
依赖:(Springboot中居然没有自带这两个依赖? 是否说明AOP并不常用?)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjtools -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
暂定目标为“统计业务层代码的执行耗时”。
在cn.tedu.store
包下创建aop
包,用于存放切面类(当然,也可以使用其它包名,例如aspect
),并在这个包中创建TimerAspect
类(切面类推荐使用Aspect
作为类名的最后一个单词)。作为一个切面类,必须添加@Aspect
注解,同时,是由Spring管理的,所以,还需要添加@Component
注解:
package cn.tedu.store.aop;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class TimerAspect {
}
然后,在切面类中,自定义方法,实现切面中的功能,并通过注解配置切面的位置:
@Around("execution(* cn.tedu.store.service.impl.*.*(..))")
public Object aaaaa(ProceedingJoinPoint pjp) throws Throwable {
// 记录开始时间
long start = System.currentTimeMillis();
// 执行业务方法,例如注册业务、登录业务等
Object obj = pjp.proceed();
// 记录结束时间
long end = System.currentTimeMillis();
System.err.println("执行耗时:" + (end - start) + "毫秒。");
// 返回
return obj;
}
以上代码中,注解的配置值execution(* cn.tedu.store.service.impl.*.*(..))
将确定切面的位置,这段值表示“在cn.tedu.store.service.impl
包下的所有类(包名右侧的第1个星号)的所有方法(包名右侧的第2个星号),且无视方法的参数数量(括号中的2个小数)和方法的返回值类型(包名左侧的星号)”,也就是当前项目中所有的业务方法都会匹配上!
以上代码中,使用的@Around
注解表示切面的连接方法,@Around
表示“包裹”,也就是在应用切面的业务方法之前和之后都存在!也可以使用@Before
表示“之前”,则切面在某位置之前,也可以使用@After
表示“之后”。
以上代码中,自定义的方法必须添加ProceedingJoinPoint
接口类型的参数,该参数的proceed()
方法就相当于切面所包裹的位置(业务)需要执行方法,所以,可以通过pjp.proceed()
表示“注册业务”,也可以表示“登录业务”或任何其它业务!
需要注意的是:该方法将抛出异常,用于表示具体的业务方法可能抛出的异常,例如“注册”时抛出的UsernameDuplicateException
,在切面中,必须将该异常抛出,否则,就必须使用try...catch
进行捕获,相当于自行处理了异常,后续控制器类就不会发现这个异常了!另外,调用pjp.proceed()
方法将得到一个Object
类型的返回值,这个返回值就相当于业务方法的返回值,例如“登录”成功后将返回一个User
对象,加载收货地址列表时将返回一个List<Address>
,在自定义切面方法时,必须获取该返回值,并且作为切面方法的返回值,否则,就相当于业务方法返回了null
!