Mybatis-Spring源碼分析圖解

Mybatis-Spring

當我們使用mybatis和spring整合後為什麼下面的代碼可以運行?

一個問題:

我就寫瞭個mapper接口為什麼能用?

首先來看,在spring的配置xml中有一段

<bean id="configurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.jame.dao"/>
</bean>

這段xml的作用是將一個類添加到spring容器中,點進這個類看看

它實現瞭一個BeanDefinitionRegistryPostProcessor接口,關於這個接口的作用和執行時機上篇博客寫過瞭,這裡就不再贅述

那麼它必然實現postProcessBeanDefinitionRegistry方法,點擊這個方法查看

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
        processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    ..........
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

其中將接口註冊到spring容器中在最後一行,先來看ClassPathMapperScanner這個類,它繼承瞭ClassPathBeanDefinitionScanner這個掃描器

scan的具體代碼

public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    doScan(basePackages);

    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

這個是spring內部的掃描方法,當它走到doScan的時候,因為ClassPathMapperScanner這個類重寫瞭doScan方法,所以會調用子類重寫的方法

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
        LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
                    + "' package. Please check your configuration.");
    } else {
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

通過包名獲取BeanDefinitionHolder,現在它獲取到瞭User接口的BeanDefinitionHolder,然後判斷如果BeanDefinitionHolder的集合為空,也就是沒有找到mapper的情況則不做任何處理,而現在有一個UserMapper的,進入else

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (AbstractBeanDefinition) holder.getBeanDefinition();
       	.........
        //主要看這行
        definition.setBeanClass(this.mapperFactoryBeanClass);
	  .........
        if (!definition.isSingleton()) {
            BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
            if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
                registry.removeBeanDefinition(proxyHolder.getBeanName());
            }
            registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
        }
    }
}

將MapperFactoryBean類設置為瞭UserMapperBeanDefinition的class

spring在創建這個userMapper這個Bean的時候會使用這個有參構造將當前這個UserMapper類型設置到mapperInterface屬性上(為啥使用有參構造而不是無參來初始化對象我也不知道…..這和spring推斷構造方法有關,以後學會瞭在來寫)

這個MapperFactoryBean實現瞭一個FactoryBean接口,這個接口可以讓我們自定義獲取bean的操作

回到spring的代碼,例如當我們使用context.getBean(xxx.class)的時候

spring將xxx.class類型解析為bean名稱,通過名稱去獲取

