java AOP切面編程實踐

本示例爲一個基於註解的切面編程實踐,該切面功能:主要是用來統計被註解標識的方法執行時的耗時時長

1,首先 配置maven依賴:

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- aop -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-aop</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

2,其次定義一個註解

package com.example.interceptor.aop;

import java.lang.annotation.*;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface TimeLog {
    String value() default "";
}

3,再定義一個基於註解的切面及編寫相應的前置、後置增強方法

package com.example.interceptor.aop;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 監控日誌攔切面截器
 * @author weichangzhong
 * @Aspect 作用是把當前類標識爲一個切面供容器讀取
 */
@Slf4j
@Aspect
@Component
@Order(1)
public class LogAspect {

    private ThreadLocal<Long> startTime = new ThreadLocal<>();

    /**
     *  切入點 JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什麼樣的條件下才能被觸發,在程序中主要體現爲書寫切入點表達式
     *  由下列方式來定義或者通過 &&、 ||、 !、 的方式進行組合, 如:
     *  execution:用於匹配方法執行的連接點
     *  @annotation:用於匹配當前執行方法持有指定註解的方法
     */
    @Pointcut("@annotation(com.example.interceptor.aop.TimeLog)")
    public void logPointCut() {

    }

    /**
     *  標識一個前置增強方法,相當於BeforeAdvice的功能. 在切點方法之前執行
     */
    @Before("logPointCut()")
    public void before() {
        log.info("--------------before----------logPointCut");
        startTime.set(System.currentTimeMillis());
    }

    /**
     * 後置增強,似於AfterReturningAdvice, 方法返回後、正常退出時執行
     * returning的值是對應的是業務方法 的返回值,且其名稱要與afterReturning方法的參數名相同
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "response")
    public void afterReturning(JoinPoint joinPoint, Object response) {
        Long cost = System.currentTimeMillis() - startTime.get();
        log.info("--------------afterReturn--------logPointCut, time: {}", cost);

        //獲取方法類型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String className = joinPoint.getTarget().getClass().getName();
        TimeLog annotation = method.getAnnotation(TimeLog.class);
        if(annotation == null) {
            log.info("ClassName: {}, methodName: {}", className, method.getName());
        }else {
            log.info("The value of annotation is: {}, className: {}, methodName: {}", annotation.value(), className, method.getName());
        }

        //獲取請求參數
        Object[] args = joinPoint.getArgs();
        if (ArrayUtils.isNotEmpty(args)) {
            //httpServletRequest、httpServletResponse、multipartFile的參數爲異步組裝的,不能序列化,因此需要過濾掉
            List<Object> filterArg = Arrays.stream(args).filter(
                    arg -> !(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse) && !(arg instanceof MultipartFile)
            ).collect(Collectors.toList());
            log.info("The parameter is: {}", filterArg.toString());
        }else{
            log.info("The parameter is: {}", args);
        }
    }

    /**
     * 異常拋出增強,相當於ThrowsAdvice. 切點方法拋異常執行
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(pointcut = "logPointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint, Throwable ex) {
        Long cost = System.currentTimeMillis() - startTime.get();
        if (log.isDebugEnabled()) {
            log.debug("----------afterThrow--------logPointCut, time: {}", cost);
        }
    }
}

3,最後定義一個被攔截的controller方法

package com.example.interceptor.controller;

import com.example.interceptor.aop.TimeLog;
import com.example.interceptor.service.TestService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author weichangzhong
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @Resource
    private TestService testService;

    @GetMapping
    @TimeLog("test-1")
    public String getTest(@RequestParam String name, String email) {
        return testService.getTestString();
    }
}

service層:

package com.example.interceptor.service;

import com.example.interceptor.aop.TimeLog;
import org.springframework.stereotype.Service;

/**
 * @author weichangzhong
 */
@Service
public class TestService {

    @TimeLog
    public String getTestString() {
        return "test response";
    }
}

啓動服務後,調用接口:localhost:8080/test?name=me&email=emailTest

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章