背景: 项目进行架构升级,将mybatis从3.1.0升级到3.4.5,mybatis-spring从1.1.0升级到1.3.1
问题:
2018-01-10 08:47:59.695 ERROR org.springframework.web.context.ContextLoader 350 initWebApplicationContext - Context initialization failed org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'bankSubscribeService': Unsatisfied dependency expressed through field 'requestappMapper'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'requestappMapper' defined in URL [jar:file:/D:/Idea_workspace/web_workspace/bbb-core-web/target/bbb-core/WEB-INF/lib/bbb-core-dao-1.2.0-SNAPSHOT.jar!/com/hhh/etrade/bbb/dao/trade/RequestappMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in URL [jar:file:/D:/Idea_workspace/web_workspace/bbb-core-web/target/bbb-core/WEB-INF/lib/bbb-core-dao-1.2.0-SNAPSHOT.jar!/spring/spring-bbb-core-dao.xml]: Invocation of init method failed; nested exception is org.apache.ibatis.type.TypeException: The alias 'City' is already mapped to the value 'com.htffund.etrade.inst.model.bank.City'.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:443) ~[spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:325) ~[spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:107) ~[spring-web-4.3.10.RELEASE.jar:4.3.10.RELEASE]
at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5118) ~[catalina.jar:7.0.73]
at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5634) ~[catalina.jar:7.0.73]
at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145) ~[catalina.jar:7.0.73]
at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:899) ~[catalina.jar:7.0.73]
at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875) ~[catalina.jar:7.0.73]
at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652) ~[catalina.jar:7.0.73]
at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1863) ~[catalina.jar:7.0.73]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_101]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_101]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_101]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_101]
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301) ~[tomcat-coyote.jar:7.0.73]
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819) ~[?:1.8.0_101]
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801) ~[?:1.8.0_101]
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:618) ~[catalina.jar:7.0.73]
at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:565) ~[catalina.jar:7.0.73]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_101]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_101]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_101]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_101]
at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301) ~[tomcat-coyote.jar:7.0.73]
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819) ~[?:1.8.0_101]
at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801) ~[?:1.8.0_101]
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1468) ~[?:1.8.0_101]
at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:76) ~[?:1.8.0_101]
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1309) ~[?:1.8.0_101]
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1401) ~[?:1.8.0_101]
at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:829) ~[?:1.8.0_101]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_101]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_101]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_101]
at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_101]
at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:324) ~[?:1.8.0_101]
at sun.rmi.transport.Transport$1.run(Transport.java:200) ~[?:1.8.0_101]
at sun.rmi.transport.Transport$1.run(Transport.java:197) ~[?:1.8.0_101]
at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_101]
at sun.rmi.transport.Transport.serviceCall(Transport.java:196) ~[?:1.8.0_101]
at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:568) ~[?:1.8.0_101]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:826) ~[?:1.8.0_101]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:683) ~[?:1.8.0_101]
at java.security.AccessController.doPrivileged(Native Method) [?:1.8.0_101]
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:682) [?:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_101]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_101]
at java.lang.Thread.run(Thread.java:745) [?:1.8.0_101]
思考:看异常信息应该是说这个别名已经存在了
(1). 为啥没升级的时候没问题呢?
(2). 虽然项目中存在多个同名的类,但是出于不同的包中啊
解决途径:
(1). Google了好久,看了mybatis-spring的官方文档也不行,好久之后才找到这篇 https://my.oschina.net/zimingforever/blog/478137 但是发现该文中的源码和mybatis3.1.0是一样的,按这个源码应该不会报错才对。
(2). 对比mybatis从3.1.0和3.4.5关于设置类别名的源码发现为什么会出现这个异常了
先说下mybatis配置别名的方法,共有两种:
方法1:指定一个具体的类的别名
在mapper.xml文件中配置
3.1.0版本的实现
public void registerAlias(String alias, Class value) {
assert alias != null;
// 把别名转为小写字母
String key = alias.toLowerCase();
// 如果key或者alias如果已经添加到了map中,且对应的class和已添加的不一致则抛出异常
if (TYPE_ALIASES.containsKey(key) && !TYPE_ALIASES.get(key).equals(value.getName()) && TYPE_ALIASES.get(alias) != null) {
if (!value.equals(TYPE_ALIASES.get(alias))) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(alias).getName() + "'.");
}
}
// 否则添加到map中
TYPE_ALIASES.put(key, value);
}
3.4.5版本的实现
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 (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
TYPE_ALIASES.put(key, value);
}
区别主要是黄色的这两处,3.1.0通过alias从map中获取,要知道map中的key都设置为全小写的了,而alias却是简单类名(不带包前缀),所以不管怎样 TYPE_ALIASES.get(alias) == null,3.1.0不会抛出异常。但是会对key对应的值进行覆盖。
方法2:指定一个包中的所有的类的别名,省去一个个设置
我遇到的问题出在这里,相同类名两个类的包前缀都是com.test.etrade.abc.model
public void registerAliases(String packageName){
// 第二个会在mybatis的io去寻找对应的类时会用到
// 因为是把这个包中所有的类都进行别名注册,因而用的的Object类
registerAliases(packageName, Object.class);
}
public void registerAliases(String packageName, Class superType){
ResolverUtil resolverUtil = new ResolverUtil();
// mybatis的io去包中寻找对应的类,因为是把这个包中所有的类都进行别名注册
// 因而此时会将所有的类都读到
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set> typeSet = resolverUtil.getClasses();
for(Class type : typeSet){
//Ignore inner classes and interfaces (including package-info.java)
// 忽略内部类和接口
if (!type.isAnonymousClass() && !type.isInterface()) {
registerAlias(type);
}
}
}
public void registerAlias(Class type) {
// 利用反射机制得到这个类的不包含包名的简单名称
String alias = type.getSimpleName();
// 如果这个类有Alias这个注解,获取这个注解的值作为别名,否则就用自己的原始类名
Alias aliasAnnotation = (Alias) type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
alias = aliasAnnotation.value();
}
// 开始注册,这个步骤使用的是第一种方法
registerAlias(alias, type);
}
(1). 不取同名的类,即使在不同的包中
(2). 采用第一种别名配置方法,对不同包中的同名类分别设置不同的别名
(3). 用注解@Alias在类上配置别名。
注: 当然别名都是不能相同的,即使是大小写不同也不行,因为别名最后都是转为全小写存到Map中的
TYPE_ALIASES.put(key, value)
。取类型别名主要是为了在mapper.xml中使用方便,不用每次都写那么长长的类全名,用简单的别名就可以了。