什么是AOP
Spring是java面向对象编程的主流框架,其主要思想有两个:反转控制(IOC)和面向切面(AOP)。
AOP(Aspect Oriented Programming)称为面向切面编程,是对面向对象(OOP)的补充和完善。
普通的面向对象基于封装、继承、多态等概念来建立一种对象层次结构,但仅限于纵向层次,无法做到横向关联。而日志、异常处理等功能涉及各个类,代码横向地散布在所有层次中,传统OOP难以复用这些代码。
而AOP利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。
所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
日志中统计执行时间
对应代码中的关键方法,在日志中通过计时统计执行时间,可以监控功能执行是否正常,也可以观察程序的性能,作为优化效果的判断依据。
统计执行时间可以使用Stopwatch工具类,引入
import com.google.common.base.Stopwatch;
使用createStarted()开始计时,结束时用elapsed统计耗时。
代码示例如下:
import com.google.common.base.Stopwatch;
Stopwatch stopwatch = Stopwatch.createStarted();
/**
* 功能代码
*/
log.info("xxxxx,elapsed:{} ms", stopwatch.elapsed(TimeUnit.MILLISECONDS));
不过这种方式只能统计一个功能的耗时,若现在有100个接口都需要统计耗时的话,显然是没办法每个都去这么写的。
这时就需要配合AOP技术来实现了。
使用AOP构建通用计时类
先放完整的实现:
@Component
@Aspect
@Log4j
public class TimingUtil {
@Pointcut("execution(* com.xxx.xx.controller..*.*(..))")
private void pointCut(){}
@Around("pointCut()")
public Object timing(ProceedingJoinPoint joinPoint){
Object object;
Stopwatch stopwatch = Stopwatch.createStarted();
try{
object = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
return null;
}
log.info("API request completed, takes " + stopwatch.elapsed(TimeUnit.MILLISECONDS) + " ms.");
return object;
}
}
来看这里面的关键点:
1 Aspect和Component注解
Component注解对于Springboot来说相当于声明bean并添加至xml配置中,加了Component的类编译时就被视为Springboot项目的bean,进行拼装。
Aspect注解则声明了该类是一个切面。
2 PointCut注解
该注解用来声明AOP切面的切入点,或者说,告诉程序什么时候要使用切面介入。
execution表示在执行某些方法时介入,是PointCut最常用的介入方式。
表达式格式为:(返回类型,方法路径.方法名(参数类型))
在本例中,要介入的是接口类以获取时间,第一个* 代表不限制返回类型,接口类存放的路径在com.xxx.xx.controller中,后面..* 表示controller包下的所有类。
再往后.*表示这个类下的所有方法,(..)代表不限制传入的参数类型。
3 Around注解
在捕获切入点后,AOP类就可以进行切面上的操作了。
根据需要处理的行为方式不同,可以使用不同的注解标记,例如前置处理@Before、后置处理@AfterRunning、异常处理@AfterThrowing、环绕处理@Around等等。
这里我们要记录接口执行的时间,因此需要在接口调用前开始计时,在接口返回后停止计时,所以需要环绕处理。
需要在Around注解后标记切入方法名,因为可能不止一个切入。
使用Around注解时,切面方法必须使用入参ProceedingJoinPoint,代表切入点,切面方法返回类型默认选object(当然,如果可以确保所有返回类型一致,也可以直接定义)。
joinPoint.proceed()这个方法表示执行切入点的方法,在此之前的代码部分为前置处理,在此之后的代码部分为后置处理。
所以在执行proceed之前启动秒表,在执行后记录时间,这样对所有接口进行计时的功能就完成了。