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



    4.0.0

    com.su
    trace-spring-boot-starter
    1.0-SNAPSHOT

    
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.4.RELEASE
    

    
       
        
            org.springframework.boot
            spring-boot-autoconfigure
        

        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        

        
            org.projectlombok
            lombok
            1.18.24
        
        
            com.alibaba
            transmittable-thread-local
            2.13.2
        
        
            org.apache.commons
            commons-lang3
        
    

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

@Data
public class UserInfo {
    private String traceId;

    private String spanId;

    private String userNo;
}

 

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

public class UserContext {
    public static final TransmittableThreadLocal 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 拦截器,拦截请求中的信息并存储

@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 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 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,如果配置文件中找不到,则会报错

@ConfigurationProperties(prefix = "app")
@Component
public class AppIdsConfig {
    private String ids;
    private Set 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 getIdsSet() {
        return idsSet;
    }

}

6.配置拦截器

@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> 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文件,添加配置:

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

8.maven打包 clean,install/deploy

三、引用stater

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

1.添加依赖

 
            com.su
            trace-spring-boot-starter
            1.0-SNAPSHOT
        

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

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

3.测试类

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

}

4.测试

你可能感兴趣的:(spring,boot,java,spring)