springboot入门——AOP实现打印HTTP请求到日志

spring boot实现AOP打印日志功能

简单谈一谈我对AOP的理解。

已简单的web请求为例,从Controller收到一个请求,到实现业务功能,再到与数据库交互,是一个链条一样的结构,几乎每一个链条都分可为上述的三层。
例如:Controller层会有记录http请求到日志的需求,且各个链条这部分的逻辑通常都是一致的。从面向对象的角度考虑,如果每一根链条都单独实现这个需求,会造成代码的冗余以及难以维护,因此就出现了用一份代码实现,在所有Controller获得请求时记录一条日志的设计,这就好像是一个穿插在所有链条上的一个很切面,与各个链条相交的点可以称之为切点,这就是我所理解的AOP的设计思想。

这里用AOP实现了打印HTTP记录HTTP请求到日志中的小例子,定义一个User类,实现一个查询用户的方法,访问这个方法时,将HTTP请求的内容记录到日志中。直接上代码:
1.pom.xml文件

主要引入了三个依赖【spring-boot-starter】【spring-boot-starter-web】【spring-boot-starter-aop】

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>org.monkey.springboot</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>11</java.version>
	</properties>

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

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

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

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

2.User类

【@Component】改注解将User对象添加到spring容器中

package org.monkey.springboot.demo.entity;

import org.springframework.stereotype.Component;

@Component
public class User {
    private Integer id;
    private String name;
    private String password;
    private String gender;
    private int status;
    
    public Integer getId() {
        return id;
    }
    
    public void setId(Integer id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getPassword() {
        return password;
    }
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    public String getGender() {
        return gender;
    }
    
    public void setGender(String gender) {
        this.gender = gender;
    }
    
    public int getStatus() {
        return status;
    }
    
    public void setStatus(int status) {
        this.status = status;
    }
    
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", status=" + status +
                '}';
    }
}

3.接收请求的UserController

【@RestController】注解表示该类是一个Controller;
【@RequestMapping("/user")】为该类设置一个URL前缀 /user;
在getUsers方法中,创建三个user对象并返回到页面模拟获取用户的操作;
参数【id】的作用,是便于在利用AOP打印参数时,使效果更加直观。

package org.monkey.springboot.demo.controller;

import org.monkey.springboot.demo.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.websocket.server.PathParam;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {
    
    private static final Logger logger = LoggerFactory.getLogger(UserController.class);
    
    @GetMapping("/list/{id}")
    public List<User> getUsers(@PathVariable("id") Integer id) {
        logger.info("getUsers");
        logger.info("id = {}", id);
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            User user = new User();
            user.setId(i);
            user.setName("name-" + i);
            user.setPassword("password-" + i);
            user.setGender(i % 2 == 0 ? "男" : "女");
            user.setStatus(0);
            users.add(user);
        }
        return users;
    }
}

4.AOP的关键类HttpAspect

【@Aspect】注解说明这是AOP的类(不知道官方说法是啥,姑且这么认为吧)
【@Pointcut】整个类可以认为是一个切面,该注解标注的是一个切点,(前面提到的面和链条的交点)

package org.monkey.springboot.demo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class HttpAspect {
    
    private static final Logger logger = LoggerFactory.getLogger(HttpAspect.class);
    
    // 切点,用于匹配所要执行功能的方法
    @Pointcut("execution(public * org.monkey.springboot.demo.controller.UserController.*(..))")
    public void log(){
    }
    // @Before该注解下的方法,要在执行与切点匹配的方法之前执行,匹配模式有log() 决定
    @Before("log()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // url
        logger.info("url={}.", request.getRequestURL());
        // method
        logger.info("method={}.", request.getMethod());
        // ip
        logger.info("ip={}.", request.getRemoteAddr());
        // 类方法
        logger.info("class_method={}.", joinPoint.getSignature().getDeclaringTypeName() + "." +
                joinPoint.getSignature().getName());
        // 参数
        logger.info("args={}.", joinPoint.getArgs());
    }
    
    // @After该注解下的方法,要在执行与切点匹配的方法之后执行,匹配模式有log() 决定
    @After("log()")
    public void doAfter() {
        logger.info("doAfter");
    }
    
    // @After该注解下的方法,要在执行与切点匹配的方法之后执行,该注解能够获取方法执行完成之后返回值,匹配模式有log() 决定
    @AfterReturning(returning = "object", pointcut = "log()")
    public void doAfterReturn(Object object) {
        logger.info("returning={}.", object);
    }
}

最后,再贴一下我的运行结果
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,针对您的问题,我可以给您一些简单的解释。 SpringBoot中的AOP(面向切面编程)可以帮助我们对方法进行拦截和增强,这样我们就可以在方法执行前后进行一些自己想要的操作,比如接口的请求数据解密和返回数据加密。 具体来说,我们可以通过定义一个切面类,在其中定义一个前置通知和一个后置通知。前置通知可以在方法执行前进行解密操作,后置通知可以在方法执行后进行加密操作。 下面是一个简单的示例代码: ```java @Aspect @Component public class EncryptAspect { @Autowired private EncryptService encryptService; @Pointcut("execution(public * com.example.controller.*.*(..))") public void encrypt() {} @Before("encrypt()") public void doEncrypt(JoinPoint joinPoint) { Object[] args = joinPoint.getArgs(); for (Object arg : args) { if (arg instanceof String) { String decrypted = encryptService.decrypt((String) arg); // 将解密后的数据重新设置到参数中 // ... } } } @AfterReturning(value = "encrypt()", returning = "result") public void doDecrypt(JoinPoint joinPoint, Object result) { if (result instanceof String) { String encrypted = encryptService.encrypt((String) result); // 将加密后的数据返回 // ... } } } ``` 在上面的示例代码中,我们定义了一个切面类`EncryptAspect`,其中通过`@Pointcut`注解定义了需要拦截的方法,即`com.example.controller`包下的所有方法。在`doEncrypt`方法中,我们可以获取到方法的参数,并进行解密操作;在`doDecrypt`方法中,我们可以获取到方法的返回值,并进行加密操作。 需要注意的是,上面的示例代码中的`EncryptService`是一个加解密服务的接口,具体的加解密实现可以根据自己的需求进行编写。 希望以上解释可以帮助到您。如果还有其他问题,欢迎随时提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值