Springboot Mybatis 打包jar扫描bean与mapper问题研究与解决

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

解决办法:

  1. 通过mybatis-configuration.xml进行配置
  2. 通过SpringBootVFS进行自动扫描配置(推荐)

你可能感兴趣的:(java,mybatis,spring,boot,Spring技术栈)