Spring-Mybatis執行機制概括

DearBelinda發表於2019-01-19

前言

本篇是繼上篇MyBatis原理概括延伸的,所以如果有小夥伴還沒看上篇博文的話,可以先去看下,也不會浪費大家太多的時間,因為本篇會結合到上篇敘述的相關內容。

好,切入正題,這篇主要講一個點,就是我們在結合spring去使用mybatis的時候,spring為我們做了什麼事。還是老套路,我們只講過程思路,具體細節還望各位小夥伴找時間去研究,如果我全講了,你們也都看懂了,那你們最多也就是感到一種獲得感,而不是成就感,獲得感是會隨著時間的推移而慢慢減少的,所以我這裡主要提供給大家一個思路,然後大家可以順著這條思路慢慢摸索下去,從而獲得成就感!

使用spring-mybatis

1.spring-mybatis是什麼

MyBatis-Spring 會幫助你將 MyBatis 程式碼無縫地整合到 Spring 中。 使用這個類庫中的類, Spring 將會載入必要的 MyBatis 工廠類和 session 類。 這個類庫也提供一個簡單的方式來注入 MyBatis 資料對映器和 SqlSession 到業務層的 bean 中。 而且它也會處理事務, 翻譯 MyBatis 的異常到 Spring 的 DataAccessException 異常(資料訪問異常,譯者注)中。最終,它並 不會依賴於 MyBatis,Spring 或 MyBatis-Spring 來構建應用程式程式碼。(這是官網解釋)

2.基於XML配置和註解形式使用

a.基於XML配置

一般情況下,我們使用xml的形式引入mybatis,一般的配置如下:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${driver}" />
    <property name="url" value="${url}" />
    <property name="username" value="${username}" />
    <property name="password" value="${password}" />
    <!-- 初始化連線大小 --> 
    <property name="initialSize" value="${initialSize}"></property> 
    <!-- 連線池最大數量 --> 
    <property name="maxActive" value="${maxActive}"></property> 
    <!-- 連線池最大空閒 --> 
    <property name="maxIdle" value="${maxIdle}"></property> 
    <!-- 連線池最小空閒 --> 
    <property name="minIdle" value="${minIdle}"></property> 
    <!-- 獲取連線最大等待時間 --> 
    <property name="maxWait" value="${maxWait}"></property> 
</bean>

<!-- spring和MyBatis的完美結合,不需要mybatis的配置對映檔案 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 自動掃描mapping.xml檔案 --> 
    <property name="mapperLocations" value="classpath:com/javen/mapping/*.xml"></property> 
</bean>

<!-- DAO介面所在包名,Spring會自動查詢其下的類 --> 
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
    <property name="basePackage" value="com.javen.dao" /> 
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> 
</bean>

如上配置所示,我們一般需要申明dataSource、sqlSessionFactory以及MapperScannerConfigurer。如何我們還有其他mybatis的配置,比如plugin、typehandler等,我們可以另外申明一個mybaits-config.xml檔案,在sqlSessionFactory配置中引入即可。下面對各部分作用總結下。
dataSource:申明一個資料來源;
sqlSessionFactory:申明一個sqlSession的工廠;
MapperScannerConfigurer:讓spring自動掃描我們持久層的介面從而自動構建代理類。

b.基於註解形式

註解形式的話相當於將上述的xml配置一一對應成註解的形式

@Configuration  
@MapperScan(value="org.fhp.springmybatis.dao")  
public class DaoConfig {
  
    @Value("${jdbc.driverClass}")  
    private String driverClass;  
  
    @Value("${jdbc.user}")  
    private String user;  
  
    @Value("${jdbc.password}")  
    private String password;  
  
    @Value("${jdbc.jdbcUrl}")  
    private String jdbcUrl;  
  
    @Bean  
    public DataSource dataSource() {  
        DriverManagerDataSource dataSource = new DriverManagerDataSource();  
        dataSource.setDriverClassName(driverClass);  
        dataSource.setUsername(user);  
        dataSource.setPassword(password);  
        dataSource.setUrl(jdbcUrl);  
        return dataSource;  
    }  
  
    @Bean  
    public DataSourceTransactionManager transactionManager() {  
        return new DataSourceTransactionManager(dataSource());  
    }  
  
    @Bean  
    public SqlSessionFactory sqlSessionFactory() throws Exception {  
        SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();  
        sessionFactory.setDataSource(dataSource());  
        return sessionFactory.getObject();  
    }  
}  

很明顯,一樣需要一個dataSource,SqlSessionFactory以及一個@MapperScan的註解。這個註解的作用跟上述的
MapperScannerConfigurer的作用是一樣的。

3.spring和mybatis無縫整合的機制

