1.什么是 AOP ?

AOP 的全称为 Aspect Oriented Programming,译为面向切面编程,是通过预编译方式和运行期动态代理实现核心业务逻辑之外的横切行为的统一维护的一种技术。AOP 是面向对象编程(OOP)的补充和扩展。 利用 AOP 可以对业务逻辑各部分进行隔离,从而达到降低模块之间的耦合度,并将那些影响多个类的公共行为封装到一个可重用模块,从而到达提高程序的复用性,同时提高了开发效率,提高了系统的可操作性和可维护性。
2.为什么要用 AOP ?
在实际的 Web 项目开发中,我们常常需要对各个层面实现日志记录,性能统计,安全控制,事务处理,异常处理等等功能。如果我们对每个层面的每个类都独立编写这部分代码,那久而久之代码将变得很难维护,所以我们把这些功能从业务逻辑代码中分离出来,聚合在一起维护,而且我们能灵活地选择何处需要使用这些代码。
3.AOP 的核心概念
微信截图_20220401224258.png

Spring AOP

1.简介
AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。
微信截图_20220401224322.png
其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都属于通知(Advice)。

2.pom.xml

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

3.Web 日志注解

package com.songzixian.annotation;

import java.lang.annotation.*;

/**
 * <p>
 * Description:
 * </p>
 *
 * @author songzixian
 * @version v1.0.0
 * @create 2021-06-06 22:59
 * @see com.songzixian.annotation
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)//注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在;
@Target(ElementType.METHOD)//@Target的值可以表示是在类上生效还是方法生效(ElementType.METHOD=方法上使用)
public @interface ControllerWebLog {
    /**
     * 所调用接口的名称
     */
    String methodName();

    /**
     * 标识该条操作日志是否需要持久化存储
     */
    boolean intoDb() default false;
}

4.实现切面逻辑

package com.songzixian.conroller;

import com.songzixian.annotation.ControllerWebLog;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * Description:
 * </p>
 *
 * @author songzixian
 * @version v1.0.0
 * @create 2021-06-07 0:05
 * @see com.songzixian.conroller
 */
@Aspect
@Component
@Order(100)
//@Profile({"dev", "test"}) 加这个注解代表只会在开发环境生效,配置需要改完开发环境
public class WebLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
    private ThreadLocal<Map<String, Object>> threadLocal = new ThreadLocal<Map<String, Object>>();
    /**
     * 横切点
     */
    @Pointcut("execution(public * com.songzixian.conroller..*.*(..))")
    public void webLog() {
    }
    /**
     * 接收请求,并记录数据
     * @param joinPoint
     * @param controllerWebLog
     */
    @Before(value = "webLog()&& @annotation(controllerWebLog)")
    public void doBefore(JoinPoint joinPoint, ControllerWebLog controllerWebLog) {
        // 接收到请求
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        // 记录请求内容,threadInfo存储所有内容
        Map<String, Object> threadInfo = new HashMap<>();
        logger.info("URL : " + request.getRequestURL());
        threadInfo.put("url", request.getRequestURL());
        logger.info("URI : " + request.getRequestURI());
        threadInfo.put("uri", request.getRequestURI());
        logger.info("HTTP_METHOD : " + request.getMethod());
        threadInfo.put("httpMethod", request.getMethod());
        logger.info("REMOTE_ADDR : " + request.getRemoteAddr());
        threadInfo.put("ip", request.getRemoteAddr());
        logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "."
                + joinPoint.getSignature().getName());
        threadInfo.put("classMethod",
                joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("Request Args: " + Arrays.toString(joinPoint.getArgs()));
        threadInfo.put("args", Arrays.toString(joinPoint.getArgs()));
        logger.info("USER_AGENT"+request.getHeader("User-Agent"));
        threadInfo.put("userAgent", request.getHeader("User-Agent"));
        logger.info("执行方法:" + controllerWebLog.methodName());
        threadInfo.put("methodName", controllerWebLog.methodName());
        threadLocal.set(threadInfo);
    }
    /**
     * 执行成功后处理
     * @param controllerWebLog
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(value = "webLog()&& @annotation(controllerWebLog)", returning = "ret")
    public void doAfterReturning(ControllerWebLog controllerWebLog, Object ret) throws Throwable {
        Map<String, Object> threadInfo = threadLocal.get();
        threadInfo.put("result", ret);
        if (controllerWebLog.intoDb()) {
            //插入数据库操作
            //insertResult(threadInfo);
        }
        // 处理完请求,返回内容
        logger.info("RESPONSE : " + ret);
    }

    /**
     * 获取执行时间
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object ob = proceedingJoinPoint.proceed();
        Map<String, Object> threadInfo = threadLocal.get();
        Long takeTime = System.currentTimeMillis() - startTime;
        threadInfo.put("takeTime", takeTime);
        logger.info("耗时:{}ms" ,takeTime);
        threadLocal.set(threadInfo);
        return ob;
    }
    /**
     * 异常处理
     * @param throwable
     */
    @AfterThrowing(value = "webLog()", throwing = "throwable")
    public void doAfterThrowing(Throwable throwable) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        // 异常信息
        logger.error("{}接口调用异常,异常信息{}", request.getRequestURI(), throwable);
    }
}

只想在开发环境和测试环境中使用?
对于那些性能要求较高的应用,不想在生产环境中打印日志,只想在开发环境或者测试环境中使用,要怎么做呢?我们只需为切面添加 @Profile 就可以了,如下图所示:
image (1).png

指定profile

spring:
  profiles:
    active:
      dev

image (2).png

这样就指定了只能作用于 dev 开发环境和 test 测试环境,生产环境 prod 是不生效的!
怎么使用呢?
因为我们的切点是自定义注解 @ControllerWebLog, 所以我们仅仅需要在 Controller 控制器的每个接口方法添加 @ControllerWebLog注解即可,如果我们不想某个接口打印出入参日志,不加注解就可以了:

@ControllerWebLog(methodName = "方法名称",intoDb = true)

5.测试接口

@RequestMapping("testAop")
    @ControllerWebLog(methodName = "testAop",intoDb = true)
    public String testAop(String id,String name){
        log.info("id={},name={}",id,name);
        return "testAop方法测试";
    }

测试

http://localhost:8081/testAop?id=8&name=张三

image (4).png

Last modification:April 25, 2022
如果觉得这篇技术文章对你有用,请随意赞赏