码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • Springboot自定义starter


    一、start背景和简介

    1.背景

    工作中经常需要将多个springboot项目共同的非业务模块抽取出来,比如访问日志、维护请求上下文中的用户信息或者链路id等等。此次模拟的是请求中用户信息维护,方便整个请求中用户信息的取用。

    2.作用

    根据项目组的实际需求,封装starter,可以简化开发,统一规范的效果。

    3.规范

    官方的starter包规范:spring-boot-starter-xxx

    自定义starter包规范:xxx-spring-boot-starter

    二、stater定义

    1.创建maven项目(trace-spring-boot-stater),并添加依赖

    1. "1.0" encoding="UTF-8"?>
    2. "http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. 4.0.0
    6. com.su
    7. trace-spring-boot-starter
    8. 1.0-SNAPSHOT
    9. org.springframework.boot
    10. spring-boot-starter-parent
    11. 2.3.4.RELEASE
    12. org.springframework.boot
    13. spring-boot-autoconfigure
    14. org.springframework.boot
    15. spring-boot-configuration-processor
    16. true
    17. org.projectlombok
    18. lombok
    19. 1.18.24
    20. com.alibaba
    21. transmittable-thread-local
    22. 2.13.2
    23. org.apache.commons
    24. commons-lang3

    2.UserInfo  定义请求上下文内容的类

    1. @Data
    2. public class UserInfo {
    3. private String traceId;
    4. private String spanId;
    5. private String userNo;
    6. }

    3.UserContext 实现同一个或父子线程中上下文用户信息的取用

    1. public class UserContext {
    2. public static final TransmittableThreadLocal userInfo = new TransmittableThreadLocal<>();
    3. private UserContext() {
    4. }
    5. public static void set(UserInfo user) {
    6. userInfo.set(user);
    7. }
    8. public static UserInfo get() {
    9. return (UserInfo) userInfo.get();
    10. }
    11. public static void remove() {
    12. userInfo.remove();
    13. }
    14. public static String userNo() {
    15. UserInfo userInfo = (UserInfo) UserContext.userInfo.get();
    16. return Objects.isNull(userInfo) ? null : userInfo.getUserNo();
    17. }
    18. }

    4.UserContextInterceptor 拦截器,拦截请求中的信息并存储

    1. @Slf4j
    2. @Component
    3. public class UserContextInterceptor implements HandlerInterceptor {
    4. private static final String TRACE_ID = "traceId";
    5. private static final String SPAN_ID = "spanId";
    6. private static final String APP_ID = "appId";
    7. private static final String USER_NO = "userNo";
    8. @Resource
    9. private AppIdsConfig appIdsConfig;
    10. @Override
    11. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
    12. // 验证请求方的 AppId
    13. String appId = request.getHeader(APP_ID);
    14. Set appIdsSet = appIdsConfig.getIdsSet();
    15. if (StringUtils.isBlank(appId) || CollectionUtils.isEmpty(appIdsSet) || !appIdsSet.contains(appId)) {
    16. returnJson(response, ComResponse.fail(ResponseCodeEnums.AUTHOR_ERROR_CODE));
    17. return false;
    18. }
    19. String spanId = UUIDGenerator.getUUID();
    20. String traceId = request.getHeader(TRACE_ID);
    21. String userNo = request.getHeader(USER_NO);
    22. if (StringUtils.isEmpty(traceId)) {
    23. traceId = UUIDGenerator.getUUID();
    24. }
    25. UserInfo userInfo = new UserInfo();
    26. userInfo.setUserNo(userNo);
    27. userInfo.setTraceId(traceId);
    28. userInfo.setSpanId(spanId);
    29. UserContext.set(userInfo);
    30. MDC.put(TRACE_ID, traceId);
    31. MDC.put(SPAN_ID, spanId);
    32. log.info("{requestServer:{},traceId:{},spanId:{}}", appId, traceId, spanId);
    33. return true;
    34. }
    35. @Override
    36. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
    37. UserContext.remove();
    38. MDC.remove(TRACE_ID);
    39. MDC.remove(SPAN_ID);
    40. }
    41. private void returnJson(HttpServletResponse response, ComResponse result) {
    42. response.setCharacterEncoding("UTF-8");
    43. response.setStatus(HttpStatus.UNAUTHORIZED.value());
    44. response.setContentType("application/json;charset=UTF-8");
    45. try (PrintWriter writer = response.getWriter()) {
    46. writer.print(JacksonUtil.toJsonString(result));
    47. } catch (IOException e) {
    48. log.error("response error", e);
    49. }
    50. }
    51. }
    52. 5.AppIdsConfig配置类,读取配置文件中的信息

      注意:使用@ConfigurationProperties的好处是懒加载,如果配置文件中没有配置也不会报错。

      如果使用@Value,如果配置文件中找不到,则会报错

      1. @ConfigurationProperties(prefix = "app")
      2. @Component
      3. public class AppIdsConfig {
      4. private String ids;
      5. private Set idsSet;
      6. public String getIds() {
      7. return ids;
      8. }
      9. public void setIds(String ids) {
      10. this.ids = ids;
      11. if(StringUtils.isNotEmpty(ids)){
      12. this.idsSet = org.springframework.util.StringUtils.commaDelimitedListToSet(ids);
      13. }else{
      14. this.idsSet = Collections.emptySet();
      15. }
      16. }
      17. public Set getIdsSet() {
      18. return idsSet;
      19. }
      20. }

      6.配置拦截器

      1. @Configuration
      2. public class TraceConfiguration implements WebMvcConfigurer {
      3. @Resource
      4. private UserContextInterceptor userContextInterceptor;
      5. @Override
      6. public void addInterceptors(InterceptorRegistry registry) {
      7. registry.addInterceptor(userContextInterceptor).addPathPatterns("/**")
      8. .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/doc.html/**");
      9. }
      10. @Override
      11. public void configureMessageConverters(List> converters) {
      12. MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
      13. ObjectMapper objectMapper = new ObjectMapper();
      14. objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
      15. objectMapper.registerModule(new JavaTimeModule());
      16. }
      17. @Override
      18. public void addResourceHandlers(ResourceHandlerRegistry registry) {
      19. registry.addResourceHandler("/**").addResourceLocations(
      20. "classpath:/static/");
      21. registry.addResourceHandler("doc.html").addResourceLocations(
      22. "classpath:/META-INF/resources/");
      23. registry.addResourceHandler("/webjars/**").addResourceLocations(
      24. "classpath:/META-INF/resources/webjars/");
      25. }
      26. }

      7.最后重点,创建配置文件,把需要自动装载的类配置上

      我用的springboot3以下版本,所以在resource/META-INF下新建一个spring.factories文件,添加配置:

      1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
      2. com.su.trace.configure.TraceConfiguration

      8.maven打包 clean,install/deploy

      三、引用stater

      在其他项目是使用上边自定义的starter

      1.添加依赖

      1. com.su
      2. trace-spring-boot-starter
      3. 1.0-SNAPSHOT

      2.添加配置文件内容(如果多个项目的配置内容一致,可以考虑将配置信息放到公共配置里)

      app.ids=activity,crm,order,product,customer
      

      3.测试类

      1. @Slf4j
      2. @RestController
      3. @RequestMapping("test")
      4. public class TestController {
      5. @GetMapping("/trace")
      6. public void testTrace(){
      7. log.info("userID:{}", UserContext.userNo());
      8. }
      9. }

      4.测试

    53. 相关阅读:
      Windows系统下使用tar命令,压缩文件与解压缩文件并指定路径
      用Python剪辑视频?太简单了
      xinput1_3.dll丢失的解决方法,快速修复xinput1_3.dll文件
      PART1 WebPack
      Codeforces Round #836 (Div. 2) A-D
      Mysql基础(三)——常用函数与聚合函数
      Mini Linux嵌入式设备服务器
      嵌入式开发:如何调试Arm Cortex-M上的硬故障
      linux redis自启动
      web前端-css中最直观的反馈-伪类及伪元素选择器的使用
    54. 原文地址:https://blog.csdn.net/qq_34896887/article/details/134726096
      • 最新文章
      • 攻防演习之三天拿下官网站群
        数据安全治理学习——前期安全规划和安全管理体系建设
        企业安全 | 企业内一次钓鱼演练准备过程
        内网渗透测试 | Kerberos协议及其部分攻击手法
        0day的产生 | 不懂代码的"代码审计"
        安装scrcpy-client模块av模块异常,环境问题解决方案
        leetcode hot100【LeetCode 279. 完全平方数】java实现
        OpenWrt下安装Mosquitto
        AnatoMask论文汇总
        【AI日记】24.11.01 LangChain、openai api和github copilot
      • 热门文章
      • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
        奉劝各位学弟学妹们,该打造你的技术影响力了!
        五年了,我在 CSDN 的两个一百万。
        Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
        面试官都震惊,你这网络基础可以啊!
        你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
        心情不好的时候,用 Python 画棵樱花树送给自己吧
        通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
        13 万字 C 语言从入门到精通保姆级教程2021 年版
        10行代码集2000张美女图,Python爬虫120例,再上征途
      Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
      正则表达式工具 cron表达式工具 密码生成工具

      京公网安备 11010502049817号