Spring Boot父子上下文冲突:后部署应用自动配置Bean类型不匹配问题咨询
问题根因
你遇到的异常本质是类加载器隔离冲突:
Tomcat默认对每个部署的war包使用独立的WebAppClassLoader加载自身依赖,两个应用都打包了Spring Boot自动配置相关依赖,导致
BeanTypeRegistry类分别被两个不同的类加载器实例化。父上下文里的Bean是第一个应用类加载器加载的类型,第二个应用要注入的时候用自身类加载器加载的同全类名去匹配,JVM会判定为不同类型,因此抛出看起来矛盾的类型不匹配异常。
可行解决方案
方案1:排除子上下文(新模块)的重复自动配置
在子模块的启动类上修改@SpringBootApplication注解,排除父上下文已经存在的自动配置类,或者直接关闭自动配置手动导入所需配置:
// 关闭重复的自动配置,按需手动导入业务所需的配置类 @SpringBootApplication(exclude = { org.springframework.boot.autoconfigure.condition.BeanTypeRegistryAutoConfiguration.class, // 其他父上下文已经存在的自动配置类都可以在这里补充排除 })
如果子模块不需要走Spring Boot默认自动配置,也可以直接用@Configuration+@ComponentScan替代@SpringBootApplication,完全关闭自动配置逻辑,仅扫描自定义业务Bean。
方案2:调整子上下文Bean注册规则
给子上下文添加配置,允许覆盖同名Bean,同时禁止子上下文从父上下文拉取自动配置相关Bean:
@Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { ConfigurableApplicationContext parentContext = ((SpringCompMgr) ComponentManager.getInstance()).getApplicationContext(); Map<String, Object> props = new HashMap<>(); // 允许子上下文覆盖父上下文同名Bean props.put("spring.main.allow-bean-definition-overriding", true); // 子上下文自动配置逻辑忽略父上下文已有Bean props.put("spring.boot.autoconfigure.ignore-parent-context", true); return application .parent(parentContext) .sources(MyApplication.class) .properties(props) .bannerMode(Banner.Mode.LOG); }
如果你的Spring Boot 1.5.x版本没有spring.boot.autoconfigure.ignore-parent-context配置,可以手动注册一个BeanFactoryPostProcessor,在子上下文扫描Bean时直接过滤掉所有org.springframework.boot.autoconfigure包下的重复Bean定义
方案3:统一公共依赖的类加载器
把两个应用共用的Spring Boot、Spring Framework等依赖包从war包中剔除,放到Tomcat公共类加载路径下,让所有应用共享同一个类实例,从根源解决类加载器冲突:
- 在新模块的pom.xml中,把所有和现有应用重合的Spring相关依赖的
scope设置为provided,打包时不会打进war包 - 将对应版本的Spring Boot、Spring等公共jar包放到Tomcat安装目录的
lib文件夹下 - 重启Tomcat重新部署两个应用即可
方案4:临时移除父上下文重复Bean(仅应急使用)
如果无法调整依赖和配置,可以在设置父上下文之前,手动移除父上下文里的自动配置相关Bean,让子上下文自己注册对应实例:
ConfigurableApplicationContext parentContext = ((SpringCompMgr) ComponentManager.getInstance()).getApplicationContext(); DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) parentContext.getBeanFactory(); if (beanFactory.containsBeanDefinition("org.springframework.boot.autoconfigure.condition.BeanTypeRegistry")) { beanFactory.removeBeanDefinition("org.springframework.boot.autoconfigure.condition.BeanTypeRegistry"); }
该方案会影响先部署应用的自动配置逻辑,仅在先部署应用已完全初始化、不会再用到这些Bean的场景下使用
内容的提问来源于stack exchange,提问作者Johannes

