Closed Createsequence closed 8 months ago
根据排查,似乎是在通过 WebMvcConfigurationSupport#resourceHandlerMapping
创建 HandlerMapping
时,由于当前实例仍然没有 ServletContext
所以报错:
其中,实例中的 ServletContext
来自于 ServletContextAware
回调接口的 setServletContext
方法,此时通过调试发现,用于在后处理阶段调用 ServletContextAware
回调接口的后处理器 ServletContextAwareProcessor
在容器中并不存在:
由于未经过后处理器的处理,因此该 WebMvcConfigurationSupport
实例并没有获得 ServletContext
,然后创建 HandlerMapping
由于 ServletContext
为空所以直接报错了。
经过试验,不再直接通过 @EnableCrane4j
引入配置,而是直接引入 Crane4jAutoConfiguration
配置类即可:
/**
* 在项目里面另外建一个配置类继承 Crane4jAutoConfiguration
*
* @author huangchengxing
*/
@Configuration
public class Crane4jConfig extends Crane4jAutoConfiguration {
}
不过具体原因还需要进一步排查,怀疑是 crane4j-spring-boot-starter
中引入的 springboot 依赖中自带了部分自动配置类,导致依赖或配置冲突。
经过验证,也可以在自己的项目中的 META-INF
文件夹下通过 SPI 文件引入 crane4j 配置类,这种方式也不会报错:
在 springboot 2.7 及以上版本,你需要在 spirng
文件夹下提供一个 org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件,里面内容如下:
cn.crane4j.spring.boot.config.Crane4jAutoConfiguration
cn.crane4j.spring.boot.config.Crane4jJacksonConfiguration
cn.crane4j.spring.boot.config.Crane4jMybatisPlusAutoConfiguration
在 springboot 2.7 以下版本,你需要提供一个 spring.factories
文件,里面内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.crane4j.spring.boot.config.Crane4jAutoConfiguration,\
cn.crane4j.spring.boot.config.Crane4jJacksonConfiguration,\
cn.crane4j.spring.boot.config.Crane4jMybatisPlusAutoConfiguration
2.4.0 版本这个问题仍然还未解决,经过研究,发现了问题确实是因为配置的加载顺序导致的,不过并不是之前说的 ServletContextAwareProcessor
不存在(实际上生效的是它的子类 WebApplicationContextServletContextAwareProcessor
),而是 ServletContextAwareProcessor
生效时并没有获取到 ServletContext
。
首先,Crane4jAutoConfiguration
这个配置类里面有一个 BeanMethodContainerPostProcessor
,它是一个 BeanPostProcessor
类型的 Bean,用来将方法适配为方法容器,在容器启动时,它会在 registerBeanPostProcessors
这一步提前将其初始化。
然而, BeanMethodContainerPostProcessor
依赖了非常多的上级组件,这导致它们会跟着该后处理器一并发生初始化,这个依赖链最后导致提前触发了 WebMvcAutoConfiguration
的初始化:
EnableWebMvcConfiguration
实现了 ServletContextAware
接口,当它加载时,ServletContextAwareProcessor
这个后处理器依然获取不到 ServletContext
,因此 EnableWebMvcConfiguration
也不能通过回调接口得到 ServletContextAware
。
再往后,当容器加载所有单例 Bean 时,将会根据 EnableWebMvcConfiguration.resourceHandlerMapping
工厂方法创建 HandlerMapping
,此时它将会尝试获取 EnableWebMvcConfiguration
中通过 ServletContextAware
回调注入的 ServletContext
,然而由于该配置类并没有获得 ServletContext
,因此就会报错 “No ServletContext set”。
SpringBoot 在 Web 环境的启动这一块我不是很熟,但是根据目前的观察,将 Crane4j 的配置类延迟加载可以解决这个问题,比如将注解加载项目的配置类上,或者将令本地的配置类继承 Crane4j 的配置类。
不过,这个问题也反映了通过 BeanPostProcessor
实现将方法适配为容器的功能并不合理,它会导致在自动装配时,仅仅为了加载这么一个 Bean 就让非常多的关联组件被提前加载。在下一个 2.5.0 版本,将会重构它,参照 Spring 的 EventListenerProcessor
,改为基于 SmartInitializingSingleton
实现。
springboot 版本为
2.2.13.RELEASE
,在启动类添加@EnableCrane4j
注解后,启动应用报错: