前言

前几天 项目上有这样的一个问题, 内存给的2G, 项目似乎是启动不起来, 然后 之后调整为 4G 项目就启动起来了 

然后 这个问题, 我们最开始都没有太在意, 认为可能 初始化项目需要的内存稍微需要大一些吧 ? 

 

然后 上周五, 和测试同事发布项目的时候, 又出现了这个问题, 然后 在测试环境 周四是能够启动起来, 然后 期间有一些小调整, 然后 周五发布项目 就发布不起了 

还有一些其他的现象, 预发布环境是发布不了的, 然后 就没有往下走了, 大家下班了 

我本地 启动项目, mountServices 是可以启动起的, 并且没有什么异常 

然后 我在我们开发环境我试了一下 第一次也没得问题, 能够启动成功, 然后 第二次 我又重新打了一个包, 更新了所有的依赖, 看了一下日志, 发现 启动的过程中 有很多 "2018-10-12 19:24:52,352 [main] WARN  org.springframework.beans.factory.support.AbstractBeanFactory (AbstractBeanFactory.java:1490) - Bean creation exception on non-lazy FactoryBean type check: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'trainReportPerDayDetailMapper' defined in URL [jar:file:/home/dubbo/xxx-base/deploy/xxx-base-service-0.2-SNAPSHOT.jar!/com/xxx/dao/generator/XXXMapper.class]: Unsatisfied dependency expressed through bean property 'sqlSessionFactory': Error creating bean with name 'sqlSessionFactory' defined in class path resource [spring/applicationContext-data.xml]: Invocation of init method failed; nested exception is java.lang.StackOverflowError; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'sqlSessionFactory' defined in class path resource [spring/applicationContext-data.xml]: Invocation of init method failed; nested exception is java.lang.StackOverflowError" 这种样子的错误

    当然 还能从日志文件里面 找到最关键的一个信息, 一个"完整的StackTrace", 参见文件下载 

 

项目基本信息 : dubbo项目, spring-* 4.3.0 + dubbo 2.8.4 + mybatis 3.2.7 + mybatis-spring 1.2.2 

 

问题的排查

看到这串 StackTrace, 我就意识到 问题并不是那么简单, 因此 周五还是早早的下班比较好, 还是找一个心情好的时间 来看下这个问题, 不然 搞到半夜也浪费时间, 还对身体不好, 而且还有一个问题, 同事 还等着的呢, 算了 下班了 

那么 如何解决这个问题呢?, 自然 要复现啊, 但是 我本地又没得这个问题啊, 然后 抱着侥幸的心理, 在 StackTrace 上面的 DruidDataSource.init 上面打了一个断点, 然后 debug 启动了一下项目 

然后 断点停了, 大致的造成项目启动不了的一个粗略的原因 也出来了 

34 mybatis-spring Mapper太多导致 StackOverflow

 

这 157 次 AbstractAutowireCapableBeanFactory. doCreateBean 的调用, 前几次 是容器中一个 实例[redisXXX], 使用 @Autowire 注入了一个 连接池 

然后 中间 153 次调用是各个 mybatis 的 Mapper, ExMapper 

然后 后面的几次 是, sqlSessionFactory,   dataSource 的调用  

在 spring容器 注入 redisXXX 连接池依赖的时候, 调用了 DefaultListableBeanFactory. doGetBeanNamesForType 来查找容器中所有的 redis连接池 对象 

需要遍历 容器里面所有的对象, 在遍历到了 第一个 AMapper 的时候, 发现容器里面还没有创建 AMapper, 然后 调用了 AbstractAutowireCapableBeanFactory. getTypeForFactoryBean 来实例化 AMapper 

然后 又解析 AMapper 的依赖 sqlSessionFactory 

然后 调用 DefaultListableBeanFactory. doGetBeanNamesForType 方法来查找 sqlSessionFactory, 遍历到 AMapper 的时候, AMapper已经实例化了, 然后 判断了一下类型, 是否匹配 sqlSessionFactory , 不匹配 继续往下走 

然后 找到了 第二个BMapper, 然后 发现容器里面还没有创建 BMapper, 然后 调用了 AbstractAutowireCapableBeanFactory. getTypeForFactoryBean 来实例化 AMapper 

然后 又解析 BMapper 的依赖 sqlSessionFactory 

直到 到达最后一个 Mapper, 找到了所有的 sqlSessionFactory 之后, 开始 实例化 sqlSessionFactory  (参见下图)

