为了紧跟 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,静态检测?