一個類似循環依賴問題(對getBeansOfType理解)

項目中碰到的問題,相同代碼在單元測試中報錯,但起整個項目不報錯。spring一行一行debug解決的。這裏把筆記粘出來給碰到相同問題的同學一點點啓迪(雖然我感覺沒人像我一樣倒黴)

下面三種配置方式在一般的Mybatis配置中,都是可以用的。
 
1.
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
</bean>
2.
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
3.
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
 
 
 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="typeAliasesPackage" value="com.aa.dao.domain,com.bb.dao.domain"/>
    <property name="mapperLocations" value="classpath*:com/**/mappers/**/*.xml"/>
</bean>
 
 
背景
 
但在項目中碰到了一個問題,我爲了實現動態數據源,重寫org.mybatis.spring.SqlSessionFactoryBean
 
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
 
public class LocalSqlSessionFactoryBean extends SqlSessionFactoryBean implements BeanFactoryAware {
 
    private DataSourceSelecter dataSourceSelecter;
 
    @Override
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        SqlSessionFactory sqlSessionFactory = super.buildSqlSessionFactory();
        // 設置默認的JdbcType
        Configuration configuration = sqlSessionFactory.getConfiguration();
        configuration.setJdbcTypeForNull(JdbcType.VARCHAR);
        if (this.dataSourceSelecter != null) {
            Field mapperRegistryField = ReflectionUtils.findField(Configuration.class"mapperRegistry");
            ReflectionUtils.makeAccessible(mapperRegistryField);
            ReflectionUtils.setField(mapperRegistryField, configuration, new MultiMapperRegistry(configuration, this.dataSourceSelecter));
        }
        return sqlSessionFactory;
    }
 
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (beanFactory instanceof ListableBeanFactory) {
            ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
            Map<String, DataSourceSelecter> map =null;
            try{
                map = listableBeanFactory.getBeansOfType(DataSourceSelecter.class);
            }catch (Exception ex){
                System.out.println();
            }
            System.out.println("");
            if (map != null && map.size() == 1) {
                this.dataSourceSelecter = map.values().iterator().next();
            }
        }
    }
}
LocalSqlSessionFactoryBean的作用簡單點來說修改SqlSessionFactory中Configuration裏生成代理對象MapperRegistry爲MultiMapperRegistry,
給它設置dataSourceSelecterdataSourceSelecter裏面保存了對應接口方法使用的數據源信息。
MultiMapperRegistry生成的代理對象比Mybaits自帶的多了在執行invoke操作前保存當先數據源類型到ThreadLocal,執行sql時,動態數據源會取這個類型對應數據源來操作。
 
但當我們配置層如下兩種時,發現SqlSessionFactory中Configuration裏生成代理對象的是MapperRegistry而不是MultiMapperRegistry,原因看代碼一定是this.dataSourceSelecter != null這句話不爲true,但是this.dataSourceSelecter在xml中有配置。
1.
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
</bean>
 
2.
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
 
    <property name="routerMap">
        <map>
            <entry key="other" value="com.aa.commconf.dao.mapper.**" />
            <entry key="fbs" value="com.jiupai.fbs.dao.mapper.**" />
            <entry key="payadm" value="com.aa.dao.payadm.mapper/>
        </map>
    </property>
</bean>
 
我們來看看dataSourceSelecter是這麼被注入的,可以看到listableBeanFactory.getBeansOfType(DataSourceSelecter.class)這段代碼,當我們初始化LocalSqlSessionFactoryBean時會檢查Spring中有無DataSourceSelecter這個類型的對象,但是問題就出現在這個方法。
 
 
當我們在創建完LocalSqlSessionFactoryBean這個對象,依賴注入populateBean完成後。開始調用initializeBean來進行初始工作。首先BeanFactoryAware,優先級高於InitializingBean,先執行setBeanFactory方法。setBeanFactory裏會調用getBeansOfType這個方法。
getBeansOfType會做兩個步驟
I.從單例緩存裏取 Object beanInstance = this.getSingleton(beanName, false),類型合適的beanName
II.遍歷BeanDefinition取類型合適的beanName,如果是FactoryBean我們需要創建FactoryBean對象,調用Class<?> getObjectType();它實際才知道實際創建的bean類型,看下圖我們可知他會創建工廠bean
 
 
這裏有個allowEagerInit參數,它的作用是,當我按類型去取bean時,如果FactoryBean要不要創建實際object
 
III.把從I、II步獲取到的beanName通過getbean方法獲取對象
 
 
分析
    在這個前提下我們來分析看爲什麼1 2情況。會導致我們在獲取Mapper代理對象時,dataSourceSelecter還是空的
 
情況 2
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
 
