SpringBootLean 是对springboot学习与研究项目,是根据实际项目的形式对进行配置与处理,欢迎star与fork。
[oschina 地址]
http://git.oschina.net/cmlbeliever/SpringBootLearning
[github 地址]
https://github.com/cmlbeliever/SpringBootLearning
最近在项目中集成以全注解的方式Mybatis,配置了自动bean包与mapper所在包
db.mybatis.mapperLocations=classpath*:com/cml/springboot/sample/db/resource/*
db.mybatis.typeAliasesPackage=com.cml.springboot.sample.bean
db.mybatis.typeHandlerPackage=com.cml.springboot.framework.mybatis.typehandler
这些配置直接在ide上运行都是ok的,但是经过打包成jar包后,就一直报错提示找打不到对应的bean或者对应的mapper。主要错误如下:
Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'logbean'. Cause: java.lang.ClassNotFoundException: Cannot find class: logbean
***************************
APPLICATION FAILED TO START
***************************
Description:
Field logMapper in com.cml.springboot.sample.service.impl.LogServiceImpl required a bean of type 'com.cml.springboot.sample.db.LogMapper' that could not be found.
Action:
Consider defining a bean of type 'com.cml.springboot.sample.db.LogMapper' in your configuration.
针对以上问题,在新开的分支deploy_jar_bugfind上进行研究,找到了一个临时解决办法,就是配置mybat-configuration.xml将对应的bean都配置到xml上。
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
settings>
<typeAliases>
<typeAlias type="com.cml.springboot.sample.bean.LogBean" alias="logbean"/>
<typeAlias type="com.cml.springboot.sample.bean.User" alias="user"/>
typeAliases>
<typeHandlers>
<typeHandler javaType="org.joda.time.DateTime"
handler="com.cml.springboot.framework.mybatis.typehandler.JodaDateTimeTypeHandler" />
<typeHandler javaType="org.joda.time.LocalTime"
handler="com.cml.springboot.framework.mybatis.typehandler.JodaLocalTimeTypeHandler" />
typeHandlers>
configuration>
但是这个仅仅适合小项目并且bean少的情况,假如在打项目上,可能有几十上百的bean,都要一一配置岂不是累死人了,而且容易出bug。
于是继续研究,首先自定义DefaultSqlSessionFactoryBean取代默认的SqlSessionFactoryBean,并且在buildSqlSessionFactory()方法上对bean扫描的配置进行一步步log拆分,主要代码如下:
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
logger.debug("##################################################################################");
List children = VFS.getInstance().list(packageToScan.replace(".", "/"));
logger.debug("findclass:" + children);
logger.debug("##################################################################################");
logger.debug("DefaultSqlSessionFactoryBean.buildSqlSessionFactory.registerAliases:" + packageToScan);
ResolverUtil> resolverUtil = new ResolverUtil>();
resolverUtil.find(new ResolverUtil.IsA(Object.class), packageToScan);
Set>> typeSet = resolverUtil.getClasses();
logger.debug(
"DefaultSqlSessionFactoryBean.buildSqlSessionFactory.typeAliasesPackage.scanClass:" + typeSet);
for (Class> type : typeSet) {
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
String alias = type.getSimpleName();
String key = alias.toLowerCase(Locale.ENGLISH);
logger.debug(
"DefaultSqlSessionFactoryBean.buildSqlSessionFactory.typeAliasesPackage.scan:" + key);
}
}
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
发现在ide上运行的时候是可以扫描到指定的bean,但是在jar上就扫描不到了。主要问题找到了,就是对代码的扫描问题,原本想自定义configuration的,但是发现好多类都是依赖configuration这个类,于是便放弃这个想法,转而研究扫描不到bean的问题。
继续研究源码,得知bean的扫描是通过ResolverUtil这个类进行的,并且ResolverUtil扫描是通过VFS进行扫描的,主要代码:
public ResolverUtil find(Test test, String packageName) {
String path = getPackagePath(packageName);
try {
List<String> children = VFS.getInstance().list(path);
for (String child : children) {
if (child.endsWith(".class"))
addIfMatching(test, child);
}
} catch (IOException ioe) {
log.error("Could not read package: " + packageName, ioe);
}
return this;
}
结合log上的信息,工程上默认使用的是Mybatis的DefaultVFS进行扫描。查看这个类代码后也没发现什么问题,转而求救于百度与谷歌,经过多方搜索,找到了Mybatis官网issue (地址:https://github.com/mybatis/mybatis-3/issues/325)这里有对这些问题进行说明。根据issue的描述与各大神的回答,定位到了问题是DefaultVFS在获取jar上的class问题。
在mybat/spring-boot-starter工程上找到了SpringBootVFS,这个类重写了class扫描功能,通过spring进行扫描。
于是将SpringBootVFS拷贝到工程上,并且添加到VFS实现上去,代码如下:
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource datasource, MybatisConfigurationProperties properties)
throws Exception {
log.info("*************************sqlSessionFactory:begin***********************" + properties);
VFS.addImplClass(SpringBootVFS.class);
DefaultSqlSessionFactoryBean sessionFactory = new DefaultSqlSessionFactoryBean();
sessionFactory.setDataSource(datasource);
sessionFactory.setTypeAliasesPackage(properties.typeAliasesPackage);
sessionFactory.setTypeHandlersPackage(properties.typeHandlerPackage);
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactory.setMapperLocations(resolver.getResources(properties.mapperLocations));
// sessionFactory
// .setConfigLocation(new PathMatchingResourcePatternResolver().getResource(properties.configLocation));
SqlSessionFactory resultSessionFactory = sessionFactory.getObject();
log.info("===typealias==>" + resultSessionFactory.getConfiguration().getTypeAliasRegistry().getTypeAliases());
log.info("*************************sqlSessionFactory:successs:" + resultSessionFactory
+ "***********************" + properties);
return resultSessionFactory;
}
这样mybaits打包jar后扫描问题完美解决。
综上,解决SpringBoot Mybatis打包jar后问题处理完毕,已提交到git项目master分支和deploy_jar_bugfind
http://git.oschina.net/cmlbeliever/SpringBootLearning
解决办法: