AOP简介
AOP是Spring提供的两个核心功能之一:IOC(控制反转),AOP(Aspect Oriented Programming 面向切面编程);IOC有助于应用对象之间的解耦,AOP可以实现横切关注点和它所影响的对象之间的解耦;
AOP,它通过对既有的程序定义一个横向切入点,然后在其前后切入不同的执行内容,来拓展应用程序的功能,常见的用法如:打开事务和关闭事物,记录日志,统计接口时间等。AOP不会破坏原有的程序逻辑,拓展出的功能和原有程序是完全解耦的,因此,它可以很好的对业务逻辑的各个部分进行隔离,从而使业务逻辑的各个部分之间的耦合度大大降低,提高了部分程序的复用性和灵活性。
引入POM.xml
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
AOP几个注解
- @Aspect,此注解将一个类定义为一个切面类;
- @Pointcut,此注解可以定义一个切入点,可以是规则表达式,也可以是某个package下的所有函数,也可以是一个注解等,其实就是执行条件,满足此条件的就切入;
然后可以定义切入位置,我们可以选择在切入点的不同位置进行切入:
- @Before在切入点开始处切入内容;
- @After在切入点结尾处切入内容;
- @AfterReturning在切入点return内容之后切入内容(可以用来对返回值做一些处理);
- @Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容;
- @AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑;
实现切面
我们定义一个切面,然后定义一个切入点,切入点这个方法,不需要方法体,返回值是void,在切入点后定义一下切入条件,当满足条件时,就会在切入点的指定位置织入我们自定义的内容。
package com.blog.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
@Aspect
@Component
public class LogAspect {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**记录方法调用开始时间*/
ThreadLocal<Long> startTime = new ThreadLocal<>();
@Pointcut("execution(* com.blog.controller.*.*(..))")
public void log(){};
@Before("log()")
public void doBefore(JoinPoint joinPoint){
//记录调用开始时间
startTime.set(System.currentTimeMillis());
ServletRequestAttributes servlet = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servlet.getRequest();
String url = request.getRequestURL().toString();
String ip = request.getRemoteAddr();
String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
ResultLog resultLog = new ResultLog(url,ip,classMethod,args);
logger.info("Request : {}",resultLog);
}
@AfterReturning(returning = "result",pointcut = "log()")
public void doAfterReturn(Object result){
logger.info("耗时:"+(System.currentTimeMillis()-startTime.get())/1000+"s");
logger.info("Result : {}",result);
}
private class ResultLog{
private String url;
private String ip;
private String classMethod;
private Object[] object;
public ResultLog(String url, String ip, String classMethod, Object[] object) {
this.url = url;
this.ip = ip;
this.classMethod = classMethod;
this.object = object;
}
@Override
public String toString() {
return "ResultLog{" +
"url='" + url + '\'' +
", ip='" + ip + '\'' +
", classMethod='" + classMethod + '\'' +
", object=" + Arrays.toString(object) +
'}';
}
}
}
AOP切面优先级
当我们对web层做多个切面时,会有一个问题:究竟先去切入谁?这里引入一个优先级的问题。处理方法非常简单,我们定义切面时,使用一个注解@Order(i),这个i决定着优先级的高低。
比如,现在定义了两个切面,一个@Order(3),一个@Order(8),那么执行时:
- 在切入点前的操作,按order的值由小到大执行,即:先@Order(3),后@Order(8);
- 在切入点后的操作,按order的值由大到小执行,即:先@Order(8),后@Order(3);