SpringBoot 整合 AOP 实现日志处理原来这么简单?

AOP 简述

    相信用过 Spring 的朋友应该都使用过 AOP,AOP 是 (Aspect Oriented Programming 的缩写),意思为面向切面编程,通过 预编译方式 和运行期间动态代理 实现程序功能的统一维护的一种技。

    AOP 是 OOP(面向对象) 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。

    利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的 耦合度 降低,提高程序的可重用性,同时提高了开发效率

SpringBoot 整合 AOP

Maven 依赖

<dependency>
	<!-- 集成 SpringMVC -->
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 集成 Aspect -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

创建日志拦截器

这个日志拦截器主要是使用 AOP 来实现

@Aspect
@Component
public class LogAspect {

}

需要在类上加上 @Aspect 注解,表示这是一个切面类,还需要加上 @Component 注解,可以被扫描到

@Aspect
@Component
public class LogAspect {

	/**
     * 切入点
     */
    @Pointcut("execution(* com.example.filedemo.controller.*.*(..))")
    public void log() {

    }
}

定义一个 log() 方法,标上 @Pointcut 注解,表示这是一个切入点,execution() 是最常用的切点函数,其语法如下:

  • execution():表达式主体
  • 第一个 * 表示返回类型,* 号 表示所有类型
  • 包名:表示需要拦截的包名,后面的两个小数点表示当前包和当前包的所有子包,com.example.filedemo.controller 包以及该包下的子包和所有类的方法
  • 第二个 * 号表示类名,* 号表示所有的类
  • *(…) 这个星号表示方法名, * 号表示所有的方法,括号里的小数点表示方法的参数,两个小数点表示任何参数

比如现在我们有这么一个需求,在用户请求时获取用户操作信息,例如:用户访问了哪些内容,URL,输入了什么参数,请求的是哪个方法。我们需要在方法请求前就输出该日志,那么我们应该这么做:

@Aspect
@Component
public class LogAspect {

	private Logger log = LoggerFactory.getLogger(this.getClass());

	/**
     * 切入点
     */
    @Pointcut("execution(* com.example.filedemo.controller.*.*(..))")
    public void log() {

    }
	
	/**
     * 方法请求前
     */
    @Before("log()")
    public void doBefore() {
        log.info("-------来请求了-------");
    }

    /**
     * 方法请求后
     * 说明:用户请求信息打印
     * @param joinPoint 连接点
     */
    @After("log()")
    public void doAfter(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        RequestMethod requestLog = new RequestMethod(url, ip, classMethod, args);
        log.info("Request : {}", requestLog);
    }
}

可以将用户请求信息封装成一个类

private class RequestMethod {
    private String url;
    private String ip;
    private String classMethod;
    private Object[] args;

    public RequestMethod(String url, String ip, String classMethod, Object[] args) {
        this.url = url;
        this.ip = ip;
        this.classMethod = classMethod;
        this.args = args;
    }

    @Override
    public String toString() {
        return "RequestMethod{" +
                "url='" + url + '\'' +
                ", ip='" + ip + '\'' +
                ", classMethod='" + classMethod + '\'' +
                ", args=" + Arrays.toString(args) +
                '}';
    }
}

也可以来个方法执行结果输出

/**
 * 返回执行结果,在执行方法后返回
 * pointcut:连接切入点
 * returing:返回结果
 * @param result
 */
@AfterReturning(returning = "result", pointcut = "log()")
public void doAfterReturn(Object result) {
    log.info("Result : {}", result);
}

完整代码

package com.example.filedemo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;

/**
 * @author Woo_home
 * @create 2020/9/17 21:04
 */

@Aspect
@Component
public class LogAspect {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 切入点
     */
    @Pointcut("execution(* com.example.filedemo.controller.*.*(..))")
    public void log() {

    }

    /**
     * 方法请求前
     */
    @Before("log()")
    public void doBefore() {
        log.info("-------来请求了-------");
    }

    /**
     * 方法请求后
     * 说明:用户信息打印
     * @param joinPoint 连接点
     */
    @After("log()")
    public void doAfter(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String url = request.getRequestURL().toString();
        String ip = request.getRemoteAddr();
        String classMethod = joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        RequestMethod requestLog = new RequestMethod(url, ip, classMethod, args);
        log.info("Request : {}", requestLog);
    }

    /**
     * 返回执行结果,在执行方法后返回
     * @param result
     */
    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturn(Object result) {
        log.info("Result : {}", result);
    }

    private class RequestMethod {
        private String url;         // 请求 url
        private String ip;          // 请求 ip
        private String classMethod; // 请求方法
        private Object[] args;      // 请求参数

        public RequestMethod(String url, String ip, String classMethod, Object[] args) {
            this.url = url;
            this.ip = ip;
            this.classMethod = classMethod;
            this.args = args;
        }

        @Override
        public String toString() {
            return "RequestMethod{" +
                    "url='" + url + '\'' +
                    ", ip='" + ip + '\'' +
                    ", classMethod='" + classMethod + '\'' +
                    ", args=" + Arrays.toString(args) +
                    '}';
        }
    }
}

测试

我们写个简单的 Controller

package com.example.filedemo.controller;

import com.example.filedemo.util.ResultSet;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Woo_home
 * @create 2020/9/17 21:42
 */

@Controller
public class TestController {

    private Map<String, Object> map = new HashMap<>();

    @GetMapping("/info1")
    @ResponseBody
    public Map info1() {
        map.put("1", "zhangsan");
        map.put("2", "lisa");
        return map;
    }

    @GetMapping("/userInfo/{id}/{name}")
    @ResponseBody
    public Map userInfo(@PathVariable Integer id, @PathVariable String name) {
        map.put(String.valueOf(id), name);
        return map;
    }
}

然后我们将 SpringBoot 跑起来测试一下,首先我们先访问 info1 这个接口

在这里插入图片描述
请求没什么问题,然后我们看下控制台的输出日志

2020-09-18 12:38:24.759  INFO 10192 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect    : -------来请求了-------
2020-09-18 12:38:24.933  INFO 10192 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect    : Request : RequestMethod{url='http://localhost:8080/info1', ip='0:0:0:0:0:0:0:1', classMethod='com.example.filedemo.controller.TestController.info1', args=[]}
2020-09-18 12:38:24.940  INFO 10192 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect    : Result : {1=zhangsan, 2=lisa}

注意:这里的 ip 0:0:0:0:0:0:0:1 是因为服务器和客户端都在同一台电脑上所以才会出现的

OK,我们再访问 info2 这个接口
在这里插入图片描述
请求没什么问题,然后我们看下控制台的输出日志

2020-09-18 12:53:41.438  INFO 12616 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect    : -------来请求了-------
2020-09-18 12:53:41.442  INFO 12616 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect    : Request : RequestMethod{url='http://localhost:8080/userInfo/100/John', ip='0:0:0:0:0:0:0:1', classMethod='com.example.filedemo.controller.TestController.userInfo', args=[100, John]}
2020-09-18 12:53:41.447  INFO 12616 --- [nio-8080-exec-1] com.example.filedemo.aspect.LogAspect    : Result : {100=John}

OK,关于 SpringBoot 整合 AOP 的内容就到这了

相关文章阅读

编写一个接口,实现代理设计模式(Proxy)
Spring的AOP入门(AOP概述、底层实现原理、Cglib、注解)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值