为了紧跟 Spring 6 的步伐,Spring Boot 在 2022 年 11 月 24 日释放了 3.0.0. 当前版本是 3.0.1(2022-12-23)。Spring 6 要求用 JDK 17+, Spring Boot 3 自然也要上 JDK 17+ 才能使用,对于一直死死抱住 JDK 8 不放的要升级到 Spring Boot 3 就是个比较大的挑战。
Spring Boot 到底带来了什么显著的特性呢?
- 依赖于 Spring 6, 最低 Java 17, 兼容 Java 19
- 支持生成 GraalVM 本地映像,取代实验性的 Spring Native 项目
- 最低 Java EE 9 和支持 Jakarta EE 10
- 依赖从 Java EE 迁移到 Jakarta EE API
- 升级到 Tomcat 10
从 Spring Boot 2.x 升级到 Spring Boot 3 的指南请阅官方的文档 Spring Boot 3.0 Migration Guide。Spring Boot 1 的项目还得老老实实的先升级到 Spring Boot 2,如果是早期的 Spring Boot 2,第一步是升级到 Spring Boot 2.7.x, 一步步来,免得步子大了扯到X。再到是把 JDK 换成 17 或更新的版本,编译,运行,有问题就改代码。
最主要的 API 改动就是 javax
到 jakarta.servlet
,比如
- javax.servlet -> jakarta.servlet
- javax.annotations -> jakarta.annotations
- javax.persistence -> jakarta.persistence
- javax.tansaction -> jakarta.transaction
本人有一个项目是 Spring Boot 2.7.6 + JDK 17,升级时直接到 Spring Boot 的依赖改成 3.0.1,然后试着 mvn compile
, 以上那些 API 因为包名更改,之前的引用如
- javax.annotation.PostConstruct
- javax.servlet.http.HttpServletRequest
等由于编译不过,直接替换 javax 为 jakarta,改成相应的 jakarta.annotations.PostConstruct, jakarta.servlet.http.HttpServletRequest 就行
最后是通过 mvn compile
编译,可以一运行 Spring 应用,就报某个 Bean 找不到
Parameter 2 of constructor in yanbin.blog.testweb.controllers.ManagementController required a bean of type 'yanbin.blog.testweb.service.CalcEngineFactory' that could not be found.
来到 CalcEngineFactory 代码,明明有注解 @Named
, 为什么就不再注册为一个 Spring Bean 了呢?如果把注解 @Named
换成 Spring 的 @Component
就没问题了。问题是解决了,找原因吧,断点,调试,来到
org.springframework.context.annotation.ClassPathBeanDefinitionScanner.doScan(String... basePackages) 方法
用 @Named
注解的类不被 findCandidateComponents(basePackage)
认为是 SpringBean,而用 @Component
注解的就是。继续跟随到方法 scanCandidateComponents(basePackage)
, 看到
1 2 |
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); if (isCandidateComponent(metadataReader)) { |
用 @Named
注解的类 isCandidateComponent(metadataReader)
返回 false, @Component
注解的类返回的是 true。
在 isCandidateComponent()
方法中用到了 excludeFilters
和 includedFilters
来判断 true 或 false。
关注这个 includedFilters
,在 Spring Boot 3 中它包含了三个 AnnotationTypeFilter,分别是
- annotationType: interface org.springframework.stereotype.Component
- annotationType: interface jakarta.annotation.ManagedBean
- annotationType: interface jakarta.inject.Named
确实是有 @Named
, 但它不是我们先前用的 javax.inject.Named
, 而是 jakarta.inject.Named
。由于 javax.inject.Named
来自于依赖 javax.inject:javax.inject
,升级了 Spring Boot 3 后并不妨碍编译,产生了一个运行期才能被发现的异常。
这就是为什么用 @Component
注解就能解决这个问题,当然用 @Component
的子接口 @Service
, @Controller
等也是能被注册为 Spring Bean 的。
如果依然执着于 @Named
,那就去掉 javax.inject:javax.inject
依赖,换成 @jakarta.inject.Named
注解。在升级到 Spring Boot 3 后,头脑中要有是否该 javax
换成 jakarta
的想法。
如果对比 Spring 5 和 6 ClassPathBeanDefinitionScanner 类的注释,也说明了从 @javax.inject.Named
到 @jakarta.inject.Named
的变成。
v5.3.24 ClassPathBeanDefinitionScanner
* <p>Also supports Java EE 6's {@link javax.annotation.ManagedBean} and
* JSR-330's {@link javax.inject.Named} annotations, if available.
v6.0.4 ClassPathBeanDefinitionScanner
* <p>Also supports Jakarta EE's {@link jakarta.annotation.ManagedBean} and
* JSR-330's {@link jakarta.inject.Named} annotations, if available.
假如仍然希望 Spring 把 @javax.inject.Named
注解的类当作 Spring Bean 的话,那就要看是否能影响 org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider 中 includeFilters: List<TypeFilter> 的变量值。我们读到 ComponentScanAnnotationParser 类中相关的代码
1 2 3 4 5 6 7 8 9 |
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, String declaringClass) { ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry, componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader); ...................... else { Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver"); scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass)); } |
那就是用 @ComponentScan
注解的
1 2 |
boolean useDefaultFilters() default true; Filter[] includeFilters() default {}; |
这两个属性,于是我们在 Spring Boot 的 Java 配置或启动类中加上
1 |
@ComponentScan(includeFilters = @ComponentScan.Filter(classes = javax.inject.Named.class)) |
这样的话就会在默认的 includeFilters 中再加上
- annotationType: interface javax.inject.Named
如此用 @javax.inject.Named
注解的类也会被当作 Spring Bean 来对待。不过,这种方式本人不推荐使用,尽量用符合 Spring Boot 3(Spring 6) 规范的方式,改成 @jakarta.inject.Named
。
最后,除 javax.inject:javax.inject 中的 @Named
注解外,其他的注解类也可能工作不正常需请注意。
https://www.springcloud.io/post/2023-02/springboot-3-javax-inject-named-unvailable
怎么,被抄袭了!算是被机器翻译引用的?
可以把 javax 加到 blacklist 里吗?
那不行,还有很多要有用的 javax, 如 javax.sql, javax.xml, 再说怎么加到 blacklist,静态检测?