MapperScannerConfigurer實現BeanDefinitionRegistryPostProcessor,beanFactroy啓動完成會創建並執行postProcessBeanDefinitionRegistry。用來給我們註冊beanDefintion。
當設置的是sqlSessionFactoryBeanName是,
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("sqlSessionFactory"new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
 
 
開始分析~ 上面講了,初始化LocalSqlSessionFactoryBean時getBeansOfType,會導致MapperFactoryBean被初始化,但LocalSqlSessionFactoryBean自身還沒初始化完dataSourceSelecter還沒被填充。MapperFactoryBean初始化時會去按照"sqlSessionFactory名字去調用getbean方法獲取bean來注入,此時LocalSqlSessionFactoryBean還在singletonFactories裏面被get,移動到earlySingletonObjects。返回一個還沒設置dataSourceSelecterLocalSqlSessionFactoryBean。但是LocalSqlSessionFactoryBean它本身又是一個FactoryBean,會繼續調用getObject方法來獲取實際對象,
 
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }
 
    return this.sqlSessionFactory;
}
 
public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property 'dataSource' is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}
 
LocalSqlSessionFactoryBean.buildSqlSessionFactory.
 
到這裏可以看出調用getObject()返回會調用this.buildSqlSessionFactory()方法,但LocalSqlSessionFactoryBean的buildSqlSessionFactory通過dataSourceSelecter去判斷是否修改MapperRegistry時,dataSourceSelecter還沒拿到,所以沒有替換,就沒有完成我們想要功能
(這裏還有一個細節,就是當LocalSqlSessionFactoryBean沒有實例化完成時,他getObject獲取的對象也是不會被緩存的。每次getObject都是buildSqlSessionFactory()新創建的)
 
情況 2是必會出現這種情況,但情況1有點詭異。它並不一定會出現這種情況,繼續往下分析
 
 
情況1
 
1.
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
</bean>
definition.setAutowireMode(2)設置按類型依賴注入
 
情況2問題在於如果Mapper代理對象早於LocalSqlSessionFactoryBean創建,就不會產生MapperRegistry沒有被替換這種問題,即不會有this.dataSourceSelecter null問題,否則就會產生這種問題
 
我們先按照情況2一樣,LocalSqlSessionFactoryBean創建調用getObjectType,導致MapperFatoryBean被創建,此時MapperFactoryBean初始化時就不是上面按照"sqlSessionFactory名字去調用getbean方法獲取bean來注入來了。它也是調用getObjectType來取,但換湯不換藥,和情況1一樣,拿到的LocalSqlSessionFactoryBean返回的是還沒初始化dataSourceSelecter邏輯產生的,不符合我們需要。
 
那關鍵在於MapperFatoryBean早於LocalSqlSessionFactoryBean創建會怎麼樣。一個MapperFatoryBean在依賴注入時調用getObjectType會將其他FactoryBean創建。
這裏語言不太好描述,有點像原子核裂變。
 
Mapper1 - Mapper1
 
        - Mapper2 - Mapper1 - …
 
                  - Mapper2 
 
                  - Mapper3 - …
                        
                  .
                  .
                  . …                                               LocalSqlSessionFactoryBean
 
                  - LocalSqlSessionFactoryBean - …
 
 
 
        - Mapper3 - Mapper1 - …
 
                  - Mapper2 - …
 
                  - Mapper3 
 
                  - LocalSqlSessionFactoryBean - …
 
        - LocalSqlSessionFactoryBean - Mapper1 - …
 
                                  - Mapper2 - …
 
                                  - Mapper3 - ...
 
                                  - LocalSqlSessionFactoryBean
 
這裏也有個細節,每個MapperFatoryBean再依賴注入調用getObjectType時是都會將自己本身從singletonFactories裏面被get,移動到earlySingletonObjects,而不是被創建因爲上面說的allowEagerInit,允許初始化Fatorybean。
 
看紅色的LocalSqlSessionFactoryBean有點抽象,就是在獲取第一個LocalSqlSessionFactoryBean時,其他Mapper已經全都在earlySingletonObjects裏了,等他被創建,在他被創建時,調用setBeanFactory裏會調用getBeansOfType這個方法時,他不會導致MapperFactoryBean被創建,而是所有的MapperFactoryBean,都呆在
earlySingletonObjects等待他被創建,只要他創建一個,回溯所有Mapper都講注入他被創建。
 
 
情況3
 
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.**.mapper.**"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
definition.getPropertyValues().add("sqlSessionFactory"this.sqlSessionFactory);
這個時候sqlSessionFactory作爲MapperScannerConfigurer的依賴對象,在創建MapperScannerConfigurer時就已經創建,此時MapperScannerConfigurer還沒有創建並執行postProcessBeanDefinitionRegistry,即Mapper接口都沒被掃出來生成代理對象beanDefinition
 
 
總結:
    總的來說裏面內含的問題很複雜,需要仔細琢磨。但大體但不確切的說就是隱含了一個循環依賴問題,創建Mapper依賴於LocalSqlSessionFactoryBean的創建,而LocalSqlSessionFactoryBean的創建會調用getBeansOfType導致所有FatcoryBean被創建,而MapperFactoryBean又依賴LocalSqlSessionFactoryBean的getObejct返回對象,此時根據之前分享的spring循環依賴解決,取道的是早期LocalSqlSessionFactoryBean,依賴的dataSourceSelecter屬性還沒有注入。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章