如何在SpringBoot项目中通过AOP拦截来实现日志功能

一、简要说明

     如今SpringBoot技术快速发展,采用注解开发更是现在开发的热潮。博主自己就结合自己在项目中关于日志功能的实现,采用AOP来拦截前端请求,获取我们所需的信息来实现日志功能。我会从搭建项目到能自己根据需求获得自己想要的数据,一步一步,让大家明白如何实现日志管理功能的。由于博主个人 能力有限,对 文章的描述就采用大白话,如果有描述不清楚的地方,希望各位大佬指正!

二、关于AOP的介绍

      一谈到aop大家可能就会想到Spring,因为Spring具有两大特征IOC与AOP,而AOP又是里面的核心。一说AOP条件反射随口而出"切面编程",说定义大家都懂,但如何利用AOP我相信大多数人还是一头雾水。不过不用担心,我相信通过我接下来的介绍,会让大家重新有一个对AOP不一样的认识。

三、项目搭建

     以下内容是关于如何创建一个SpringBoot的项目的步骤,会的小伙伴,自动跳过即可。

    ①:

②:③:四、准备工作

1、导入依赖

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.3.2</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

2、创建数据库表

关于mylog表中的字段根据自己的需求设定了,我就设置几个比较常用的,后面我会介绍还可以请求到 那些数据

四、后台代码的书写

这里博主先将基础的MVC架构的代码写出来,再写关于自定义注解的方法

1、pojo层

@Data
@TableName("mylog")
public class Mylog {
    @TableId(type = IdType.AUTO)
    private Integer operationId;

    private String operationModel;
    private String operationDescribe;
    private String requestMethod;
    private String requestParam;
    private String operationUrl;
}

2、DAO层

@Mapper
public interface MylogDao extends BaseMapper<Mylog> {

}

3、Service层

public interface MylogService {
    int SaveLog(Mylog mylog);
}
@Service
public class MylogServiceImp implements MylogService {

    @Resource
    private MylogDao mylogDao;

    @Override
    public int SaveLog(Mylog mylog) {
        return mylogDao.insert(mylog);
    }
}

4、controller层

   当我们写完所有代码时,在测试的时候,就是调用的controller层下的方法,看aop是否拦截我们所需的数据。

@Controller
public class MyLogController {

    @Log(operationModel = "登录模块" , operationDescribe = "访问登录模块")
    @GetMapping("/login/{name}/{age}")
    @ResponseBody
    public String Login(@PathVariable("name") String name, @PathVariable("age") Integer age){
        return name+"今年"+age+"岁登录了系统";
    }
}

5、自定义注解(重点)

从这里开始就是我们需要学习的重点部分了,首先我们像普通注解一样定义一个自己用于切面的注解,代码如下:

@Documented
@Target({ElementType.METHOD})//表示放在controller层的方法上
@Retention(RetentionPolicy.RUNTIME)//表示注解在那个阶段执行
public @interface Log {

    //写在注解里面,可以用于存到数据库时的对方法的描述

    String  operationModel() default " ";//操作模块

    String operationDescribe() default " ";//操作说明

}

6、定义切面(重点)

如果只有自定义的注解,没有定义切面和切点,AOP是没有起到拦截的作用,先呈上代码再做详细 解释。

@Component// 让SpringBoot来识别到
@Aspect//切面 定义了 通知与切点的关系
public class LogAspect {

    private Mylog mylog;

    @Resource
    private MylogService mylogService;

    @Pointcut("@annotation(com.ghs.test.annotation.Log)")//定义切点 表示注解加在哪里(添加全路径)
    public void PointCut(){

    }

