SpringBoot AOP的使用及执行顺序

2,754 阅读3分钟

1.引入Aop的依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.定义切面Aspect

package com.example.boot.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * com.example.boot.aspect
 * Description:
 *
 * @author jack
 * @date 2021/6/24 11:18 上午
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(com.example.boot.aspect.Log)")
    public void logPointcut() {
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        printLog(joinPoint, startTime);
        Object result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        log.debug("Cost                        : {}ms", endTime - startTime);
        System.out.println();
        return result;
    }

    private void printLog(ProceedingJoinPoint joinPoint, long startTime) {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (Objects.isNull(servletRequestAttributes)) {
            return;
        }
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String className = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        String requestType = request.getMethod();
        String uri = request.getRequestURI();
        String requestTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date(startTime));
        log.debug("====================Request time:{}====================", requestTime);
        log.debug("Controller                  : {}#{}", className, methodName);
        log.debug("RequestType                 : {}", requestType);
        log.debug("Uri                         : {}", uri);
        log.debug("Parameters                  : {}", geParametersString(request));
        log.debug("RequestJson                 : {}", getSubscribeJson(request));
        log.debug("Ip                          : {}", getIpAddress(request));
    }

    /**
     * 获取表单提交的参数信息
     *
     * @param request requestp
     * @return parameterString
     */
    private String geParametersString(HttpServletRequest request) {
        Map<String, String[]> parameterMap = request.getParameterMap();
        StringBuilder stringBuilder = new StringBuilder();
        parameterMap.forEach((k, v) -> stringBuilder.append(k).append("=").append(Arrays.toString(v)).append(" "));
        return stringBuilder.toString();
    }

    /**
     * 从request中获取json
     *
     * @param request request
     * @return parameterJson
     */
    public String getSubscribeJson(HttpServletRequest request) {
        String contentType = request.getContentType();
        if (!"application/json".equals(contentType)) {
            return null;
        }
        BufferedReader reader = null;
        StringBuilder stringBuilder = new StringBuilder();
        try {
            reader = new BufferedReader(new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            log.error("从request中获取json数据失败");
        } finally {
            try {
                if (null != reader) {
                    reader.close();
                }
            } catch (IOException e) {
                log.error("getSubscribeJson()关闭数据流失败");
            }
        }
        return stringBuilder.toString();
    }

    /**
     * 获取客户端ip
     *
     * @param request request
     * @return ip
     */
    private String getIpAddress(HttpServletRequest request) {
        String unknown = "unknown";
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

3.自定义注解,在切面中拦截

/**
 * com.example.boot.aspect
 * Description:
 *
 * @author jack
 * @date 2021/6/24 11:20 上午
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {
}

4.使用注解

@RestController
@RequestMapping(value = "/message")
@Slf4j
public class MessageController {

    @Log
    @GetMapping(value = "/getOne")
    public void getOne(Long id) {
        log.info("getOne:{}", id);
    }
  
}

5.效果

image.png

6.advice执行的顺序

6.1 单个切面正常情况的顺序

  1. @Around
  2. @Before
  3. joinPoint.proceed(); 执行方法
  4. @AfterReturning
  5. @After
  6. @Around

6.2 单个切面异常情况的顺序

  1. @Around
  2. @Before
  3. joinPoint.proceed(); 执行方法
  4. @AfterThrowing

6.3 多个切面正常情况的顺序

@Order 值越小的越先执行,越先执行的最后才结束

  1. Aspect1 @Around
  2. Aspect1 @Before
  3. Aspect2 @Around
  4. Aspect2 @Before
  5. Aspect2 joinPoint.proceed(); 执行方法
  6. Aspect2 @AfterReturning
  7. Aspect2 @After
  8. Aspect2 @Around
  9. Aspect1 @AfterReturning
  10. Aspect1 @After
  11. Aspect1 @Around

image.png

6.4 测试代码

LogAspect1

package com.example.boot.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * com.example.boot.aspect
 * Description:
 *
 * @author jack
 * @date 2021/6/24 11:18 上午
 */
@Aspect
@Component
@Slf4j
@Order(1)
public class LogAspect1 {

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(com.example.boot.aspect.Log)")
    public void logPointcut() {
    }

    /**
     * 声明前置通知
     *
     * @param point point
     */
    @Before("logPointcut()")
    public void doBefore(JoinPoint point) {
        log.info("LogAspect1:doBefore");
    }

    /**
     * 声明后置通知
     *
     * @param point       point
     * @param returnValue returnValue
     */
    @AfterReturning(pointcut = "logPointcut()", returning = "returnValue")
    public void doAfterReturning(JoinPoint point, Object returnValue) {
        log.info("LogAspect1:doAfterReturning,returnValue:{}", returnValue);
    }

    /**
     * 声明异常通知
     *
     * @param e e
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        log.error("LogAspect1:doAfterThrowing", e);
    }

    /**
     * 声明最终通知
     */
    @After("logPointcut()")
    public void doAfter() {
        log.info("LogAspect1:doAfter");
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("LogAspect1:doAround-1");
        Object result = joinPoint.proceed();
        log.info("LogAspect1:doAround-2");
        return result;
    }
}

LogAspect2

package com.example.boot.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * com.example.boot.aspect
 * Description:
 *
 * @author jack
 * @date 2021/6/24 11:18 上午
 */
@Aspect
@Component
@Slf4j
@Order(2)
public class LogAspect2 {

    /**
     * 配置切入点
     */
    @Pointcut("@annotation(com.example.boot.aspect.Log)")
    public void logPointcut() {
    }

    /**
     * 声明前置通知
     *
     * @param point point
     */
    @Before("logPointcut()")
    public void doBefore(JoinPoint point) {
        log.info("LogAspect2:doBefore");
    }

    /**
     * 声明后置通知
     *
     * @param point       point
     * @param returnValue returnValue
     */
    @AfterReturning(pointcut = "logPointcut()", returning = "returnValue")
    public void doAfterReturning(JoinPoint point, Object returnValue) {
        log.info("LogAspect2:doAfterReturning,returnValue:{}", returnValue);
    }

    /**
     * 声明异常通知
     *
     * @param e e
     */
    @AfterThrowing(pointcut = "logPointcut()", throwing = "e")
    public void doAfterThrowing(Exception e) {
        log.error("LogAspect2:doAfterThrowing", e);
    }

    /**
     * 声明最终通知
     */
    @After("logPointcut()")
    public void doAfter() {
        log.info("LogAspect2:doAfter");
    }

    /**
     * 配置环绕通知,使用在方法logPointcut()上注册的切入点
     *
     * @param joinPoint join point for advice
     */
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("LogAspect2:doAround-1");
        Object result = joinPoint.proceed();
        log.info("LogAspect2:doAround-2");
        return result;
    }

}