然后 解析sqlSessionFactory 的依赖, 实例化 dataSource 等等, 然后 到了我们的 DruidDataSource.init 的断点 

34 mybatis-spring Mapper太多导致 StackOverflow

 

问题的解决 

至于解决的方法, 我有几种思路, 但是 似乎不太好处理呢?? 
方法1. 先预先吧所有的 Mapper 对应的实例 加载好, 破坏递归的条件[初始化当前Mapper, 进而解析依赖, 进而递归到下一个Mapper的流程] 
方法2. Mapper 对应的实例 的sqlSessionFactory, 注入方式 似乎是 byType, 然后 才进入了"递归", 更新一**入的方式, 就不用进入需要遍历beanNames 的"递归" 

方法1

   似乎是不太好找到更新优先初始化 Mapper 实例的方法
    最开始, 我想的是 是否可以通过 MapperScannerConfigurer 里面配置优先级, 然后配置其他的bean的优先级, 但是 似乎实现起来并没有这么简单, 提升 Mapper 的优先级似乎是搞不定 
方法2

    case1. 从 ClassPathMapperScanner 里面调整一下 默认的 autowireMode, 差不多是 需要两个事情吧, 重写两个自己的 MapperScannerConfigurer, ClassPathMapperScanner, 调整一下 前者使用的 MapperScanner, 以及后者的 autowireMode 
    case2. 然后之后 又瞄了一下 ClassPathMapperScanner 的代码, 发现 可以显示指定, sqlSessionFactoryBeanName/sqlSessionFactory 或者sqlSessionTemplateBeanName/sqlSessionTemplate 可以直接引用 容器中的实例, 同样可以达到效果 
    
最后使用 方法2 的后者 达到了效果, 问题 似乎是解决了, 前者 没有尝试  

 

后记

1. tips : idea 条件断点 的一个细节, 打了改断点之后, 项目启动 初始化每一个 ServiceBean 的时候[afterPropertiesSet], 有6个 beansOfTypeIncludingAncestors 的查询, 非常慢 
DefaultListableBeanFactory:440 打了一个条件断点, 条件如下 
"accFormatMapper".equals(beanName) && type.type == SqlSessionFactory.class 
原来这么慢, 是因为 条件断点的开销, 
擦 我还以为是调整了一下, "<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />" 的开销呢, 

另外 通过 overhead 这里的提示, 可以看到 这个条件断点的开销, 普通的断点 基本上是不会有太大的开销的 

34 mybatis-spring Mapper太多导致 StackOverflow

 

2. 差不多是后来一点 : 网上搜索了一下 "spring mybatis Mapper太多 StackOverflow"

是能够搜索到一篇文章的 : https://blog.csdn.net/hongxingxiaonan/article/details/50354195 

查看了一下 , SqlSessionDaoSupport 的注释, setXX 这两个方法的 @Autowire 是在 1.2.0 里面删除的 

/**
 * Convenient super class for MyBatis SqlSession data access objects.
 * It gives you access to the template which can then be used to execute SQL methods.
 * <p>
 * This class needs a SqlSessionTemplate or a SqlSessionFactory.
 * If both are set the SqlSessionFactory will be ignored.
 * <p>
 * {code Autowired} was removed from setSqlSessionTemplate and setSqlSessionFactory
 * in version 1.2.0.
 * 
 * @author Putthibong Boonbong
 *
 * @see #setSqlSessionFactory
 * @see #setSqlSessionTemplate
 * @see SqlSessionTemplate
 * @version $Id$
 */

  @Autowired(required = false)
  public final void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  @Autowired(required = false)
  public final void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

另外 比较了一下 mybatis-spring 1.1.1 和 1.2.0 的 MapperScannerCongiurer, 1.2.0 将 doScan 的步骤封装了一层, 封装了一个 ClassPathMapperScanner

另外 就是 如果 MapperScannerCongiurer 如果没有显式的指定 sqlSessionFactoryBeanName/sqlSessionFactory 或者sqlSessionTemplateBeanName/sqlSessionTemplate, 配置 MapperFactoryBean 的 autowireMode 为 "autowire by type" 

1.1.1 里面 则是, 如果显式指定 或者 通过 @Autowire(required = false), 至于二者的 先后顺序, 应该是 以用户显式指定的为准, 如果 已经显式指定, 会忽略 @Autowire 

综上 1.1.1 不显示指定 sqlSessionFactoryBeanName/sqlSessionFactory 或 sqlSessionTemplateBeanName/sqlSessionTemplate, 会通过 @Autowire 来寻找 sqlSessionFactory, 

