升级到 Spring Boot 3 后 javax.inject.Named 不可用

为了紧跟 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 到底带来了什么显著的特性呢?

  1. 依赖于 Spring 6, 最低 Java 17, 兼容 Java 19
  2. 支持生成 GraalVM 本地映像,取代实验性的 Spring Native 项目
  3. 最低 Java EE 9 和支持 Jakarta EE 10
  4. 依赖从 Java EE 迁移到 Jakarta EE API
  5. 升级到 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 改动就是 javaxjakarta.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), 看到

@Named 注解的类 isCandidateComponent(metadataReader) 返回 false, @Component 注解的类返回的是 true。

isCandidateComponent() 方法中用到了 excludeFiltersincludedFilters 来判断 true 或 false。

关注这个 includedFilters,在 Spring Boot 3 中它包含了三个 AnnotationTypeFilter,分别是

  1. annotationType: interface org.springframework.stereotype.Component
  2. annotationType: interface jakarta.annotation.ManagedBean
  3. 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 类中相关的代码

那就是用 @ComponentScan  注解的

这两个属性,于是我们在 Spring Boot 的 Java 配置或启动类中加上

这样的话就会在默认的 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://yanbin.blog/upgrade-to-spring-boot-3-javax-inject-named-unvailable/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

guest

2 Comments
Inline Feedbacks
View all comments
bbbush
bbbush
25 days ago

可以把 javax 加到 blacklist 里吗?