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)

相关推荐