SpringBootLean 是對springboot學(xué)習(xí)與研究項(xiàng)目竹捉,是根據(jù)實(shí)際項(xiàng)目的形式對進(jìn)行配置與處理,歡迎star與fork。
[oschina 地址]
http://git.oschina.net/cmlbeliever/SpringBootLearning
[github 地址]
https://github.com/cmlbeliever/SpringBootLearning
最近在項(xiàng)目中集成以全注解的方式Mybatis簸喂,配置了自動(dòng)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上運(yùn)行都是ok的毙死,但是經(jīng)過打包成jar包后,就一直報(bào)錯(cuò)提示找打不到對應(yīng)的bean或者對應(yīng)的mapper喻鳄。主要錯(cuò)誤如下:
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上進(jìn)行研究,找到了一個(gè)臨時(shí)解決辦法除呵,就是配置mybat-configuration.xml將對應(yīng)的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>
但是這個(gè)僅僅適合小項(xiàng)目并且bean少的情況,假如在打項(xiàng)目上颜曾,可能有幾十上百的bean纠拔,都要一一配置豈不是累死人了,而且容易出bug泛豪。
于是繼續(xù)研究稠诲,首先自定義DefaultSqlSessionFactoryBean取代默認(rèn)的SqlSessionFactoryBean,并且在buildSqlSessionFactory()方法上對bean掃描的配置進(jìn)行一步步log拆分,主要代碼如下:
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
logger.debug("##################################################################################");
List<String> children = VFS.getInstance().list(packageToScan.replace(".", "/"));
logger.debug("findclass:" + children);
logger.debug("##################################################################################");
logger.debug("DefaultSqlSessionFactoryBean.buildSqlSessionFactory.registerAliases:" + packageToScan);
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
resolverUtil.find(new ResolverUtil.IsA(Object.class), packageToScan);
Set<Class<? extends Class<?>>> 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");
}
}
}
發(fā)現(xiàn)在ide上運(yùn)行的時(shí)候是可以掃描到指定的bean诡曙,但是在jar上就掃描不到了臀叙。主要問題找到了,就是對代碼的掃描問題,原本想自定義configuration的价卤,但是發(fā)現(xiàn)好多類都是依賴configuration這個(gè)類匹耕,于是便放棄這個(gè)想法,轉(zhuǎn)而研究掃描不到bean的問題荠雕。
繼續(xù)研究源碼稳其,得知bean的掃描是通過ResolverUtil這個(gè)類進(jìn)行的,并且ResolverUtil掃描是通過VFS進(jìn)行掃描的炸卑,主要代碼:
public ResolverUtil<T> 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;
}
結(jié)合log上的信息既鞠,工程上默認(rèn)使用的是Mybatis的DefaultVFS進(jìn)行掃描。查看這個(gè)類代碼后也沒發(fā)現(xiàn)什么問題盖文,轉(zhuǎn)而求救于百度與谷歌嘱蛋,經(jīng)過多方搜索,找到了Mybatis官網(wǎng)issue (地址:https://github.com/mybatis/mybatis-3/issues/325)這里有對這些問題進(jìn)行說明五续。根據(jù)issue的描述與各大神的回答洒敏,定位到了問題是DefaultVFS在獲取jar上的class問題。
在mybat/spring-boot-starter工程上找到了SpringBootVFS疙驾,這個(gè)類重寫了class掃描功能凶伙,通過spring進(jìn)行掃描。
于是將SpringBootVFS拷貝到工程上它碎,并且添加到VFS實(shí)現(xiàn)上去函荣,代碼如下:
@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項(xiàng)目master分支和deploy_jar_bugfind
http://git.oschina.net/cmlbeliever/SpringBootLearning
解決辦法:
- 通過mybatis-configuration.xml進(jìn)行配置
- 通過SpringBootVFS進(jìn)行自動(dòng)掃描配置(推薦)