手把手教你自定义自己SpringBoot Starter组件源码剖析
我们知道SpringBoot Starter也就是启动器。是SpringBoot组件化的一大优点。基于这个思想,基于这个思想SpringBoot 才变得非常强大,官方给我们提供很多开箱即用的启动器。
Spring Boot Starter 是 Spring Boot 的一个重要特性,它有以下优点:
- 依赖管理:Starter 自动处理项目的依赖关系,使得开发者无需手动添加和管理每个依赖。
- 自动配置:Starter 提供了一种自动配置的方式,可以根据你的 classpath 和你定义的属性自动配置 Spring 应用。
- 简化开发:通过提供各种服务的 Starter(如数据库、安全、缓存等),极大地简化了开发过程。
- 减少样板代码:由于 Starter 的自动配置和依赖管理,开发者可以专注于业务逻辑,而不是配置和基础设施代码。
- 快速原型开发:使用 Starter 可以快速创建可运行的原型。
- 易于理解和使用:Spring Boot Starter 的设计目标之一就是让非专业的开发者也能快速上手。
- 社区支持:除了官方提供的 Starter,还有大量的社区提供的 Starter,可以满足各种特定需求。
我现在手把手教大家如何封装自己的starter 做自己的springboot组件,当然你也可以发布自己的starter 到maven中央仓库供大家使用
剖析SpringBoot自带Starter
我们以WebMvcAutoConfiguration这个自动加载为例
自动配置类要能加载,有一个要求,源码分析结果是,需要在META-INFspring.factories中做如下配置
JAVA复制代码# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration= org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration, org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,
这样SpringBoot在启动完成时候,会找到我们引入,的starter 找到META-INFspring.factories 属性文件,找到需要自动加载配置的类路径,然后帮我们自动注入到Spring IOC 容器,我们在项目中就可以直接使用了。
这里实现自动加载还要依赖一些注解如:
js复制代码@Configuration // 指定这个类是个配置类 @ConditionalOnXXX // 在指定条件成立的情况下自动配置类生效 @AutoConfigureOrder //配置类顺序 @AutoConfigureAfter // 在哪个配置类之后 @Bean //给容器中添加组件 @ConfigurationProperties //结合相关的XXXProperties类 来绑定相关的配置 @EnableConfigurationProperties // 让XXXProperties加入到容器中,别人就可以自动装配
自定义自己的starter
剖析了SpringBoot 官方的starter 我们自定义自己的starter,(我们仿照着写)
命名规范
配置提示
如果自定义属性文件中,需要IDEA智能提示需要引入
pom复制代码 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
定义starter
这里我以自己封装总结我工作以来总结项目封装的一个SpringBoot starter为例
java复制代码 <dependency> <groupId>cn.soboys</groupId> <artifactId>rest-api-spring-boot-starter</artifactId> <version>1.2.0</version> </dependency>
就是我自己封装的start。已经发布中央仓库。
目前更新版本1.3.0 功能如下
- 支持一键配置自定义RestFull API 统一格式返回
- 支持RestFull API 错误国际化
- 支持全局异常处理,全局参数验证处理
- 业务错误断言工具封装,遵循错误优先返回原则
- redis工作封装。支持所有key操作工具
- RestTemplate 封装 POST,GET 请求工具
- 日志集成。自定义日志路径,按照日志等级分类,支持压缩和文件大小分割。按时间显示
- 工具库集成 集成了lombok,hutool,commons-lang3,guava。不需要自己单个引入
- 集成MyBatisPlus一键代码生成
rest-api-spring-boot-starter 仓库地址 Github
- 自定义配置属性文件
yml复制代码rest-api: enabled: false logging: path: ./logs i18n: # 若前端无header传参则返回中文信息 i18n-header: Lang default-lang: cn message: # admin internal_server_error: en: Internal Server Error cn: 系统错误 not_found: en: Not Found cn: 请求资源不存在
- 定义属性配置类
java复制代码package cn.soboys.restapispringbootstarter.i18n; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import java.util.Map; import java.util.Optional; /** * @author 公众号 程序员三时 * @version 1.0 * @date 2023/6/26 11:55 * @webSite https://github.com/coder-amiao */ //@PropertySource(value = "classpath:i18n.yaml", factory = YamlPropertySourceFactory.class) @Configuration @ConfigurationProperties(prefix = "rest-api.i18n") @Data public class I18NMessage { /** * message-key:<lang:message> */ private Map<String, Map<String, String>> message; /** * Default language setting (Default "cn"). */ private String defaultLang = "cn"; private String i18nHeader = "Lang"; /** * get i18n message * * @param key * @param language * @return */ public String message(I18NKey key, String language) { return Optional.ofNullable(message.get(key.key())) .map(map -> map.get(language == null ? defaultLang : language)) .orElse(key.key()); } /** * get i18n message * * @param key * @param language * @return */ public String message(String key, String language) { return Optional.ofNullable(message.get(key)) .map(map -> map.get(language == null ? defaultLang : language)) .orElse(key); } }
- 定义BeanAutoConfiguration自动加载配置类
java复制代码package cn.soboys.restapispringbootstarter.config; import cn.soboys.restapispringbootstarter.ApplicationRunner; import cn.soboys.restapispringbootstarter.ExceptionHandler; import cn.soboys.restapispringbootstarter.ResultHandler; import cn.soboys.restapispringbootstarter.aop.LimitAspect; import cn.soboys.restapispringbootstarter.i18n.I18NMessage; import cn.soboys.restapispringbootstarter.utils.RedisTempUtil; import cn.soboys.restapispringbootstarter.utils.RestFulTemp; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.web.client.RestTemplate; import java.nio.charset.Charset; import java.util.List; /** * @author 公众号 程序员三时 * @version 1.0 * @date 2023/6/27 11:36 * @webSite https://github.com/coder-amiao */ @Configuration @ConditionalOnProperty(name = "rest-api.enabled", havingValue = "true") public class BeanAutoConfiguration { @Bean public I18NMessage i18NMessage() { return new I18NMessage(); } @Bean public ResultHandler resultHandler() { return new ResultHandler(); } @Bean public ExceptionHandler exceptionHandler() { return new ExceptionHandler(); } @Bean public StartupApplicationListener startupApplicationListener() { return new StartupApplicationListener(); } @Bean public RestApiProperties restApiProperties() { return new RestApiProperties(); } @Bean public RestApiProperties.LoggingProperties loggingProperties(RestApiProperties restApiProperties) { return restApiProperties.new LoggingProperties(); } @Bean public ApplicationRunner applicationRunner() { return new ApplicationRunner(); } /** * restTemplate 自动注入 */ @Configuration @ConditionalOnProperty(name = "rest-api.enabled", havingValue = "true") class RestTemplateConfig { /** * 第三方请求要求的默认编码 */ private final Charset thirdRequest = Charset.forName("utf-8"); @Bean public RestTemplate restTemplate(ClientHttpRequestFactory factory) { RestTemplate restTemplate = new RestTemplate(factory); // 处理请求中文乱码问题 List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters(); for (HttpMessageConverter<?> messageConverter : messageConverters) { if (messageConverter instanceof StringHttpMessageConverter) { ((StringHttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest); } if (messageConverter instanceof MappingJackson2HttpMessageConverter) { ((MappingJackson2HttpMessageConverter) messageConverter).setDefaultCharset(thirdRequest); } if (messageConverter instanceof AllEncompassingFormHttpMessageConverter) { ((AllEncompassingFormHttpMessageConverter) messageConverter).setCharset(thirdRequest); } } return restTemplate; } @Bean public ClientHttpRequestFactory simpleClientHttpRequestFactory() { SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setConnectTimeout(15000); factory.setReadTimeout(5000); return factory; } @Bean public RestFulTemp restFulTemp() { return new RestFulTemp(); } } }
- 自动装配 在项目
spring.factories 配置自己加载配置类
xml复制代码org.springframework.boot.autoconfigure.EnableAutoConfiguration= cn.soboys.restapispringbootstarter.config.BeanAutoConfiguration, cn.soboys.restapispringbootstarter.config.BeanAutoConfiguration.RestTemplateConfig, cn.soboys.restapispringbootstarter.utils.RedisTempUtil
扩展思考,我们可以看到SpringBoot官方stater 很多启用都类似@Enablexxx注解 这个怎么实现。我的
rest-api-spring-boot-starter 1.3.0已经实现不需要在application.properties配置一行 直接在启动类或者配置类使用EnableRestFullApi就可以使用全部功能
作者:程序员三时
链接:
https://juejin.cn/post/7252712159561711674
文章评论(0)