diff --git a/README.md b/README.md index 51f5076..80fc854 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- Downloads + Downloads Build Status Build Status Coverage Status diff --git a/blade-core-boot/src/main/resources/bootstrap.yml b/blade-core-boot/src/main/resources/bootstrap.yml index 3c0ebe1..e91fbd5 100644 --- a/blade-core-boot/src/main/resources/bootstrap.yml +++ b/blade-core-boot/src/main/resources/bootstrap.yml @@ -94,7 +94,7 @@ mybatis-plus: swagger: title: SpringBlade 接口文档系统 description: SpringBlade 接口文档系统 - version: 5.0.0 + version: 5.0.1 license: Powered By SpringBlade licenseUrl: https://bladex.vip terms-of-service-url: https://bladex.vip diff --git a/blade-core-cloud/pom.xml b/blade-core-cloud/pom.xml index a839b97..4b7629d 100644 --- a/blade-core-cloud/pom.xml +++ b/blade-core-cloud/pom.xml @@ -41,6 +41,16 @@ org.springframework.cloud spring-cloud-starter-bootstrap + + + tools.jackson.core + jackson-databind + de.codecentric diff --git a/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/FeignJacksonEnvPostProcessor.java b/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/FeignJacksonEnvPostProcessor.java new file mode 100644 index 0000000..360c8f6 --- /dev/null +++ b/blade-starter-loadbalancer/src/main/java/org/springblade/core/loadbalancer/config/FeignJacksonEnvPostProcessor.java @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springblade.core.loadbalancer.config; + +import net.dreamlu.mica.auto.annotation.AutoEnvPostProcessor; +import org.jspecify.annotations.NonNull; +import org.springframework.boot.EnvironmentPostProcessor; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.env.DefaultPropertiesPropertySource; +import org.springframework.core.Ordered; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.util.ClassUtils; + +import java.util.Map; + +/** + * Feign 分页模型 JSON 支持的装配兼容处理 + * + *

+ * Feign 为 Spring Data 的分页与排序模型提供的两个 JSON 扩展仅有 Jackson 3 实现, + * 而其装配条件本应校验的 Jackson 模块基类在 Jackson 3 中已改名,上游条件退化为恒成立, + * 于是只要分页与排序模型在类路径上就会装配,Jackson 3 缺席时中断容器启动。 + *

+ * + *

+ * 故仅在 Jackson 3 缺席时给出关闭的默认值,该默认值优先级最低,下游可显式开启; + * Jackson 3 在场时不再干预,交由上游条件判定。 + *