1.2.0 不显示指定 sqlSessionFactoryBeanName/sqlSessionFactory 或 sqlSessionTemplateBeanName/sqlSessionTemplate, 会通过 autowireMode 为 "autowire by type" 来寻找 sqlSessionFactory 

我觉得, 其实 不显示指定的话, 都会走 上面的递归的流程 

但是 为什么博主问题就解决了, 可能还是 环境上有一些问题, 或者我上面的猜测 有一些错误的地方吧, 感谢 hongxingxiaonan 分享 

 

3. 附上一个模拟该场景的代码, 方便理解 

以下实例代码中, ConnectionProvider 以及相关的工具类, 可以通过其他的方式代替, 稍作改动 就能运行

package com.hx.test05;

import com.hx.log.collection.CollectionUtils;
import com.hx.mongo.connection.DefaultMysqlConnectionProvider;
import com.hx.mongo.connection.interf.ConnectionProvider;
import org.apache.commons.lang.StringUtils;

import java.util.*;

import static com.hx.log.util.Log.info;

/**
 * Test28MapperStackOverFlow
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 10/14/2018 9:39 AM
 */
public class Test28MapperStackOverFlow {

    /**
     * container
     */
    private static Set<String> BEAN_NAMES = new LinkedHashSet<>();
    private static Map<String, Object> BEAN_CREATED = new HashMap<>();

    /**
     * for debug
     */
    private static int STACK_DEPTH = 0;
    private static boolean DEBUG = true;

    // Test28MapperStackOverFlow
    public static void main(String[] args) {

        // initialized
        registerBean("connectionProvider", new DefaultMysqlConnectionProvider());
        // not initialized
        for (int i = 0; i < 10; i++) {
            registerBean("The" + i + "thMapper", null);
        }

        List<String> result = doGetBeanNamesForType(MapperFactoryBean.class);
        info(result);

    }

    /**
     * look up 容器中所有匹配 给定类型的 beanName
     *
     * @param requiredType requiredType
     * @return java.util.List<java.lang.String>
     * @author Jerry.X.He
     * @date 10/14/2018 9:42 AM
     * @since 1.0
     */
    public static List<String> doGetBeanNamesForType(Class requiredType) {
        if (DEBUG) {
            info(generatePadding(STACK_DEPTH) + "doGetBeanNamesForType, requiredType : " + requiredType.getName());
        }
        STACK_DEPTH++;

        List<String> result = new ArrayList<>();
        for (String beanName : BEAN_NAMES) {
            if (isTypeMatch(beanName, requiredType)) {
                result.add(beanName);
            }
        }

        STACK_DEPTH--;
        return result;
    }

    /**
     * @param beanName     beanName
     * @param requiredType requiredType
     * @return boolean
     * @author Jerry.X.He
     * @date 10/14/2018 9:40 AM
     * @since 1.0
     */
    public static boolean isTypeMatch(String beanName, Class requiredType) {
        if (DEBUG) {
            info(generatePadding(STACK_DEPTH) + "isTypeMatch, beanName : " + beanName);
        }

        Object beanInstance = getBean(beanName);
        if (beanInstance != null) {
            return doTypeMatch(beanInstance, requiredType);
        }

        // create bean by factoryBean
        MapperFactoryBean bean = new MapperFactoryBean();
        registerBean(beanName, bean);

        // resolve dependencies
        List<String> connectionProviderBeanNames = doGetBeanNamesForType(ConnectionProvider.class);
        if (CollectionUtils.isEmpty(connectionProviderBeanNames)) {
            throw new RuntimeException(" can't find bean of ConnectionProvider ! ");
        }
        ConnectionProvider provider = getBean(connectionProviderBeanNames.get(0), ConnectionProvider.class);

        // populate attributes
        bean.setProvider(provider);

        return doTypeMatch(bean, requiredType);
    }

    public static boolean doTypeMatch(Object instance, Class requiredType) {
        return requiredType.isAssignableFrom(instance.getClass());
    }

    /**
     * 从容器中拿 beanName 对应的 bean
     *
     * @param beanName beanName
     * @return java.lang.Object
     * @author Jerry.X.He
     * @date 10/14/2018 9:45 AM
     * @since 1.0
     */
    public static Object getBean(String beanName) {
        return BEAN_CREATED.get(beanName);
    }

