问题描述
项目框架springboot+mybatisplus+mybatis,需求中使用webservice,经过各种研究、对比、测试最后决定使用wsimport生成代码。wsimport
是jdk自带的webservice客户端工具,可以根据wsdl文档生成客户端调用代码(java代码)位于JAVA_HOME/bin
目录下。
wsimport [options] <WSDL_URI>
生成的文件如下:
每个webservice生成的代码都有一些同名的类,如DtZrsv、ObjectFactory,当Springboot启动时悲剧发生了,一长串的异常,整个世界都不好了。
Error creating bean with name 'sqlSessionFactory' defined in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.apache.ibatis.session.SqlSessionFactory]: Factory method 'sqlSessionFactory' threw exception; nested exception is org.apache.ibatis.type.TypeException: The alias 'ObjectFactory' is already mapped to the value 'com.example.webservice.ObjectFactory'.
解决路程
看到这个错误,下意识的认为是命名重复,bean工厂实例化的问题,接下来对着文件名和类名一通操作。。。
经过一通折腾还是没有解决问题,于是决定跟踪源码看看到底是啥原因。首先根据错误信息找到MybatisPlusAutoConfiguration类,并在sqlSessionFactory方法设下断点
经过第一轮调试初步确定错误实在方法最后一行return factory.getObject()出错,
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
.......
.......
return factory.getObject();
}
facotry.getObject方法
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet(); // 关键代码
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
SqlRunner.DEFAULT.close();
this.sqlSessionFactory = buildSqlSessionFactory(); // 关键代码
}
接下来很容易就找到buildSqlSessionFactory,看名称就知道这个地方应该是创建sqlSessionFactory的方法,继续跟踪查找元凶,在单步调试的帮助下找到错误行
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
......
......
if (hasLength(this.typeAliasesPackage)) {
scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
.filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
.filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); //关键代码
}
public void registerAlias(Class<?> type) {
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
registerAlias(alias, type); //关键代码
}
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
发现在 targetConfiguration.getTypeAliasRegistry()::registerAlias 产生错误,追踪下去直到registerAlias(String alias, Class<?> value)方法,发现了报错的地方,从代码中可以看出mybatis加载类型别名时,会将别名存储在个Map<String,Class<?>>中,而map内部插入一个key相同的键值对是一定不行的,因此当存在相同类名时会出错。
解决办法
在代码中可以看出配置了typeAliasesPackage才会进入这个方法,因此目前使用最简单粗暴将其配置去除。