a.BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar的認識

在講mybatis如何無縫整合進spring之前,我們先認識下BeanDefinitionRegistryPostProcessor和ImportBeanDefinitionRegistrar這兩個介面的作用。

我們先看下這兩個介面是什麼樣的。

//BeanDefinitionRegistryPostProcessor介面
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
    void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

//ImportBeanDefinitionRegistrar介面
public interface ImportBeanDefinitionRegistrar {
    void registerBeanDefinitions(AnnotationMetadata var1, BeanDefinitionRegistry var2);
}

對於這兩個介面我們先看官方文件給我們的解釋。

以下是BeanDefinitionRegistryPostProcessor的解釋:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in. In particular, BeanDefinitionRegistryPostProcessor may register further bean definitions which in turn define BeanFactoryPostProcessor instances.

意思大概就是我們可以擴充套件spring對於bean definitions的定義。也就是說可以讓我們實現自定義的註冊bean定義的邏輯。
再來看下ImportBeanDefinitionRegistrar的解釋:

public interface ImportBeanDefinitionRegistrar Interface to be implemented by types that register additional bean definitions when processing @Configuration classes. Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary.

Along with @Configuration and ImportSelector, classes of this type may be provided to the @Import annotation (or may also be returned from an ImportSelector).

通俗解釋來講就是在@Configuration上使用@Import時可以自定義beanDefinition,或者作為ImportSelector介面的返回值(有興趣的小夥伴可以自行研究)。

所以總結下就是如果我想擴充套件beanDefinition那麼我可以繼承這兩個介面實現。下面我們就從mybatis配置方式入手講講spring和mybatis是如何無縫整合的。

b.基於XML配置mybatis是如何整合進spring的

首先,容器啟動的時候,我們在xml配置中的SqlSessionFactoryBean會被初始化,所以我們先看下SqlSessionFactoryBean是在初始化的時候作了哪些工作。

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
    private Resource configLocation;
    private Configuration configuration;
    private Resource[] mapperLocations;
    private DataSource dataSource;
    private TransactionFactory transactionFactory;
    private Properties configurationProperties;
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    private SqlSessionFactory sqlSessionFactory;
    private String environment = SqlSessionFactoryBean.class.getSimpleName();
    private boolean failFast;
    private Interceptor[] plugins;
    private TypeHandler<?>[] typeHandlers;
    private String typeHandlersPackage;
    private Class<?>[] typeAliases;
    private String typeAliasesPackage;
    private Class<?> typeAliasesSuperType;
    private DatabaseIdProvider databaseIdProvider;
    private Class<? extends VFS> vfs;
    private Cache cache;
    private ObjectFactory objectFactory;
    private ObjectWrapperFactory objectWrapperFactory;

    public SqlSessionFactoryBean() {
    }
    ...
}

我們可以看到這個類實現了FactoryBean、InitializingBean和ApplicationListener介面,對應的介面在bean初始化的時候為執行些特定的方法(如果不清楚的小夥伴請自行百度,這裡不作過多敘述)。現在我們來看看都有哪些方法會被執行,這些方法又作了哪些工作。

//FactoryBean
public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
        this.afterPropertiesSet();
    }

    return this.sqlSessionFactory;
}

//InitializingBean
public void afterPropertiesSet() throws Exception {
    Assert.notNull(this.dataSource, "Property `dataSource` is required");
    Assert.notNull(this.sqlSessionFactoryBuilder, "Property `sqlSessionFactoryBuilder` is required");
    Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property `configuration` and `configLocation` can not specified with together");
    this.sqlSessionFactory = this.buildSqlSessionFactory();
}

//ApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
    if (this.failFast && event instanceof ContextRefreshedEvent) {
        this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }

}

通過觀察程式碼我們可以知道前面兩個都是在做同一件事情,那就是在構建sqlSessionFactory,在構建sqlSessionFactory時mybatis會去解析配置檔案,構建configuation。後面的onApplicationEvent主要是監聽應用事件時做的一些事情(不詳講,有興趣的同學可以自己去了解下)。

其次,我們回憶下我們在xml配置中還配置了MapperScannerConfigurer,或者也可以配置多個的MapperFactoryBean,道理都是一樣的,只是MapperScannerConfigurer幫我們封裝了這一個過程,可以實現自動掃描指定包下的mapper介面構建MapperFactoryBean。

問題1:為什麼我們從spring容器中能直接獲取對應mapper介面的實現類?而不用使用sqlSession去getMapper呢?
答案其實在上面就已經為大家解答了,就是MapperFactoryBean。我們先看看這個類。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    private Class<T> mapperInterface;
    private boolean addToConfig = true;

    public MapperFactoryBean() {
    }

    public MapperFactoryBean(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    ...
}

這個類繼承了SqlSessionDaoSupport,實現了FactoryBean。
我們先講講SqlSessionDaoSupport這個類

public abstract class SqlSessionDaoSupport extends DaoSupport {
    private SqlSession sqlSession;
    private boolean externalSqlSession;

    public SqlSessionDaoSupport() {
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (!this.externalSqlSession) {
            this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
        }

    }

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSession = sqlSessionTemplate;
        this.externalSqlSession = true;
    }

    public SqlSession getSqlSession() {
        return this.sqlSession;
    }

    protected void checkDaoConfig() {
        Assert.notNull(this.sqlSession, "Property `sqlSessionFactory` or `sqlSessionTemplate` are required");
    }
}

可以看到這個類繼承了DaoSupport,我們再來看下這個類。

public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public DaoSupport() {
    }

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        this.checkDaoConfig();

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }

    protected abstract void checkDaoConfig() throws IllegalArgumentException;

    protected void initDao() throws Exception {
    }
}

可以看到實現了InitializingBean介面,所以在類初始化時為執行afterPropertiesSet方法,我們看到afterPropertiesSet方法裡面有checkDaoConfig方法和initDao方法,其中initDao是模板方法,提供子類自行實現相關dao初始化的操作,我們看下checkDaoConfig方法作了什麼事。

//MapperFactoryBean
protected void checkDaoConfig() {
    super.checkDaoConfig();
    Assert.notNull(this.mapperInterface, "Property `mapperInterface` is required");
    Configuration configuration = this.getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        try {
            configuration.addMapper(this.mapperInterface);
        } catch (Exception var6) {
            this.logger.error("Error while adding the mapper `" + this.mapperInterface + "` to configuration.", var6);
            throw new IllegalArgumentException(var6);
        } finally {
            ErrorContext.instance().reset();
        }
    }

}

這個方法具體的實現是在MapperFactoryBean類裡面的,主要作用就是對驗證mapperInterface是否存在configuration物件裡面。

然後我們再來看下MapperFactoryBean實現了FactoryBean的目的是什麼。我們都知道FactoryBean有一個方法是getObject,這個方法的作用就是在spring容器初始化bean時,如果判斷這個類是否繼承自FactoryBean,那麼在獲取真正的bean例項時會呼叫getObject,將getObject方法返回的值註冊到spring容器中。在明白了這些知識點之後,我們看下MapperFactoryBean的getObject方法是如何實現的。

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

看到這裡是否就已經明白為什麼在結合spring時我們不需要使用sqlSession物件去獲取我們的mapper實現類了吧。因為spring幫我們作了封裝!
之後的操作可以結合上面博文去看mybatis如何獲取到對應的Mapper物件的了。附上上篇博文地址:MyBatis原理概括

接下來我們看下mybatis是如何結合spring構建MapperFactoryBean的beanDefinition的。這裡我們需要看看MapperScannerConfigurer這個類,這個類的目的就是掃描我們指定的dao層(持久層)對應的包(package),構建相應的beanDefinition提供給spring容器去例項化我們的mapper介面物件。

//MapperScannerConfigurer
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    private String basePackage;
    private boolean addToConfig = true;
    private SqlSessionFactory sqlSessionFactory;
    private SqlSessionTemplate sqlSessionTemplate;
    private String sqlSessionFactoryBeanName;
    private String sqlSessionTemplateBeanName;
    private Class<? extends Annotation> annotationClass;
    private Class<?> markerInterface;
    private ApplicationContext applicationContext;
    private String beanName;
    private boolean processPropertyPlaceHolders;
    private BeanNameGenerator nameGenerator;

    public MapperScannerConfigurer() {
    }
    ...
}

通過程式碼,我們可以看到這個類實現了BeanDefinitionRegistryPostProcessor這個介面,通過前面對BeanDefinitionRegistryPostProcessor的講解,我們去看看MapperScannerConfigurer中的postProcessBeanDefinitionRegistry方法的實現。

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

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; 	
"));
}

可以看這裡就是在構建ClassPathMapperScanner物件,然後呼叫scan方法掃描。接下來我們繼續看這個掃描的操作,因為這個類繼承了ClassPathBeanDefinitionScanner,呼叫的scan方法是在ClassPathBeanDefinitionScanner裡申明的。