    public static <T> T getBean(String beanName, Class<T> clazz) {
        Object bean = BEAN_CREATED.get(beanName);
        if (!doTypeMatch(bean, clazz)) {
            return null;
        }

        return (T) bean;
    }

    /**
     * 注册 给定的 bean
     *
     * @param beanName beanName
     * @param bean     bean
     * @return void
     * @author Jerry.X.He
     * @date 10/14/2018 9:58 AM
     * @since 1.0
     */
    public static void registerBean(String beanName, Object bean) {
        BEAN_NAMES.add(beanName);
        Object oldBean = BEAN_CREATED.put(beanName, bean);
        // process oldBean
    }

    /**
     * MapperFactoryBean
     *
     * @author Jerry.X.He <[email protected]>
     * @version 1.0
     * @date 10/14/2018 9:52 AM
     */
    static class MapperFactoryBean {
        ConnectionProvider provider;

        public MapperFactoryBean() {
        }

        public ConnectionProvider getProvider() {
            return provider;
        }

        public void setProvider(ConnectionProvider provider) {
            this.provider = provider;
        }
    }

    // ----------------- 辅助方法 -----------------------

    /**
     * 生成 len 个空格, 调试
     *
     * @param len len
     * @return java.lang.String
     * @author Jerry.X.He
     * @date 10/14/2018 10:16 AM
     * @since 1.0
     */
    private static String generatePadding(int len) {
        return StringUtils.leftPad("", len << 2);
    }

}

34 mybatis-spring Mapper太多导致 StackOverflow

 

执行的大致流程如下, 试想 将 10个Mapper 换成 100 个Mapper, 会有怎样的烟火 #_# 