    @Before("PointCut()")
    public void Before(JoinPoint joinPoint){

        mylog = new Mylog();
        ServletRequestAttributes requests = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requests.getRequest();
        //通过HttpServletRequest对象request获取method、url;
        mylog.setOperationUrl(request.getRequestURI());
        mylog.setRequestMethod(request.getMethod());
        mylog.setRequestParam(Arrays.toString(joinPoint.getArgs()));

        //通过反射机制获得切入点处的方法
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();

        //获取在自定义的注解@Log()中的内容
        Log annotation = method.getAnnotation(Log.class);
        mylog.setOperationModel(annotation.operationModel());
        mylog.setOperationDescribe(annotation.operationDescribe());
    }
    @Around("PointCut()")
    public Object Around(ProceedingJoinPoint point) throws Throwable {

        Object proceed = point.proceed();
        mylogService.SaveLog(mylog);
        System.out.println(mylog.toString());
        return  proceed;
    }
}

上述代码中,除了有注释的地方,我会强调以下几个点:

①该代码理解

        ServletRequestAttributes requests = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requests.getRequest();

如果你只是拿得到代码后就只是CV,那么对你个人能力的提升是大大打折扣的。现在让我来说明一下为什么可以拿到HttpServletRequest对象以及为什么要拿到它。

为什么可以拿到HttpServletRequest对象:

首先RequestContextHolder是Spring框架中的一个工具类,在该工具类中调用其静态方法法getRequestAttributes()获取当前线程中绑定的请求属性,然后将 RequestAttributes 对象转换为 ServletRequestAttributes 对象,最后,再通过servletRequestAttributes.getRequest()得到HttpServletRequest对象。

为什么要拿到HttpServletRequest对象呢:

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。

换句话说,当我们得到HttpServletRequest对象,我们可以从idea中看到有那些方法;

String getMethod();

    String getPathInfo();

    String getPathTranslated();

    default PushBuilder newPushBuilder() {
        return null;
    }

    String getContextPath();

    String getQueryString();

    String getRemoteUser();

    boolean isUserInRole(String var1);

    Principal getUserPrincipal();

    String getRequestedSessionId();

    String getRequestURI();

    StringBuffer getRequestURL();

    String getServletPath();

    HttpSession getSession(boolean var1);

    HttpSession getSession();

    String changeSessionId();

    boolean isRequestedSessionIdValid();

    boolean isRequestedSessionIdFromCookie();

    boolean isRequestedSessionIdFromURL();

以上等等方法都是HttpServletRequest自带的,我也是从中挑了几个比较常用的,所以我在前面说,你们可以根据自己的需求来设计需要的日志表哦。而且在我们真实的项目中需要获取session的时候,也可以通过该方法获得哦。

②该代码的理解

        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        Method method = signature.getMethod();
        Log annotation = method.getAnnotation(Log.class);

Signature是 Java 反射 API 中的一个接口,在SpringAOP中可以通过 JoinPoint 对象获得连接点的相关信息,在该对象中通过getSignature() 方法获取到 Signature 对象,但因为我们的链接点是一个方法,可以将Signature 对象转换为 MethodSignature 对象,通过MethodSignature 对象可以获取到更加详细的方法签名信息。再通过反射获取自定义的注解,这样一来就可以得到我们自定义在@Log()里面的内容了。

③几个注解的理解

@Before("PointCut()")

表示在程序执行前执行。

@After("PointCut()")

表示在程序执行之后执行

@Around("PointCut()")

表示环绕整个程序执行

五、测试结果

我们访问MyLogController层下的"localhost:8080/login/567/18"接口,看是否能访问我们所需的信息。

效果展示

总上所述,我们成功将,需要拦截的方法描述、请求方法、请求参数、访问路径等信息在控制台输出并存到日志表中。

六、总结

     通过上面的描述我们就可以实现一个基本的采用SpringAOP实现的日志管理功能了,上面的代码虽小,但还是五脏俱全了,就算应付一些比较复杂的要求,明白了原理多查查相关资料也是能搞定的。如果,这篇文章对正在阅读的小伙伴来说有一定帮助的话,小伙伴不要忘记点赞多多支持博主哦。如果需要将我们的日志管理写在项目中,就记得在留言区留言,如果需要的小伙伴比较多,我后期就写一个HTML页面的,关于日志管理的模块(包含一些基本的操作的)。