protected <T> T doGetBean(
    String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
    throws BeansException {
    //獲取對應的beanName
    String beanName = transformedBeanName(name);
    Object bean;
    Object sharedInstance = getSingleton(beanName);

    if (sharedInstance != null && args == null) {
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }
	.......
    // Create bean instance.
    if (mbd.isSingleton()) {
        sharedInstance = getSingleton(beanName, () -> {
            try {
                //真正創建對象的地方
                return createBean(beanName, mbd, args);
            }
            catch (BeansException ex) {
                // Explicitly remove instance from singleton cache: It might have been put there
                // eagerly by the creation process, to allow for circular reference resolution.
                // Also remove any beans that received a temporary reference to the bean.
                destroySingleton(beanName);
                throw ex;
            }
        });
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
    }     
}

首先是調用getSingleton方法,嘗試獲取存在緩存中的bean(其實就是三個Map,key為bean名稱,value是對象),那現在是首次獲取map中沒有

然後執行到下面的createBean,當創建完這個bean後spring需要判斷這個bean是一個普通bean還是一個FactoryBean,程序員是想要獲取普通bean還是FactoryBean,還是FactoryBean的getObject方法返回的從工廠生成的對象

咱們一段一段看

protected Object getObjectForBeanInstance(
    Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {

    if (BeanFactoryUtils.isFactoryDereference(name)) {
        if (beanInstance instanceof NullBean) {
            return beanInstance;
        }
        if (!(beanInstance instanceof FactoryBean)) {
            throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
        }
    }
	.....
}

BeanFactoryUtils.isFactoryDereference(name)的作用是一個字符串判斷,當返回傳入名稱是否為工廠,如果name不為空,並且以&開頭返回true

這個方法在下面的判斷也使用到瞭,記一下它的作用即可

來看例子

在我們使用FactoryBean通過context.getBean(“工廠Bean名稱”)的時候獲取的是FactoryBean的getObject生成的對象,如果我們想獲取FactoryBean的引用則需要在名稱前面加一個&符號

回來看代碼,如果這個bean的引用是一個NullBean類型則直接返回引用,下面有做瞭一個判斷

if (!(beanInstance instanceof FactoryBean))再次判斷這個bean是不是一個FactoryBean,如果為true則拋出異常,這個好理解,因為我們在getBean的時候完全可以將一個普通的bean名稱前面加上&符號

主要的判斷在下面的這個if

if (!(beanInstance instanceof FactoryBean) || BeanFactoryUtils.isFactoryDereference(name)) {
    return beanInstance;
}

現在有3中情況

1.當前的bean是一個普通的bean

第一個條件false 取反 true 第二個條件false 結果true,直接返回bean實例

2.當前是一個FactoryBean,想通過工廠獲取Bean

第一個條件 true 取反false 第二個條件false 結果false,進行下面的操作

3.當前是一個FactoryBean,想獲取工廠的引用

第一個條件 true 取反 false 第二個條件 true 結果 true 直接返回factoryBean實例

當前我們是想通過FactoryBean獲取對象,那麼不進if,繼續下面的代碼

Object object = null;
// 如果beanDefinition為null,則嘗試從緩存中獲取給定的FactoryBean公開的對象
if (mbd == null) {
    //嘗試從緩存中加載bean
    object = getCachedObjectForFactoryBean(beanName);
}
// 未能從緩存中獲得FactoryBean公開的對象,則說明該bean是一個新創建的bean
if (object == null) {
    FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
    if (mbd == null && containsBeanDefinition(beanName)) {
        mbd = getMergedLocalBeanDefinition(beanName);
    }
    boolean synthetic = (mbd != null && mbd.isSynthetic());
    // 從給定的FactoryBean中獲取指定的beanName對象
    object = getObjectFromFactoryBean(factory, beanName, !synthetic);
}
return object;

主要來看getObjectFromFactoryBean

protected Object getObjectFromFactoryBean(FactoryBean<?> factory, String beanName, boolean shouldPostProcess) {
    if (factory.isSingleton() && containsSingleton(beanName)) {
        synchronized (getSingletonMutex()) {
            Object object = this.factoryBeanObjectCache.get(beanName);
            if (object == null) {
                //調用factoryBean的getObject方法
                object = doGetObjectFromFactoryBean(factory, beanName);
                Object alreadyThere = this.factoryBeanObjectCache.get(beanName);
                if (alreadyThere != null) {
                    object = alreadyThere;
                }
            }
            ..........
        }
    }
}

doGetObjectFromFactoryBean方法

private Object doGetObjectFromFactoryBean(FactoryBean<?> factory, String beanName) throws BeanCreationException {
    Object object;
    try {
        if (System.getSecurityManager() != null) {
            AccessControlContext acc = getAccessControlContext();
            try {
                object = AccessController.doPrivileged((PrivilegedExceptionAction<Object>) factory::getObject, acc);
            }
            catch (PrivilegedActionException pae) {
                throw pae.getException();
            }
        }
        else {
            //調用重寫的getObject方法
            object = factory.getObject();
        }
    }
   	.......
    return object;
}

也就是說當我們getBean(“userMapper”)的時候其實是調用FactoryBean的getObject方法,代碼回到mybatis-spring項目的MapperFactoryBean類中的getObject方法

@Override
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

@Override
public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
}

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

到最後發現是通過jdk的動態代理來生成的對象,那麼回答開始的問題

我就寫瞭個接口為什麼能用?

因為mybatis在spring加載bean之前修改瞭beanDefinition,通過MapperScannerConfigurer類實現的BeanDefinitionRegistryPostProcessor接口中將我們定義的一些mapper接口的BeanDefinition的BeanClass屬性修改為瞭MapperFactoryBean,而這個類實現瞭FactoryBean,我們獲取接口實際上是通過FactoryBean的getObject方法

到此這篇關於Mybatis-Spring源碼分析的文章就介紹到這瞭,更多相關Mybatis-Spring源碼分析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: