Spring 6.0.9升级至6.1.3触发Bean创建异常求助
为修复CVE-2024-22233漏洞,将Spring版本从6.0.9升级至6.1.3,修改build.gradle的版本号如下:
diff --git a/build.gradle b/build.gradle index 08e30392f5..2aa311df7e 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ ext { // Dependency versions codenarcVersion = '3.2.0' - springVersion = '6.0.9' + springVersion = '6.1.3' groovyAllVersion = '3.0.17' bouncyCastleVersion = '1.70' log4jVersion = '2.20.0'
构建测试应用时,加载名为cabinetFinder(类型com.simplicity.domain.meta.Cabinet)的Bean失败,报错如下:
Unsatisfied dependency expressed through constructor parameter 0: Could not convert argument value of type [com.simplicity.domain.meta.Characteristics] to required type [java.util.Map]: Failed to convert value of type 'com.simplicity.domain.meta.Characteristics' to required type 'java.util.Map'; Cannot convert value of type 'com.simplicity.domain.meta.Characteristics' to required type 'java.util.Map': no matching editors or conversion strategy found
XML Bean定义中按名称指定构造参数,但Spring 6.1.3似乎改为按参数索引映射,导致Characteristics对象被传入需要Map类型的第一个构造参数,该问题在Spring 6.0.9中不存在。
需明确以下问题:
- 是否遗漏必要配置?
- 除Spring版本外还需修改
build.gradle的哪些内容? - 这属于Spring的问题吗?
Java代码
// SimplicityCachingManager.java @ContextConfiguration(locations = { "classpath*:/META-INF/**/*SpringConfiguration.groovy", "classpath*:/META-INF/**/applicationContext-*.xml" }) @RunWith(SpringJUnit4ClassRunner.class) public abstract class AbstractSpringCachingTest { } public class SimplicityCachingManager extends AbstractSpringCachingTest { @Resource protected SimplicityFactory simplicityFactory; } // SimplicityFactoryIntegUtils.java public class SimplicityFactoryIntegUtils { @Resource private SimplicityFactory simplicityFactory; } // SimplicityFactory.java public class SimplicityFactory { @Autowired private ApplicationContext applicationContext; public void initInstance() { registerCabinets(this.applicationContext.getBeansOfType(Cabinet.class)); } } // Cabinet.java import org.springframework.context.ApplicationContext; public class Cabinet { @Autowired private ApplicationContext applicationContext; public Cabinet(final Map<String, Map<CabinetEnum, String>> oldCabinets, final int cabinetRevisionNo, final int categoryId, final DescriptorModel descriptorModel, @Nullable final String facadeType, @Nullable final String label, final SecurityLevel securityLevel, final DeliveryInfo deliveryInfo, final Map<String, SimpleContainer> simpleContainers, final Map<String, ComplexContainer> complexContainers, final Map<String, Map<CabinetEnum, String>> manufacturer, final int maxDoors, final Characteristics characteristics, final Set<String> cabinetsToBeExcluded, final Set<String> manufacturerToBeExcluded, final Set<String> knownModelNames, final boolean defaultCabinet, final boolean alternateModel) { } } // MetaCabinetSpringConfiguration.java @Configuration public class MetaCabinetSpringConfiguration { @Bean(name = "cabinetCharacteristics") public Characteristics cabinetCharacteristics() { return createCharacteristics(false, Functionality.SINGLE); } public Characteristics createCharacteristics(final boolean color, final Functionality functionality) { return new Characteristics(Generation.NA, color, functionality, MaximumSize.NA); } } // Characteristics.java public class Characteristics { public Characteristics(final Generation generation, final boolean color, final Functionality functionality, final MaximumSize maximumSize) { } }
XML配置
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd"> <!-- applicationContext-definition-1.xml --> <bean id="simplicityFactoryIntegUtils" class="com.simplicity.domain.meta.SimplicityFactoryIntegUtils" /> <!-- applicationContext-definition-2.xml --> <bean id="simplicityFactory" class="com.simplicity.domain.meta.SimplicityFactory" init-method="initInstance"/> <!-- applicationContext-metaCabinetFinder.xml --> <bean id='cabinetFinder' parent='moamp'> <constructor-arg name='simpleContainers'><map merge='true'/></constructor-arg> <constructor-arg name='descriptorModel' ref='descriptorModel-generic' /> <constructor-arg name="knownModelNames"> <set> <value>dummyModelName</value> </set> </constructor-arg> <constructor-arg name='defaultCabinet' value='true' /> </bean> <!-- applicationContext-moamp.xml --> <bean id='moamp' abstract='true' class="com.simplicity.domain.meta.Cabinet"> <constructor-arg name='characteristics' ref='cabinetCharacteristics'/> <constructor-arg name='manufacturer'><map merge='true'/></constructor-arg> <constructor-arg name='oldCabinets'><map merge="true"/></constructor-arg> <constructor-arg name="complexContainers"><map merge="true" /></constructor-arg> <constructor-arg name='deliveryInfo' ref='infodev'/> <constructor-arg name='maxDoors' value='0'/> <constructor-arg name='categoryId' value='-1' /> <constructor-arg name='cabinetRevisionNo' value='-1' /> <constructor-arg name='facadeType'><null /></constructor-arg> <constructor-arg name='label'><null /></constructor-arg> <constructor-arg name='securityLevel' value='NONE'/> <constructor-arg name='simpleContainers'><map merge='true'/></constructor-arg> <constructor-arg name="cabinetsToBeExcluded"><set /></constructor-arg> <constructor-arg name="manufacturerToBeExcluded"><set /></constructor-arg> <constructor-arg name="knownModelNames"><set /></constructor-arg> <constructor-arg name="alternateModel" value="false" /> </bean> <bean id='infodev' class='com.simplicity.domain.delivery.DeliveryInfo'> <constructor-arg name='supportedMethods'><map /></constructor-arg> <constructor-arg name='devId'><null /></constructor-arg> </bean> </beans>
问题根源
Spring 6.1.x对XML构造参数名称匹配逻辑做了调整,核心原因:
Cabinet类构造参数的名称在编译时未被保留(比如未开启-parameters编译选项,或字节码被混淆),导致Spring无法通过名称匹配, fallback 到按索引匹配构造参数。- 抽象Bean
moamp中的构造参数配置顺序,与Cabinet构造函数的参数顺序完全不一致,按索引匹配时就会出现类型不匹配——比如moamp第一个配置的是characteristics,但Cabinet构造函数第一个参数是oldCabinets(Map类型),最终触发类型转换错误。
解决方案
方案1:开启编译参数保留参数名(推荐)
在build.gradle中添加编译配置,让Java编译器保留构造函数参数名,Spring就能正确按名称匹配参数:
tasks.withType(JavaCompile) { options.compilerArgs.add('-parameters') }
此方案无需修改现有XML配置,兼容性最好。
方案2:调整XML构造参数顺序匹配构造函数
修改抽象Beanmoamp的<constructor-arg>顺序,严格对应Cabinet构造函数的参数顺序:
<bean id='moamp' abstract='true' class="com.simplicity.domain.meta.Cabinet"> <!-- 严格对应Cabinet构造函数参数顺序 --> <constructor-arg name='oldCabinets'><map merge="true"/></constructor-arg> <constructor-arg name='cabinetRevisionNo' value='-1' /> <constructor-arg name='categoryId' value='-1' /> <constructor-arg name='descriptorModel' ref='descriptorModel-generic'/> <constructor-arg name='facadeType'><null /></constructor-arg> <constructor-arg name='label'><null /></constructor-arg> <constructor-arg name='securityLevel' value='NONE'/> <constructor-arg name='deliveryInfo' ref='infodev'/> <constructor-arg name='simpleContainers'><map merge='true'/></constructor-arg> <constructor-arg name='complexContainers'><map merge="true" /></constructor-arg> <constructor-arg name='manufacturer'><map merge='true'/></constructor-arg> <constructor-arg name='maxDoors' value='0'/> <constructor-arg name='characteristics' ref='cabinetCharacteristics'/> <constructor-arg name="cabinetsToBeExcluded"><set /></constructor-arg> <constructor-arg name="manufacturerToBeExcluded"><set /></constructor-arg> <constructor-arg name="knownModelNames"><set /></constructor-arg> <constructor-arg name='defaultCabinet' value='false'/> <constructor-arg name="alternateModel" value="false" /> </bean>
方案3:显式指定构造参数索引
在XML的<constructor-arg>中添加index属性,明确参数对应的构造函数索引(索引从0开始):
<constructor-arg index='12' name='characteristics' ref='cabinetCharacteristics'/>
此方式维护成本高,参数顺序变化时需同步修改索引,不推荐。
额外检查项
- 确保所有Spring核心依赖(spring-core、spring-context等)都使用6.1.3版本,避免混合版本导致的兼容问题。
- 检查Groovy版本(当前3.0.17)是否兼容Spring 6.1.x,可根据Spring官方兼容性文档调整版本。
内容的提问来源于stack exchange,提问作者AustinDave

