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),并添加依赖

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.su</groupId>
    <artifactId>trace-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 引入 Spring Boot 统一版本父项目管理依赖 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
    </parent>

    <dependencies>
       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </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>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.13.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>
    </dependencies>

</project>

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

java 复制代码
@Data
public class UserInfo {
    private String traceId;

    private String spanId;

    private String userNo;
}

 

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

java 复制代码
public class UserContext {
    public static final TransmittableThreadLocal<UserInfo> userInfo = new TransmittableThreadLocal<>();

    private UserContext() {
    }

    public static void set(UserInfo user) {
        userInfo.set(user);
    }

    public static UserInfo get() {
        return (UserInfo) userInfo.get();
    }

    public static void remove() {
        userInfo.remove();
    }


    public static String userNo() {
        UserInfo userInfo = (UserInfo) UserContext.userInfo.get();
        return Objects.isNull(userInfo) ? null : userInfo.getUserNo();
    }
}

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

java 复制代码
@Slf4j
@Component
public class UserContextInterceptor implements HandlerInterceptor {
    private static final String TRACE_ID = "traceId";
    private static final String SPAN_ID = "spanId";
    private static final String APP_ID = "appId";
    private static final String USER_NO = "userNo";

    @Resource
    private AppIdsConfig appIdsConfig;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 验证请求方的 AppId
        String appId = request.getHeader(APP_ID);
        Set<String> appIdsSet = appIdsConfig.getIdsSet();
        if (StringUtils.isBlank(appId) || CollectionUtils.isEmpty(appIdsSet) || !appIdsSet.contains(appId)) {
            returnJson(response, ComResponse.fail(ResponseCodeEnums.AUTHOR_ERROR_CODE));
            return false;
        }

        String spanId = UUIDGenerator.getUUID();
        String traceId = request.getHeader(TRACE_ID);
        String userNo = request.getHeader(USER_NO);
        if (StringUtils.isEmpty(traceId)) {
            traceId = UUIDGenerator.getUUID();
        }

        UserInfo userInfo = new UserInfo();
        userInfo.setUserNo(userNo);
        userInfo.setTraceId(traceId);
        userInfo.setSpanId(spanId);
        UserContext.set(userInfo);

        MDC.put(TRACE_ID, traceId);
        MDC.put(SPAN_ID, spanId);

        log.info("{requestServer:{},traceId:{},spanId:{}}", appId, traceId, spanId);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        UserContext.remove();
        MDC.remove(TRACE_ID);
        MDC.remove(SPAN_ID);
    }


    private void returnJson(HttpServletResponse response, ComResponse<Object> result) {
        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpStatus.UNAUTHORIZED.value());
        response.setContentType("application/json;charset=UTF-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.print(JacksonUtil.toJsonString(result));
        } catch (IOException e) {
            log.error("response error", e);
        }
    }
}

5.AppIdsConfig配置类,读取配置文件中的信息

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

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

java 复制代码
@ConfigurationProperties(prefix = "app")
@Component
public class AppIdsConfig {
    private String ids;
    private Set<String> idsSet;

    public String getIds() {
        return ids;
    }

    public void setIds(String ids) {
        this.ids = ids;
        if(StringUtils.isNotEmpty(ids)){
            this.idsSet = org.springframework.util.StringUtils.commaDelimitedListToSet(ids);
        }else{
            this.idsSet = Collections.emptySet();
        }
    }

    public Set<String> getIdsSet() {
        return idsSet;
    }

}

6.配置拦截器

java 复制代码
@Configuration
public class TraceConfiguration implements WebMvcConfigurer {
    @Resource
    private UserContextInterceptor userContextInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userContextInterceptor).addPathPatterns("/**")
                .excludePathPatterns("/swagger-resources/**", "/webjars/**", "/v2/**", "/doc.html/**");
    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
        ObjectMapper objectMapper = new ObjectMapper();

        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.registerModule(new JavaTimeModule());
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations(
                "classpath:/static/");
        registry.addResourceHandler("doc.html").addResourceLocations(
                "classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations(
                "classpath:/META-INF/resources/webjars/");
    }
}

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

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

java 复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.su.trace.configure.TraceConfiguration

8.maven打包 clean,install/deploy

三、引用stater

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

1.添加依赖

java 复制代码
 <dependency>
            <groupId>com.su</groupId>
            <artifactId>trace-spring-boot-starter</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

java 复制代码
app.ids=activity,crm,order,product,customer

3.测试类

java 复制代码
@Slf4j
@RestController
@RequestMapping("test")
public class TestController {
    @GetMapping("/trace")
    public void testTrace(){
        log.info("userID:{}", UserContext.userNo());
    }

}

4.测试

相关推荐
敲代码的小王!2 小时前
MD5加密算法和BCrypt密码加密算法
java·算法·安全
李长渊哦6 小时前
使用Druid连接池优化Spring Boot应用中的数据库连接
数据库·spring boot·后端
web135085886356 小时前
【Spring Boot】Spring AOP动态代理,以及静态代理
spring boot·后端·spring
罗政7 小时前
冒险岛079 V8 整合版源码搭建教程+IDEA启动
java·ide·intellij-idea
架构默片7 小时前
【JAVA工程师从0开始学AI】,第五步:Python类的“七十二变“——当Java的铠甲遇见Python的液态金属
java·开发语言·python
zzyh1234568 小时前
springcloud的组件及作用
后端·spring·spring cloud
不只会拍照的程序猿8 小时前
从插入排序到希尔排序
java·开发语言·数据结构·算法·排序算法
尚学教辅学习资料8 小时前
基于SpringBoot的图书借阅小程序+LW参考示例
spring boot·后端·小程序·java毕设·图书借阅
一 乐8 小时前
高校体育场管理系统系统|体育场管理系统小程序设计与实现(源码+数据库+文档)
前端·javascript·数据库·spring boot·高校体育馆系统
山海不说话8 小时前
从零搭建微服务项目Base(第5章——SpringBoot项目LogBack日志配置+Feign使用)
spring boot·后端·spring·spring cloud·微服务·logback