最近在将项目升级到Spring Boot 3.2版本时,不少开发者遇到了一个奇怪的报错:"Invalid value type for attribute 'factoryBeanObjectType': java.lang.String"。这个错误通常发生在整合MyBatis或MyBatis-Plus时,导致应用无法正常启动。作为一个经历过这个坑的老司机,我来给大家详细剖析下这个问题的来龙去脉。
首先看下典型的错误堆栈:
code复制java.lang.IllegalArgumentException: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getTypeForFactoryBeanFromAttributes(FactoryBeanRegistrySupport.java:88)
at org.springframework.beans.factory.support.AbstractBeanFactory.getTypeForFactoryBean(AbstractBeanFactory.java:1802)
...
这个问题的根源在于Spring Boot 3.2对FactoryBean的类型检查机制做了更严格的限制。在之前的版本中,Spring对factoryBeanObjectType属性的类型检查相对宽松,而3.2版本开始要求这个属性必须是ResolvableType或Class类型。但MyBatis的部分版本(特别是mybatis-spring 2.x)在这个属性的处理上仍然使用了String类型,这就导致了类型不匹配的异常。
Spring Boot 3.2在FactoryBean的类型处理上做了重要调整。具体来说,FactoryBeanRegistrySupport类的getTypeForFactoryBeanFromAttributes方法现在会严格检查factoryBeanObjectType的类型:
java复制protected Class<?> getTypeForFactoryBeanFromAttributes(BeanDefinition beanDefinition) {
Object objectType = beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE);
if (objectType instanceof Class) {
return (Class<?>) objectType;
}
if (objectType instanceof ResolvableType) {
return ((ResolvableType) objectType).resolve();
}
if (objectType != null) {
throw new IllegalArgumentException("Invalid value type for attribute '" +
FactoryBean.OBJECT_TYPE_ATTRIBUTE + "': " + objectType.getClass().getName());
}
return null;
}
可以看到,现在只接受Class或ResolvableType类型,其他类型都会抛出IllegalArgumentException。这个变更的目的是为了提高类型安全性,但确实给一些老版本的MyBatis集成带来了兼容性问题。
在mybatis-spring 2.1.1版本中,ClassPathMapperScanner处理Mapper接口时是这样设置factoryBeanObjectType的:
java复制public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
protected void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 这里直接将String类型的beanClassName设置为了factoryBeanObjectType
definition.setAttribute("factoryBeanObjectType", definition.getBeanClassName());
}
}
}
这种实现方式在Spring Boot 3.2之前是可行的,因为老版本没有这么严格的类型检查。但升级到3.2后,这种String类型的值就不再被接受了。
MyBatis-Plus团队已经针对这个问题发布了修复版本。最佳解决方案是升级到3.5.5或更高版本:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
注意这里的关键变化:
如果由于某些原因暂时无法升级到最新版,可以采用手动排除的方式:
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.4.1</version>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
这个方案通过显式指定mybatis-spring的版本为3.0.3来解决问题。不过这只是权宜之计,建议还是尽快升级到官方推荐的版本。
在实际升级过程中,可能会遇到各种依赖冲突问题。这里分享几个实用的排查命令:
bash复制mvn dependency:tree -Dincludes=org.mybatis:mybatis-spring
bash复制mvn help:effective-pom
为了帮助大家更好地规划升级路径,这里整理一个关键组件的版本对应关系:
| Spring Boot版本 | MyBatis-Plus Starter | MyBatis-Spring版本 |
|---|---|---|
| 3.2.x | mybatis-plus-spring-boot3-starter 3.5.5+ | 3.0.3+ |
| 3.0.x - 3.1.x | mybatis-plus-boot-starter 3.5.0+ | 2.1.0+ |
| 2.7.x | mybatis-plus-boot-starter 3.5.0+ | 2.0.0+ |
如果按照上述方案升级后仍然遇到问题,可以检查以下几点:
我在实际项目中遇到过这样的情况:明明已经升级了依赖版本,但问题依旧。最后发现是某个间接依赖又拉取了旧版本。这种情况可以通过dependency:tree命令仔细排查。
要真正理解这个问题,我们需要先了解FactoryBean在Spring中的作用。FactoryBean是一种特殊的Bean,它本身是一个工厂,负责创建其他Bean实例。典型的应用场景包括:
Spring容器在初始化FactoryBean时,会先创建FactoryBean实例本身,然后通过调用getObject()方法来获取实际要管理的Bean。
MyBatis的Mapper接口正是通过MapperFactoryBean来实现的。对于每个Mapper接口,MyBatis都会创建一个MapperFactoryBean实例,这个FactoryBean负责在运行时生成Mapper接口的代理实现。
在Spring Boot 3.2之前,这个机制工作得很好,因为类型检查不严格。但3.2版本加强了类型安全,要求明确知道FactoryBean将要创建的对象类型,这就暴露了MyBatis原有实现中的类型问题。
根据我在多个项目中的升级经验,建议采用以下步骤:
升级完成后,建议重点测试以下场景:
我曾经在一个项目中忽略了性能测试,结果上线后发现某些查询变慢了。后来发现是新版本中某些默认配置发生了变化。
升级到生产环境时,建议:
记住,升级过程中最危险的往往不是你知道的问题,而是那些你没想到的问题。做好预案才能万无一失。