doGetBeanNamesForType, requiredType : com.hx.test05.Test28MapperStackOverFlow$MapperFactoryBean
    isTypeMatch, beanName : connectionProvider
    isTypeMatch, beanName : The0thMapper
    doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
        isTypeMatch, beanName : connectionProvider
        isTypeMatch, beanName : The0thMapper
        isTypeMatch, beanName : The1thMapper
        doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
            isTypeMatch, beanName : connectionProvider
            isTypeMatch, beanName : The0thMapper
            isTypeMatch, beanName : The1thMapper
            isTypeMatch, beanName : The2thMapper
            doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                isTypeMatch, beanName : connectionProvider
                isTypeMatch, beanName : The0thMapper
                isTypeMatch, beanName : The1thMapper
                isTypeMatch, beanName : The2thMapper
                isTypeMatch, beanName : The3thMapper
                doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                    isTypeMatch, beanName : connectionProvider
                    isTypeMatch, beanName : The0thMapper
                    isTypeMatch, beanName : The1thMapper
                    isTypeMatch, beanName : The2thMapper
                    isTypeMatch, beanName : The3thMapper
                    isTypeMatch, beanName : The4thMapper
                    doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                        isTypeMatch, beanName : connectionProvider
                        isTypeMatch, beanName : The0thMapper
                        isTypeMatch, beanName : The1thMapper
                        isTypeMatch, beanName : The2thMapper
                        isTypeMatch, beanName : The3thMapper
                        isTypeMatch, beanName : The4thMapper
                        isTypeMatch, beanName : The5thMapper
                        doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                            isTypeMatch, beanName : connectionProvider
                            isTypeMatch, beanName : The0thMapper
                            isTypeMatch, beanName : The1thMapper
                            isTypeMatch, beanName : The2thMapper
                            isTypeMatch, beanName : The3thMapper
                            isTypeMatch, beanName : The4thMapper
                            isTypeMatch, beanName : The5thMapper
                            isTypeMatch, beanName : The6thMapper
                            doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                                isTypeMatch, beanName : connectionProvider
                                isTypeMatch, beanName : The0thMapper
                                isTypeMatch, beanName : The1thMapper
                                isTypeMatch, beanName : The2thMapper
                                isTypeMatch, beanName : The3thMapper
                                isTypeMatch, beanName : The4thMapper
                                isTypeMatch, beanName : The5thMapper
                                isTypeMatch, beanName : The6thMapper
                                isTypeMatch, beanName : The7thMapper
                                doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                                    isTypeMatch, beanName : connectionProvider
                                    isTypeMatch, beanName : The0thMapper
                                    isTypeMatch, beanName : The1thMapper
                                    isTypeMatch, beanName : The2thMapper
                                    isTypeMatch, beanName : The3thMapper
                                    isTypeMatch, beanName : The4thMapper
                                    isTypeMatch, beanName : The5thMapper
                                    isTypeMatch, beanName : The6thMapper
                                    isTypeMatch, beanName : The7thMapper
                                    isTypeMatch, beanName : The8thMapper
                                    doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                                        isTypeMatch, beanName : connectionProvider
                                        isTypeMatch, beanName : The0thMapper
                                        isTypeMatch, beanName : The1thMapper
                                        isTypeMatch, beanName : The2thMapper
                                        isTypeMatch, beanName : The3thMapper
                                        isTypeMatch, beanName : The4thMapper
                                        isTypeMatch, beanName : The5thMapper
                                        isTypeMatch, beanName : The6thMapper
                                        isTypeMatch, beanName : The7thMapper
                                        isTypeMatch, beanName : The8thMapper
                                        isTypeMatch, beanName : The9thMapper
                                        doGetBeanNamesForType, requiredType : com.hx.mongo.connection.interf.ConnectionProvider
                                            isTypeMatch, beanName : connectionProvider
                                            isTypeMatch, beanName : The0thMapper
                                            isTypeMatch, beanName : The1thMapper
                                            isTypeMatch, beanName : The2thMapper
                                            isTypeMatch, beanName : The3thMapper
                                            isTypeMatch, beanName : The4thMapper
                                            isTypeMatch, beanName : The5thMapper
                                            isTypeMatch, beanName : The6thMapper
                                            isTypeMatch, beanName : The7thMapper
                                            isTypeMatch, beanName : The8thMapper
                                            isTypeMatch, beanName : The9thMapper
                                    isTypeMatch, beanName : The9thMapper
                                isTypeMatch, beanName : The8thMapper
                                isTypeMatch, beanName : The9thMapper
                            isTypeMatch, beanName : The7thMapper
                            isTypeMatch, beanName : The8thMapper
                            isTypeMatch, beanName : The9thMapper
                        isTypeMatch, beanName : The6thMapper
                        isTypeMatch, beanName : The7thMapper
                        isTypeMatch, beanName : The8thMapper
                        isTypeMatch, beanName : The9thMapper
                    isTypeMatch, beanName : The5thMapper
                    isTypeMatch, beanName : The6thMapper
                    isTypeMatch, beanName : The7thMapper
                    isTypeMatch, beanName : The8thMapper
                    isTypeMatch, beanName : The9thMapper
                isTypeMatch, beanName : The4thMapper
                isTypeMatch, beanName : The5thMapper
                isTypeMatch, beanName : The6thMapper
                isTypeMatch, beanName : The7thMapper
                isTypeMatch, beanName : The8thMapper
                isTypeMatch, beanName : The9thMapper
            isTypeMatch, beanName : The3thMapper
            isTypeMatch, beanName : The4thMapper
            isTypeMatch, beanName : The5thMapper
            isTypeMatch, beanName : The6thMapper
            isTypeMatch, beanName : The7thMapper
            isTypeMatch, beanName : The8thMapper
            isTypeMatch, beanName : The9thMapper
        isTypeMatch, beanName : The2thMapper
        isTypeMatch, beanName : The3thMapper
        isTypeMatch, beanName : The4thMapper
        isTypeMatch, beanName : The5thMapper
        isTypeMatch, beanName : The6thMapper
        isTypeMatch, beanName : The7thMapper
        isTypeMatch, beanName : The8thMapper
        isTypeMatch, beanName : The9thMapper
    isTypeMatch, beanName : The1thMapper
    isTypeMatch, beanName : The2thMapper
    isTypeMatch, beanName : The3thMapper
    isTypeMatch, beanName : The4thMapper
    isTypeMatch, beanName : The5thMapper
    isTypeMatch, beanName : The6thMapper
    isTypeMatch, beanName : The7thMapper
    isTypeMatch, beanName : The8thMapper
    isTypeMatch, beanName : The9thMapper
The0thMapper,  The1thMapper,  The2thMapper,  The3thMapper,  The4thMapper,  The5thMapper,  The6thMapper,  The7thMapper,  The8thMapper,  The9thMapper

Process finished with exit code 0

 

资源下载

stackTrace : https://download.csdn.net/download/u011039332/10719234

 

参考

https://blog.csdn.net/hongxingxiaonan/article/details/50354195

 

 

相关文章:

  • 2021-12-02
  • 2021-09-26
  • 2021-08-10
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-01-26
  • 2022-12-23
  • 2022-12-23
  • 2022-01-28
  • 2021-04-26
  • 2021-10-06
  • 2021-10-09
相关资源
相似解决方案