SpringBoot源码解析-升级版自定义Starter
分享
有用
返回
上一篇文章中,我们学习了如何自定义一个Starter,而今天要给大家分享的是复杂点的自定义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>12345678910111213141516171819复制代码类型:[java]
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; }1234567891011121314151617181920212223复制代码类型:[java]
注解里面包含两个属性,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;@Slf4jpublic 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); } } } }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394复制代码类型:[java]
自定义拦截器中使用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;@Configurationpublic class JavaFamilyLogAutoConfiguration implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 添加自己实现的拦截器 registry.addInterceptor(new JavaFamilyInterceptor()); } }1234567891011121314151617复制代码类型:[java]
5.编写配置文件
在resources文件夹下创建META-INF文件夹,在META-INF文件夹下创建spring.factories文件。
org.springframework.boot.autoconfigure.EnableAutoConfiguration= com.javafamily.config.JavaFamilyLogAutoConfiguration12复制代码类型:[java]
项目整体如下:
引入自定义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>12345复制代码类型:[java]
创建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;@RestControllerpublic 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; } }123456789101112131415161718192021222324复制代码类型:[java]
接着分别请求两个方法,我们看下效果。
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 ms2021-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 ms123456789101112复制代码类型:[java]
根据效果,我们可以看到test接口耗时为null说明enable属性生效了,logTest接口也打印了请求参数,说明自定义Starter的功能都实现了。同时日志中也包含了接口请求一些重要的纬度信息,大家可以根据自己的需求再添加一些纬度。
好了,本篇文章到此结束,我们下次见。
赞 (0)