//ClassPathBeanDefinitionScanner
public int scan(String... basePackages) {
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    this.doScan(basePackages);
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

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

這裡我們需要注意doScan這個方法,這個方法在ClassPathMapperScanner中重寫了。

//ClassPathMapperScanner
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    if (beanDefinitions.isEmpty()) {
        this.logger.warn("No MyBatis mapper was found in `" + Arrays.toString(basePackages) + "` package. Please check your configuration.");
    } else {
        this.processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

這裡呼叫了父類的doScan得到beanDefinitions的集合。這裡的父類的doScan方法是spring提供的包掃描操作,這裡不過多敘述,感興趣的小夥伴可以自行研究。我們還注意到在得到beanDefinitions集合後,這裡還呼叫了processBeanDefinitions方法,這裡是對beanDefinition做了一些特殊的處理以滿足mybaits的需求。我們先來看下這個方法。

//ClassPathMapperScanner#doScan
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    Iterator var3 = beanDefinitions.iterator();

    while(var3.hasNext()) {
        BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
        GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Creating MapperFactoryBean with name `" + holder.getBeanName() + "` and `" + definition.getBeanClassName() + "` mapperInterface");
        }

        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
        definition.setBeanClass(this.mapperFactoryBean.getClass());
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
        boolean explicitFactoryUsed = false;
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
        } else if (this.sqlSessionFactory != null) {
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
        }

        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            if (explicitFactoryUsed) {
                this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }

            definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
            if (explicitFactoryUsed) {
                this.logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }

            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Enabling autowire by type for MapperFactoryBean with name `" + holder.getBeanName() + "`.");
            }

            definition.setAutowireMode(2);
        }
    }

}

這裡我們注意到有這麼一行程式碼:definition.setBeanClass(this.mapperFactoryBean.getClass()),看到這裡我們就可以知道為什麼spring在載入初始化我們的mapper介面物件會初始化成MapperFactoryBean物件了

好了,到這裡我們也就明白了spring是如何幫我們載入註冊我們的mapper介面對應的實現類了。對於程式碼裡涉及到的其他細節,這裡暫時不作過多講解,還是老套路,只講解總體思路。

c.基於註解配置mybatis是如何整合進spring的

基於註解形式的配置其實就是將xml配置對應到註解中來,本質上的流程還是一樣的。所以這裡我就簡單講講。我們先看看MapperScannerRegistrar這個類,因為這個類是spring構建MapperFactoryBean的核心類。

//MapperScannerRegistrar
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private ResourceLoader resourceLoader;

    public MapperScannerRegistrar() {
    }
    ...
}

這裡我們注意到MapperScannerRegistrar實現了ImportBeanDefinitionRegistrar介面,在前面的敘述中我們已經知道了實現ImportBeanDefinitionRegistrar介面的作用是什麼了,所以我們直接看看這裡具體做了什麼操作。

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    if (this.resourceLoader != null) {
        scanner.setResourceLoader(this.resourceLoader);
    }

    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
        scanner.setAnnotationClass(annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
        scanner.setMarkerInterface(markerInterface);
    }

    Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
        scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
        scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
    List<String> basePackages = new ArrayList();
    String[] var10 = annoAttrs.getStringArray("value");
    int var11 = var10.length;

    int var12;
    String pkg;
    for(var12 = 0; var12 < var11; ++var12) {
        pkg = var10[var12];
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }

    var10 = annoAttrs.getStringArray("basePackages");
    var11 = var10.length;

    for(var12 = 0; var12 < var11; ++var12) {
        pkg = var10[var12];
        if (StringUtils.hasText(pkg)) {
            basePackages.add(pkg);
        }
    }

    Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
    var11 = var14.length;

    for(var12 = 0; var12 < var11; ++var12) {
        Class<?> clazz = var14[var12];
        basePackages.add(ClassUtils.getPackageName(clazz));
    }

    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
}

通過觀察我們看到最後還是呼叫了ClassPathMapperScanner的doScan去掃描指定包下的mapper介面(持久層),然後構建對應的beanDefinition類。前面我們知道是通過MapperScan這個註解去指定包的,然後我們也可以看到,在這個方法一開始就取出這個註解的值,然後進行接下來的操作的。

 AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));

之後的過程其實跟xml形式配置的一樣了。

後序

好啦,這篇沒想囉理八嗦說了那麼多,可能有好多小夥伴看到最後也是懵逼狀態,這裡有個建議,開啟IDE,邊看邊對著程式碼跟蹤,如果哪裡覺得不對,可以直接debug。

這裡給大家提個看原始碼的建議,就是猜想+驗證。先猜想自己的想法,然後通過查詢相關問題或者debug程式碼去驗證自己的思路。

好啦,到這裡為止,mybatis和spring-mybatis的基本原理都跟大家說了一遍,不知道小夥伴們有沒有收穫呢,下一篇,我會帶大家手寫一遍mybatis,是純手寫而且還能跑起來的那種哦!

:本人不才,以上如有錯誤的地方或者不規範的敘述還望各位小夥伴批評指點。

相關文章