为什么选择 starter 同时兼容 spring boot 1 和 spring boot 2
从用户角度来看
如果不在一个 starter 里兼容, 比如用版本号来区分, spring boot 1 的用户使用 1.,spring boot 2 用户使用 2., 这样用户升级会有很大困扰.
另外, 我们的 starter 是以日期为版本号的, 如果再分化, 则就会出现 2018-06-stable-boot1,2018-06-stable-boot2, 这样子很丑陋.
从开发者角度来看
要同时维护两个分支, 修改代码时要合到两个分支上, 发版本时要同时两个. 如果有统一的 bom 文件, 也需要维护两份. 工作量翻倍, 而且很容易出错.
因此, 我们决定在同一个代码分支里, 同时支持 spring boot 1/2. 减少开发维护成本, 减少用户使用困扰.
编写兼容的 starter 的难点
spring boot starter 的代码入口都是在各种 @Configuration 类里, 这为我们编写兼容 starter 提供了条件.
但还是有一些难点:
某些类不兼容, 比如在 spring boot 2 里删除掉了
代码模块, maven 依赖怎样组织
怎样保证 starter 在 spring boot 1/2 里都能正常工作
通过 ASM 分析现有的 starter 里不兼容的类
https://github.com/hengyunabc/springboot-classchecker
springboot-classchecker 可以从 jar 包里扫描出哪些类在 spring boot 2 里不存在的.
工作原理: springboot-classchecker 自身在 pom.xml 里依赖的是 spring boot 2, 扫描 jar 包里通过 ASM 分析到所有的 String, 提取出类名之后, 再尝试在 ClassLoader 里加载, 如果加载不到, 则说明这个类在 spring boot 2 里不存在.
例如扫描 demo-springboot1-starter.jar:
- mvn clean package
- java -jar target/classchecker-0.0.1-SNAPSHOT.jar demo-springboot1-starter.jar
结果是:
- path: demo-springboot1-starter.jar
- org.springframework.boot.actuate.autoconfigure.ConditionalOnEnabledHealthIndicator
- org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.HealthIndicatorAutoConfiguration
那么这些类在 spring boot 2 在哪里了?
实际上是改了 package:
- org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator
- org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.health.HealthIndicatorAutoConfiguration
通过扫描 20 多个 starter jar 包, 发现不兼容的类有:
- org.springframework.boot.env.PropertySourcesLoader
- org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder
- org.springframework.boot.bind.RelaxedDataBinder
Endpoint/HealthIndicator 相关的类
可以总结:
spring boot 核心的类, autoconfigure 相关的没有改动
大部分修改的是 Endpoint/HealthIndicator 相关的类
spring-boot-utils 兼容工具类
- https://github.com/hengyunabc/spring-boot-utils
- BinderUtils
在 spring boot 1 里, 注入环境变量有时需要用到 RelaxedDataBinder:
- MyProperties myProperties = new MyProperties();
- MutablePropertySources propertySources = environment.getPropertySources();
- new RelaxedDataBinder(myProperties, "spring.my").bind(new PropertySourcesPropertyValues(propertySources));
在 spring boot 2 里, RelaxedDataBinder 删除掉了, 新的写法是用 Binder:
- Binder binder = Binder.get(environment);
- MyProperties myProperties = binder.bind("spring.my", MyProperties.class).get();
通过 BinderUtils, 则可以同时支持 spring boot1/2:
MyProperties myProperties = BinderUtils.bind(environment, "spring.my", MyProperties.class);
Starter 代码模块组织
下面以实际的一个 starter 来说明.
https://github.com/hengyunabc/endpoints-spring-boot-starter
spring boot web 应用的 mappings 信息, 可以在 / mappings endpoint 查询到. 但是这么多 endpoint, 它们都提供了哪些 url?
endpoints-spring-boot-starter 的功能是展示所有 endpoints 的 url mappings 信息
endpoints-spring-boot-starter 里需要给 spring boot 1/2 同时提供 endpoint 功能, 代码模块如下:
- endpoints-spring-boot-starter
- |__ endpoints-spring-boot-autoconfigure1
- |__ endpoints-spring-boot-autoconfigure2
endpoints-spring-boot-autoconfigure1 模块在 pom.xml 里依赖的是 spring boot 1 相关的 jar, 并且都设置为 < optional>true</optional>
endpoints-spring-boot-autoconfigure2 的配置类似
endpoints-spring-boot-starter 依赖 autoconfigure1 和 autoconfigure2
如果有公共的逻辑, 可以增加一个 commons 模块
Endpoint 兼容
以 endpoints-spring-boot-autoconfigure1 模块为例说明怎样处理.
EndPointsEndPoint 类继承自 spring boot 1 的 AbstractMvcEndpoint:
- @ConfigurationProperties("endpoints.endpoints")
- public class EndPointsEndPoint extends AbstractMvcEndpoint {
通过 @ManagementContextConfiguration 引入
- @ManagementContextConfiguration
- public class EndPointsEndPointManagementContextConfiguration {
- @Bean
- @ConditionalOnMissingBean
- @ConditionalOnEnabledEndpoint("endpoints")
- public EndPointsEndPoint EndPointsEndPoint() {
- EndPointsEndPoint endPointsEndPoint = new EndPointsEndPoint();
- return endPointsEndPoint;
- }
- }
在 META-INF/resources/spring.factories 里配置
- org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\
- io.GitHub.hengyunabc.endpoints.autoconfigure1.EndPointsEndPointManagementContextConfiguration
因为 org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration 是只在 spring boot 1 里, 在 spring boot 2 的应用里不会加载它, 所以 autoconfigure1 模块天然兼容 spring boot 2.
那么类似的, autoconfigure2 模块里在 **META-INF/resources/spring.factories** 配置的是
- org.springframework.boot.actuate.autoconfigure.Web.ManagementContextConfiguration=\
- io.GitHub.hengyunabc.endpoints.autoconfigure2.ManagementApplicationcontextHolderConfiguration
仔细对比, 可以发现是 spring boot 2 下面修改了 ManagementContextConfiguration 的包名, 所以对于 Endpoint 天然是兼容的, 不同的模块自己编绎就可以了.
HealthIndicator 的兼容
类似 Endpoint 的处理, spring boot 1/2 的代码分别放不同的 autoconfigure 模块里, 然后各自的 @Configuration 类分别使用 @ConditionalOnSpringBoot1/@ConditionalOnSpringBoot2 来判断.
通过集成测试保证兼容性
还是以 endpoints-spring-boot-autoconfigure1 模块为例.
这个模块是为 spring boot 1 准备的, 则它的集成测试要配置为 spring boot 2.
参考相关的代码: 查看
在 springboot2demo/pom.xml 里依赖 spring boot 2
在 verify.groovy 里检测应用是否启动成功
总结
通过 ASM 分析现有的 starter 里不兼容的类
配置注入通过 BinderUtils 解决
各自的 @Configuration 类分别用 @ConditionalOnSpringBoot1/@ConditionalOnSpringBoot2 来判断
代码分模块: commons 放公共逻辑, autoconfigure1/autoconfigure2 对应 spring boot 1/2 的自动装配, starter 给应用依赖
Endpoint 的 Configuration 入口是 ManagementContextConfiguration, 因为 spring boot 2 里修改了 package, 所以直接在 spring.factories 里配置即可
通过集成测试保证兼容性
如果某一天, 不再需要支持 spring boot 1 了, 则直接把 autoconfigure1 模块去掉即可
来源: http://www.jianshu.com/p/ae24869291a3