IT七剑客 IT七剑客
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
首页
wresource
郭霖
孤寒者
IT邦德
沉默王二
老麦
stackoverflow
GitHub (opens new window)
  • Java基础语法

  • 程序人生

  • 实用工具

  • Java重要知识点

  • Java工具

    • currentTimeMillis 统计耗时了,太 Low,试试 Spring Boot 源码在用的 StopWatch吧,够优雅!
    • WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!
    • 在IDEA里下个五子棋不过分吧?
    • 好用到爆!GitHub 星标 32.5k+的命令行软件管理神器,功能真心强大!
    • 我们公司使用了6年的Spring Boot项目部署方案,打包 + 一键部署,稳的一批
    • 再见 Spring Task,这款老而弥坚的开源任务调度框架,用起来够优雅!
    • 解放双手!推荐一款 GitHub 星标 8.2k+的命令行软件管理器,非常酷炫!
    • 再见收费的TeamViewer,推荐一款不限速的国产远程控制软件
    • 几行代码,网站图片访问速度 100ms 飙升到 20ms!
    • 某意大利小哥,竟靠一个缓存中间件直接封神?
    • WindTerm:新一代开源免费的终端工具,GitHub星标6.6k+,太酷了!
    • 厉害!我带的实习生仅用四步就整合好SpringSecurity+JWT实现登录认证!
    • 保姆级SpringBoot+Vue图片上传到阿里云OSS教程
    • 两天两夜,1M图片优化到100kb!
    • 干掉Session?这个跨域认证解决方案真的优雅!
    • 前后端分离项目,如何解决跨域问题?
    • Spring Boot AOP 扫盲,实现接口访问的统一日志记录
      • 再见收费的Navicat!操作所有数据库就靠它了!
      • 取代 Mybatis Generator,这款代码生成神器配置更简单,开发效率更高!
      • 再见丑陋的 SwaggerUI,这款开源的API文档生成神器界面更炫酷,逼格更高!
    • 数组与字符串

    • 沉默王二 Java
    • Java工具
    沉默王二
    2022-09-01
    目录

    Spring Boot AOP 扫盲,实现接口访问的统一日志记录

    AOP 是 Spring 体系中非常重要的两个概念之一(另外一个是 IoC),今天这篇文章就来带大家通过实战的方式,在编程猫 SpringBoot 项目中使用 AOP 技术为 controller 层添加一个切面来实现接口访问的统一日志记录。

    # 一、关于 AOP

    AOP,也就是 Aspect-oriented Programming,译为面向切面编程,是计算机科学中的一个设计思想,旨在通过切面技术为业务主体增加额外的通知(Advice),从而对声明为“切点”(Pointcut)的代码块进行统一管理和装饰。

    这种思想非常适用于,将那些与核心业务不那么密切关联的功能添加到程序中,就好比我们今天的主题——日志功能,就是一个典型的案例。

    img

    AOP 是对面向对象编程(Object-oriented Programming,俗称 OOP)的一种补充,OOP 的核心单元是类(class),而 AOP 的核心单元是切面(Aspect)。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而降低耦合度,提高程序的可重用性,同时也提高了开发效率。

    我们可以简单的把 AOP 理解为贯穿于方法之中,在方法执行前、执行时、执行后、返回值后、异常后要执行的操作。

    # 二、AOP 的相关术语

    来看下面这幅图,这是一个 AOP 的模型图,就是在某些方法执行前后执行一些通用的操作,并且这些操作不会影响程序本身的运行。

    img 我们了解下 AOP 涉及到的 5 个关键术语:

    1)横切关注点,从每个方法中抽取出来的同一类非核心业务

    2)切面(Aspect),对横切关注点进行封装的类,每个关注点体现为一个通知方法;通常使用 @Aspect 注解来定义切面。

    3)通知(Advice),切面必须要完成的各个具体工作,比如我们的日志切面需要记录接口调用前后的时长,就需要在调用接口前后记录时间,再取差值。通知的方式有五种:

    • @Before:通知方法会在目标方法调用之前执行
    • @After:通知方法会在目标方法调用后执行
    • @AfterReturning:通知方法会在目标方法返回后执行
    • @AfterThrowing:通知方法会在目标方法抛出异常后执行
    • @Around:把整个目标方法包裹起来,在被调用前和调用之后分别执行通知方法

    4)连接点(JoinPoint),通知应用的时机,比如接口方法被调用时就是日志切面的连接点。

    5)切点(Pointcut),通知功能被应用的范围,比如本篇日志切面的应用范围是所有 controller 的接口。通常使用 @Pointcut 注解来定义切点表达式。

    切入点表达式的语法格式规范如下所示:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?
    				name-pattern(param-pattern)
                    throws-pattern?)
    复制代码
    
    1
    2
    3
    4
    • modifiers-pattern? 为访问权限修饰符
    • ret-type-pattern 为返回类型,通常用 * 来表示任意返回类型
    • declaring-type-pattern? 为包名
    • name-pattern 为方法名,可以使用 * 来表示所有,或者 set* 来表示所有以 set 开头的类名
    • param-pattern) 为参数类型,多个参数可以用 , 隔开,各个参与也可以使用 * 来表示所有类型的参数,还可以使用 (..) 表示零个或者任意参数
    • throws-pattern? 为异常类型
    • ? 表示前面的为可选项

    举个例子:

    @Pointcut("execution(public * com.codingmore.controller.*.*(..))")
    复制代码
    
    1
    2

    表示 com.codingmore.controller 包下的所有 public 方法都要应用切面的通知。

    # 三、实操 AOP 记录接口访问日志

    第一步,在 Spring Boot 项目的 pom.xml 文件中添加 spring-boot-starter-aop 依赖。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    复制代码
    
    1
    2
    3
    4
    5

    第二步,添加日志信息封装类 WebLog,用于记录什么样的操作、操作的人是谁、开始时间、花费的时间、操作的路径、操作的方法名、操作主机的 IP、请求参数、返回结果等。

    /**
     * Controller层的日志封装类
     * Created by macro on 2018/4/26.
     */
    public class WebLog {
        private String description;
        private String username;
        private Long startTime;
        private Integer spendTime;
        private String basePath;
        private String uri;
        private String url;
        private String method;
        private String ip;
        private Object parameter;
        private Object result;
        //省略了getter,setter方法
    }
    复制代码
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    第三步,添加统一日志处理切面 WebLogAspect。

    /**
     * 统一日志处理切面
     * Created by 石磊
     */
    @Aspect
    @Component
    @Order(1)
    public class WebLogAspect {
        private static final Logger LOGGER = LoggerFactory.getLogger(WebLogAspect.class);
    
        @Pointcut("execution(public * com.codingmore.controller.*.*(..))")
        public void webLog() {
        }
    
        @Before("webLog()")
        public void doBefore(JoinPoint joinPoint) throws Throwable {
        }
    
        @AfterReturning(value = "webLog()", returning = "ret")
        public void doAfterReturning(Object ret) throws Throwable {
        }
    
        @Around("webLog()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
            long startTime = System.currentTimeMillis();
            //获取当前请求对象
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            //记录请求信息(通过Logstash传入Elasticsearch)
            WebLog webLog = new WebLog();
            Object result = joinPoint.proceed();
            Signature signature = joinPoint.getSignature();
            MethodSignature methodSignature = (MethodSignature) signature;
            Method method = methodSignature.getMethod();
            if (method.isAnnotationPresent(ApiOperation.class)) {
                ApiOperation log = method.getAnnotation(ApiOperation.class);
                webLog.setDescription(log.value());
            }
            long endTime = System.currentTimeMillis();
            String urlStr = request.getRequestURL().toString();
            webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
            webLog.setIp(request.getRemoteUser());
            Map<String,Object> logMap = new HashMap<>();
            logMap.put("spendTime",webLog.getSpendTime());
            logMap.put("description",webLog.getDescription());
            LOGGER.info("{}", JSONUtil.parse(webLog));
            return result;
        }
    }
    复制代码
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50

    第四步,运行项目,并对 controller 下的某个控制器进行测试。

    Swagger knife4j 访问地址:http://localhost:9022/doc.html (opens new window)

    执行登录用户查询操作: img 可以在控制台可以看到以下日志信息:

    img 源码地址:

    github.com/itwanger/co… (opens new window)

    参考链接:

    作者 cxuan:www.cnblogs.com/cxuanBlog/p… (opens new window) 灰小猿:bbs.huaweicloud.com/blogs/28904… (opens new window) 山高我为峰:www.cnblogs.com/liaojie970/… (opens new window) macrozheng:github.com/macrozheng/… (opens new window)


    本篇已收录至 GitHub 上星标 1.6k+ star 的开源专栏《Java 程序员进阶之路》,据说每一个优秀的 Java 程序员都喜欢她,风趣幽默、通俗易懂。内容包括 Java 基础、Java 并发编程、Java 虚拟机、Java 企业级开发、Java 面试等核心知识点。学 Java,就认准 Java 程序员进阶之路😄。

    github.com/itwanger/to… (opens new window)

    star 了这个仓库就等于你拥有了成为了一名优秀 Java 工程师的潜力。也可以戳下面的链接跳转到《Java 程序员进阶之路》的官网网址,开始愉快的学习之旅吧。

    tobebetterjavaer.com/ (opens new window)

    img

    没有什么使我停留——除了目的,纵然岸旁有玫瑰、有绿荫、有宁静的港湾,我是不系之舟。

    作者:沉默王二 链接:https://juejin.cn/post/7067342522837631006 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    #Java#沉默王二
    上次更新: 2023/04/05, 05:23:58
    前后端分离项目,如何解决跨域问题?
    再见收费的Navicat!操作所有数据库就靠它了!

    ← 前后端分离项目,如何解决跨域问题? 再见收费的Navicat!操作所有数据库就靠它了!→

    最近更新
    01
    How the creator of Angular is dehydrating the web (Ep 574)
    06-07
    02
    For those who just don’t Git it (Ep 573)
    06-07
    03
    Modern work requires attention. Constant alerts steal it
    06-07
    更多文章>
    Theme by Vdoing | Copyright © 2022-2024 IT七剑客 | MIT License
  • 闽ICP备2021006579号-4
  • 闽公网安备 35012102500470号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式