+ * + * @author Chill + */ +@AutoEnvPostProcessor +public class FeignJacksonEnvPostProcessor implements EnvironmentPostProcessor, Ordered { + + /** + * Feign 分页模型 JSON 支持开关 + */ + private static final String FEIGN_JACKSON_KEY = "spring.cloud.openfeign.autoconfiguration.jackson.enabled"; + + /** + * Jackson 3 模块基类,上述两个扩展的直接父类 + */ + private static final String JACKSON_MODULE_CLASS = "tools.jackson.databind.JacksonModule"; + + @Override + public void postProcessEnvironment(@NonNull ConfigurableEnvironment environment, SpringApplication application) { + if (ClassUtils.isPresent(JACKSON_MODULE_CLASS, application.getClassLoader())) { + return; + } + Map defaults = Map.of(FEIGN_JACKSON_KEY, Boolean.FALSE.toString()); + DefaultPropertiesPropertySource.addOrMerge(defaults, environment.getPropertySources()); + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + +} diff --git a/doc/guide/blade-tool-5.0升级适配指南.md b/doc/guide/blade-tool-5.0升级适配指南.md index c6fa771..ebee1db 100644 --- a/doc/guide/blade-tool-5.0升级适配指南.md +++ b/doc/guide/blade-tool-5.0升级适配指南.md @@ -348,7 +348,7 @@ public class JacksonConfiguration { > ⚠️【务必看清,这是升级中最隐蔽的一个坑】Spring 7 的 `WebMvcConfigurationSupport.getMessageConverters()` 执行顺序是:先 `configureMessageConverters(list)` → 若 list 仍为空才 `addDefaultHttpMessageConverters(list)`(这一步才注册 ByteArray/String/Resource/ResourceRegion/Form 以及 JSON 转换器)→ 最后 `extendMessageConverters(list)`。 > 若重写 `configureMessageConverters` 并向 list 添加任何转换器,list 变为非空,`addDefaultHttpMessageConverters` 会被**整段跳过**,MVC 最终只剩下你加入的这一个转换器——文件下载、`byte[]`/图片/PDF、纯文本、表单请求体全部 406/`HttpMessageNotWritableException` 失败。**编译与纯 JSON 接口都正常,极难在冒烟测试中发现。** -> 正解是重写 `extendMessageConverters`:它在默认列表构建完成之后回调,拿到的是**完整**的默认转换器,再原地替换 JSON(因 §5.5⑤ 已强制 `preferred-json-mapper=jackson2`,默认 JSON 转换器为 `MappingJackson2HttpMessageConverter`,属 `AbstractJackson2HttpMessageConverter`)与 String 转换器即可,其余转换器原样保留。 +> 正解是重写 `extendMessageConverters`:它在默认列表构建完成之后回调,拿到的是**完整**的默认转换器,再原地替换 JSON(因 §5.5⑤ 已在托管层剥离 Jackson 3,默认 JSON 转换器为 `MappingJackson2HttpMessageConverter`,属 `AbstractJackson2HttpMessageConverter`)与 String 转换器即可,其余转换器原样保留。 ```java @Override @@ -387,7 +387,28 @@ public void extendMessageConverters(List> converters) { > 与 Jackson 无关的 `org.springframework.lang.Nullable/NonNull` 在 Spring 7 也被弃用(官方推 JSpecify),那是一处独立、面广的迁移,不在本表范围,别混入 Jackson 的 `@SuppressWarnings`。 -**⑤ 【关键】强制 preferred-json-mapper = jackson2**——新增一个 `LauncherService`,否则 Boot 4 默认注册 Jackson 3 转换器,§5.5③ 的 `instanceof AbstractJackson2HttpMessageConverter` 命不中,Blade 的 JSON 处理静默失效: +**⑤ 【关键】让 Boot 4 选中 Jackson 2**——Boot 的择一逻辑写在 `org.springframework.boot.http.converter.autoconfigure` 下,读源码可见是**两条并列通路,满足其一即可**(`Jackson2HttpMessageConvertersConfiguration.PreferJackson2OrJacksonUnavailableCondition` 是 `AnyNestedCondition`): + +```java +// Jackson 3 分支——三个条件同时成立才注册(javap 实测 JacksonJsonHttpMessageConverterConfiguration) +@ConditionalOnClass(tools.jackson.databind.json.JsonMapper.class) +@ConditionalOnBean(tools.jackson.databind.json.JsonMapper.class) // ← 关键:要 Bean,不只是类 +@ConditionalOnProperty(name = "spring.http.converters.preferred-json-mapper", + havingValue = "jackson", matchIfMissing = true) + +// Jackson 2 分支——以下任一命中即生效 +@ConditionalOnProperty(name = "...preferred-json-mapper", havingValue = "jackson2") // 通路 A +@ConditionalOnMissingBean(JacksonJsonHttpMessageConvertersCustomizer.class) // 通路 B +``` + +- **通路 B(主)**:Jackson 3 分支要的是**容器里有 `JsonMapper` Bean**,而该 Bean 只由 `spring-boot-jackson` 自动配置模块产出;托管层已把该模块摘掉(见 §5.5⑥),Bean 不存在 → Jackson 3 分支不命中 → Jackson 2 分支的 `@ConditionalOnMissingBean` 成立。这是**编译期事实**,不依赖启动路径。 +- **通路 A(兜底)**:`LauncherService` 设 `preferred-json-mapper=jackson2`,覆盖下游未继承 blade 托管层、`spring-boot-jackson` 漏进 classpath 的场景。 + +> ⚠️ 别把「剥离 Jackson 3」理解成「classpath 上不能有 `tools.jackson`」——**决定管线的是 `JsonMapper` Bean 在不在,不是 Jackson 3 的类在不在**。Jackson 3 的库照常引入不会翻转管线,反而是必需的,理由见 §5.5⑧。 + +> 两条都留的原因:通路 A 仅在 `BladeApplication` 启动路径执行,`@SpringBootTest`、blade-gateway(不含 blade-core-tool)覆盖不到;且 Jackson 3 的 **XML 转换器分支**(`JacksonXmlHttpMessageConverterConfiguration`)只有 `@ConditionalOnClass` + `@ConditionalOnBean`,**不受 `preferred-json-mapper` 约束**,属性通路结构性覆盖不到。 +> +> 关于失效程度需说清:因 §5.5③ 的 `if (!jsonReplaced) converters.add(0, bladeJson)` 兜底,即使两条通路全漏、Jackson 3 转换器占位,blade 转换器仍会插到队首优先命中,**MVC 服务端不会失效**;真正会静默回落到 Jackson 3 的是 `RestClient`/`RestTemplate` 客户端转换器与 WebFlux 编解码器。 新增 `blade-core-tool/.../config/JacksonLauncherServiceImpl.java`: @@ -408,6 +429,92 @@ public class JacksonLauncherServiceImpl implements LauncherService { } ``` +**⑥ 【主通路】托管层剥离 Jackson 3 的自动配置模块(只剥模块,不剥库)**——在父 POM `dependencyManagement` 把 `spring-boot-jackson` 从传递链上摘掉,容器里便不会出现 `JsonMapper` Bean,§5.5⑤ 的通路 B 恒成立。**剥离对象仅限这个自动配置模块,`tools.jackson` 系的库本体照常引入**(理由见 §5.5⑧)。**需要两个条目,缺一不可**: + +```xml + + + org.springframework.boot + spring-boot-starter-jackson + ${spring.boot.version} + + + org.springframework.boot + spring-boot-jackson + + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 3.0.3 + + + org.springframework.boot + spring-boot-jackson + + + +``` + +> ⚠️ 为什么 springdoc 必须单独排:`springdoc-openapi-starter-common:3.0.3` **直接依赖** `org.springframework.boot:spring-boot-jackson`,而不是经 `spring-boot-starter-jackson` 传递,上面第一个条目的 `` 只约束它自己的子树,管不到 springdoc。可用 `mvn dependency:tree` 单独验证(注意:在完整工程里跑,Maven 的 nearest-wins 会把 springdoc 分支下的重复节点省略,须用只含 springdoc 的最小 POM 或加 `-Dverbose` 才看得见)。 +> +> springdoc 内部仅 Spring Data REST 支持类引用 Jackson 3,且被 `@ConditionalOnClass(RepositoryRestConfiguration)` 守卫,排除无副作用。 +> +> 子模块**不需要**再写一遍局部 ``:子模块继承父 `dependencyManagement`,下游工程经 `blade-core-bom` 展开同样继承,重复声明纯属冗余。 + +**⑦ 序列化管线门禁**——托管层排除是"当前依赖树"的快照,第三方库升级随时可能开出新的引入路径(springdoc 直连就是活例)。父 POM `` 挂一条 enforcer 规则,把漏排从**运行期静默回落**提前成**构建期失败**,并顺带校验托管层排除是否随 `blade-core-bom` 正确传导: + +```xml + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven.enforcer.version} + + + ban-jackson3-autoconfigure + enforce + + + + + org.springframework.boot:spring-boot-jackson + org.springframework.boot:spring-boot-jackson-test + + + + + + + +``` + +> **封禁对象只有自动配置模块**,`tools.jackson` 系构件不在范围内——门禁守的是"`JsonMapper` Bean 不许出现",不是"Jackson 3 的类不许出现";把库一起禁掉会连带打死一批按「Jackson 3 在场」编写的第三方组件(见 §5.5⑧)。`spring-boot-jackson` / `spring-boot-jackson-test` 均为精确坐标匹配,**不会**误伤 `spring-boot-jackson2`。 +> +> 该门禁 Boot 单体 / Cloud 微服务两个下游工程需各自挂一份——它们以 Boot BOM import 方式引用 blade-tool,并非以其为 parent,`` 不会继承过去。 + +**⑧ 【必读】Jackson 3 的库本体必须留着(Cloud 侧尤其)**——⑥ 只剥自动配置模块,而 `tools.jackson` 的库是随该模块传递进来的,剥掉模块库也就跟着没了。若不补回来,凡是**编译期直接 `import tools.jackson.*`** 的第三方组件都会踩雷:轻则条件不命中静默降级,重则 `NoClassDefFoundError: tools/jackson/databind/ObjectMapper` 打断启动。Boot 单体不受影响(这些组件只存在于 Cloud 技术栈),Cloud 侧实测清单: + +| 组件 | 触发点 | 缺席后果 | +|---|---|---| +| `spring-cloud-circuitbreaker-sentinel` | `CircuitBreakerRuleChangeListener#afterSingletonsInstantiated` → `SentinelFeignClientProperties#copy()` 内 `new tools.jackson.databind.ObjectMapper()` | **容器 refresh 末尾抛错,服务起不来**;所有单例都建完了才炸,日志看着像启动成功 | +| `spring-cloud-gateway-server-webflux` | `RemoveJsonAttributesResponseBodyGatewayFilterFactory` 的实例字段 `private ObjectMapper mapper = new ObjectMapper()`,该 Bean 由 `@ConditionalOnEnabledFilter` 默认开启 | **网关起不来** | +| `spring-cloud-openfeign-core` | `PageJacksonModule` / `SortJacksonModule`,装配条件校验的模块基类在 Jackson 3 改名后退化为恒成立 | **容器启动失败**;见 `FeignJacksonEnvPostProcessor` 的兜底 | +| `spring-boot-admin-client` | `RestClientRegistrationClientConfig#registrationClient` 形参 `ObjectProvider` | 配了 `spring.boot.admin.client.url` 才装配,装配即失败 | +| `spring-cloud-alibaba-sentinel-datasource` | `JsonConverter` / `XmlConverter` | 静默不注册,Sentinel 规则数据源转换能力消失 | +| `spring-cloud-starter-alibaba-sentinel` | `SentinelAutoConfiguration$SentinelConverterConfiguration` | 自带 `@ConditionalOnClass` 守卫,整块跳过,同上静默降级 | + +这些都不属于本工程可修的范围,逐个关开关是打地鼠——上表还只是**当前版本**的快照,三方库一升级又会冒出新的。所以口径是**留库**: + +- `blade-core-cloud` 引 `tools.jackson.core:jackson-databind`,覆盖全部微服务(经 `blade-core-boot` 传递)。 +- `blade-gateway` 不含 `blade-core-cloud`,需在自己的 POM 里单独引一份,否则会被上表第二行打挂。 +- 版本由 Boot BOM 托管(`spring-boot-dependencies` 已 import `tools.jackson:jackson-bom`),不必自设版本属性。 + +留库**不会**翻转管线:§5.5⑤ 已实测 Jackson 3 转换器分支要的是 `@ConditionalOnBean(JsonMapper)`,而该 Bean 只由被剥离的 `spring-boot-jackson` 产出。`FeignJacksonEnvPostProcessor` 也据此加了 `ClassUtils.isPresent("tools.jackson.databind.JacksonModule")` 守卫——Jackson 3 在场就不干预,交回上游条件判定,只在缺席时才给出关闭默认值。 + +> 排查手法(升级三方库后建议复跑):对依赖树里的 jar 逐个扫常量池,`tools/jackson` 命中即为硬引用嫌疑,再回源码看有没有条件守卫。用 Python 的 `zipfile` 扫,别用 `strings`——macOS 的 `strings` 会把 `.class` 的 `0xCAFEBABE` magic 误判成 Mach-O fat 二进制,扫出空结果。 + ### 5.6 Redis pub/sub 容器与 Boot 4.1 新增 `@RedisListener` 自动配置的竞争 **背景**:Boot 4.1 的 `spring-boot-data-redis` 新增了注解驱动监听自动配置 `DataRedisAnnotationDrivenConfiguration`(由 `DataRedisAutoConfiguration` `@Import`,条件 `@ConditionalOnClass({org.springframework.data.redis.annotation.EnableRedisListeners, org.springframework.messaging.Message})`),用于支撑新的 `@RedisListener` 注解。它会注册一个 `@Bean(name = "redisMessageListenerContainer")` + `@ConditionalOnMissingBean` 的默认容器。 diff --git a/pom.xml b/pom.xml index 8a4bf1f..ff4a29b 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ - 5.0.0 + 5.0.1 org.springblade.blade.tool 21 @@ -60,6 +60,7 @@ 1.2.28 2.0.13 4.5.14 + 3.6.3 4.1.0 4.1.1 @@ -127,6 +128,23 @@ ${mica.auto.version} provided + + + org.springframework.boot + spring-boot-starter-jackson + ${spring.boot.version} + + + org.springframework.boot + spring-boot-jackson + + + org.springblade @@ -380,15 +398,28 @@ swagger-annotations-jakarta 2.2.52 + org.springdoc springdoc-openapi-starter-webflux-ui 3.0.3 + + + org.springframework.boot + spring-boot-jackson + + org.springdoc springdoc-openapi-starter-webmvc-ui 3.0.3 + + + org.springframework.boot + spring-boot-jackson + + @@ -420,6 +451,12 @@ easy-captcha 1.6.2 + + + com.alibaba + fastjson + 2.0.64 + cn.idev.excel @@ -532,6 +569,42 @@ + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven.enforcer.version} + + + ban-jackson3-autoconfigure + + enforce + + + + + + org.springframework.boot:spring-boot-jackson + org.springframework.boot:spring-boot-jackson-test + + + 检测到 spring-boot-jackson 进入依赖树。该模块会注册 JsonMapper Bean, + 使 Boot 4 改用 Jackson 3 的消息转换器, + @BladeView 视图过滤、大数转字符串、日期格式等序列化约定将静默失效。 + 请执行 mvn dependency:tree -Dincludes='org.springframework.boot:spring-boot-jackson' + 定位引入路径,并在父 POM dependencyManagement 对应条目补充排除。 + 注意:tools.jackson 系构件本身不在封禁范围,可以正常引入。 + + + + + + + org.apache.maven.plugins maven-compiler-plugin