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

 

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