SpringBoot源码解析-升级版自定义Starter

开课吧开课吧锤锤2021-07-28 14:49

上一篇文章中,我们学习了如何自定义一个Starter,而今天要给大家分享的是复杂点的自定义Starter。  

SpringBoot源码解析-升级版自定义Starter

需求  

自定义一个记录接口请求的Starter。  

创建Starter项目  

创建一个名为javafamily-log-spring-boot-starter项目。  

1.引入依赖  

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

2.自定义日志注解  

package com.javafamily.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 作用于方法级别
@Target(ElementType.METHOD)
// 设置注解生命周期
@Retention(RetentionPolicy.RUNTIME)
public @interface JavaFamilyLog {

    /**
     * 描述接口作用
     */
    String remark() default "";

    /**
     * 是否开启时间打印 默认开启
     */
    boolean enable() default true;
}

注解里面包含两个属性,remark表示接口作用描述,enable表示是否开启接口耗时的记录,默认为开启,该属性用于测试对比。  

3.自定义拦截器  

package com.javafamily.interceptor;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.javafamily.annotation.JavaFamilyLog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

@Slf4j
public class JavaFamilyInterceptor extends HandlerInterceptorAdapter {

    // 记录请求起始时间
    private static final ThreadLocal<Long> START_TIME_THREAD_LOCAL = new ThreadLocal<>();

    // 使用jackson序列化
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    /**
     * 前置处理
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            // 获取方法上的JavaFamilyLog注解
            JavaFamilyLog javaFamilyLog = method.getAnnotation(JavaFamilyLog.class);
            if (javaFamilyLog != null) {
                // 开启打印功能才记录时间
                if (javaFamilyLog.enable()) {
                    // 获取当前时间作为请求接口开始时间
                    long startTime = System.currentTimeMillis();
                    // 将开始时间存储到ThreadLocal中
                    START_TIME_THREAD_LOCAL.set(startTime);
                }
            }
        }
        return true;
    }

    /**
     * 后置处理
     *
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            JavaFamilyLog javaFamilyLog = method.getAnnotation(JavaFamilyLog.class);
            if (javaFamilyLog != null) {
                // 定义方法执行耗时
                long executionTime = -1;
                // 开启时间记录
                if (javaFamilyLog.enable()) {
                    // 获取当前时间作为截止时间
                    long endTime = System.currentTimeMillis();
                    // 从获取开始时间
                    long startTime = START_TIME_THREAD_LOCAL.get();
                    START_TIME_THREAD_LOCAL.remove();
                    // 计算方法执行耗时
                    executionTime = endTime - startTime;
                }
                // 获取请求路径
                String requestUri = request.getRequestURI();
                // 获取方法所在的类路径以及方法名 通过#拼接 效果直观
                String methodFullPath = method.getDeclaringClass().getName() + "#" + method.getName();
                // 获取接口作用描述
                String remark = javaFamilyLog.remark();
                // 将参数转换成字符串
                String parameters = OBJECT_MAPPER.writeValueAsString(request.getParameterMap());
                // 打印日志
                log.info("\n接口作用描述:{}\n请求路径: {}\n方法路径: {}\n请求参数:{}\n接口耗时:{} ms",
                        remark, requestUri, methodFullPath, parameters, executionTime == -1 ? null : executionTime);
            }
        }
    }
}

自定义拦截器中使用jackson序列化对象,习惯fastjson的小伙伴可以相应依赖替换。整体的思路就是被请求的方法上是否加了JavaFamilyLog注解,如果加了就对接口请求信息纬度进行记录,同时需要判断时间记录是否开启,如果不开启就不记录。  

4.添加自定义拦截器  

package com.javafamily.config;

import com.javafamily.interceptor.JavaFamilyInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


@Configuration
public class JavaFamilyLogAutoConfiguration implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加自己实现的拦截器
        registry.addInterceptor(new JavaFamilyInterceptor());
    }
}

5.编写配置文件  

在resources文件夹下创建META-INF文件夹,在META-INF文件夹下创建spring.factories文件。  

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.javafamily.config.JavaFamilyLogAutoConfiguration

项目整体如下:  

SpringBoot源码解析-升级版自定义Starter

引入自定义Starter  

创建任意SpringBoot工程,引入javafamily-log-spring-boot-starter依赖。

<dependency>
    <groupId>com.javafamily</groupId>
    <artifactId>javafamily-log-spring-boot-starter</artifactId>
    <version>0.0.1-SNAPSHOT</version>
</dependency>

创建HelloController,对刚写完的自定义Starter进行测试。  

package com.javafamily.familydemo.controller;

import com.javafamily.annotation.JavaFamilyLog;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class HelloController {

    @JavaFamilyLog(remark = "/关闭时间打印测试", enable = false)
    @GetMapping("/test")
    public String test() {

        return "success";
    }

    @JavaFamilyLog(remark = "日志测试")
    @GetMapping("/logTest")
    public String logTest(String name) {
        return "Hello " + name;
    }
}

接着分别请求两个方法,我们看下效果。  

2021-07-28 19:03:03.257  INFO 14580 --- [nio-8080-exec-6] c.j.interceptor.JavaFamilyInterceptor    : 
接口作用描述:/关闭时间打印测试
请求路径: /test
方法路径: com.javafamily.familydemo.controller.HelloController#test
请求参数:{}
接口耗时:null ms
2021-07-28 19:03:04.677  INFO 14580 --- [nio-8080-exec-7] c.j.interceptor.JavaFamilyInterceptor    : 
接口作用描述:日志测试
请求路径: /logTest
方法路径: com.javafamily.familydemo.controller.HelloController#logTest
请求参数:{"name":["JavaFamily"]}
接口耗时:5 ms

根据效果,我们可以看到test接口耗时为null说明enable属性生效了,logTest接口也打印了请求参数,说明自定义Starter的功能都实现了。同时日志中也包含了接口请求一些重要的纬度信息,大家可以根据自己的需求再添加一些纬度。  

好了,本篇文章到此结束,我们下次见。  

以上就是开课吧广场小编为大家整理发布的“SpringBoot源码解析-升级版自定义Starter”一文,更多Java教程相关内容尽在开课吧Java教程频道。  

SpringBoot源码解析

免责声明:本站所提供的内容均来源于网友提供或网络搜集,由本站编辑整理,仅供个人研究、交流学习使用。如涉及版权问题,请联系本站管理员予以更改或删除。
有用1
分享