天天看點

深入spring源碼 spring整合mybatis原了解析及spring擴充點分析前言開始總結

前言

最近看了一個講如何面試的視訊,裡面說到不要隻寫自己閱讀過某某源碼,要把它展現在項目中,就算自己做的項目中沒有,也可以說自己看到别人的項目中利用了某個架構的某些特性,于是我就準備自己動手試試,學習一下優秀架構的精髓,我手動整合了spring和mybatis,視圖體會mybatis的優秀之處。

開始

要開始整合spring和mybatis,自然是要先搭建一個maven的項目,我在整合spring和mybatis的同時還整合了log4j友善檢視日志,整合了阿裡的druid作為mysql的資料庫連接配接池,由于我隻是要整合spring和mybatis并找到mybatis基于spring的擴充點及整合的原理,沒有必要使用web項目,是以我隻引入了spring-context包。項目pom檔案如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ww</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.25.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.25.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>


        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

    </dependencies>

</project>
           

由于我不喜歡使用xml,是以本次搭建我是用了純注解的形式,在項目的resource目錄下,我建立了application.properties檔案

spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/testmybatis
spring.datasource.driver=com.mysql.jdbc.Driver
           

編寫配置類,擷取application.properties中配置的資料庫資訊

@Configuration
@PropertySource("classpath:application.properties")
public class PropertiesConfig {
    @Value("${spring.datasource.url}")
    public String url;
    @Value("${spring.datasource.username}")
    public String username;
    @Value("${spring.datasource.password}")
    public String password;
    @Value("${spring.datasource.driver}")
    public String driver;


    public String getUrl() {
        return url;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public String getDriver() {
        return driver;
    }
}
           

mapper檔案如下

public interface UserMapper {
    @Select("select id,name,height,weight from user where id=#{id}")
    public User selectUser(Integer id);
}
           

實體類

@Data
public class User {
    private int id;
    private String name;
    private String height;
    private String weight;
}
           

相對應的,在mysql中我建了一個資料庫,表名為user

深入spring源碼 spring整合mybatis原了解析及spring擴充點分析前言開始總結

 service類

@Service
public class UserService{

    @Autowired
    UserMapper mapper;
    public User getUser(int id) {
        //一開始log4j并沒有輸出日志,在官網上查了之後說加上這句話就可以列印日志了
        org.apache.ibatis.logging.LogFactory.useLog4JLogging();
        return mapper.selectUser(id);
    }
}
           

讓spring來啟動的主配置類

@Configuration
@ComponentScan("com.ww")
@MapperScan("com.ww.mapper")
@PropertySource("classpath:application.properties")
public class MybatisConfig {
    //這些都是mybatis-spring官網上的例子,照着改改就行
    @Bean
    public DataSource dataSource(PropertiesConfig config) {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(config.getDriver());
        dataSource.setUrl(config.getUrl());
        dataSource.setPassword(config.getPassword());
        dataSource.setUsername(config.getUsername());
        return dataSource;
    }
    //這些都是mybatis-spring官網上的例子,照着改改就行
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        return factoryBean.getObject();
    }
}
           

到這裡項目算搭建完成了,接下來我們運作項目,來觀察mybatis整合spring之後的運作過程,以及mybatis整合spring和不整合spring究竟有什麼不同

首先我們看到主配置類上有一行@MapperScan的注解,表示掃描mapper到spring容器中,把mapper交給spring管理,那麼我想知道mapper是什麼時候被spring掃描并注入的呢,我們點進這個注解,看到這個注解是一個組合注解,其中有這麼一行注解引起了我的注意

@Import(MapperScannerRegistrar.class)
           

這個注解是什麼意思呢,Import注解的意思是導入資源,那麼我們看看它導入的是個什麼資源,看類的名字,我猜想這可能是一個mapper掃描器的注冊器,于是我就點進去看看

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(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(BeanUtils.instantiateClass(generatorClass));
    }

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

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    //調用ClassPathMapperScanner中的doScan方法,來掃描mapper并組裝成beanDefinition
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}
           

可以看到這個類實作了ImportBeanDefinitionRegistrar這個接口,這就引出了spring的第一個擴充點,ImportBeanDefinitionRegistrar接口可以用來動态注冊bean,它可以支援我們自己寫的代碼封裝成BeanDefinition對象。在這裡,mybatis作為一個第三方架構因為沒有辦法像第一方元件那樣使用@Component或者@Service來表示這是一個需要注入的bean,是以隻能擴充這個接口來動态的注入bean。

我們再來看registerBeanDefinitions這個方法,這個方法的含義就是注冊bd(為了友善起見,以下beanDefinition都簡稱bd),我們看到方法最後調用了ClassPathMapperScanner的doScan()方法,我們看看ClassPathMapperScanner這個類

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    ...
}
           

這個類屬于spring-mybatis包下,繼承了spring的ClassPathBeanDefinitionScanner類

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //調用spring的doScan方法來掃描bd
    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 {
      //掃描完成後執行bd處理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
           

由于spring先執行componentScan,我把斷點打在ClassPathBeanDefinitionScanner的doScan方法中的時候,首先掃描我自己的bean的時候也會進入這個斷點,本次由于不分析componentScan,這一部分略過。跳過這個斷點,此時控制台上輸出

Registering bean definition for @Bean method com.ww.config.MybatisConfig.dataSource()
Registering bean definition for @Bean method com.ww.config.MybatisConfig.sqlSessionFactory()
           

表示我自己的bean已經注冊完成了,接下來就應該進入mapper掃描了,我把斷點打在MapperScannerRegistrar類中registerBeanDefinitions()方法的第一行,果然斷點跳了進來,一路執行下去,當執行完ClassPathBeanDefinitionScanner的doScan的方法之後,控制台輸出一句:

Identified candidate component class: file [E:\mycode\gitclone\mybatis-spring\target\classes\com\ww\mapper\UserMapper.class]
           

确認了候選的元件類,也就是說明spring已經掃描到mapper了,同時spring已經注冊了bd,接下來再執行,則會進入processBeanDefinitions()方法

//處理注冊好的bd
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //bd中加入mapperFactoryBean類,由此可見一個mapper對應的beanClass就是mapperFactoryBean,這是mybatis核心類之一,會詳細說
      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) {
          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) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        //自動注入類型為byType
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
           

等這個方法處理完成之後,整個bd算是建立完了,接下來mybatis就開始初始化的過程了,首先我們來看之前被添加進bd中的MapperFactoryBean類

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

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

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

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

  /**
   * {@inheritDoc}
   */
  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public boolean isSingleton() {
    return true;
  }

  //------------- mutators --------------

  /**
   * Sets the mapper interface of the MyBatis mapper
   *
   * @param mapperInterface class of the interface
   */
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * Return the mapper interface of the MyBatis mapper
   *
   * @return class of the interface
   */
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  /**
   * If addToConfig is false the mapper will not be added to MyBatis. This means
   * it must have been included in mybatis-config.xml.
   * <p/>
   * If it is true, the mapper will be added to MyBatis in the case it is not already
   * registered.
   * <p/>
   * By default addToCofig is true.
   *
   * @param addToConfig
   */
  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  /**
   * Return the flag for addition into MyBatis config.
   *
   * @return true if the mapper will be added to MyBatis in the case it is not already
   * registered.
   */
  public boolean isAddToConfig() {
    return addToConfig;
  }
}
           

MapperFactoryBean擴充了Spring的FactoryBean接口,FactoryBean作為Spring的擴充點,FactoryBean的功能是可以讓我們自定義Bean的建立過程

//傳回的對象執行個體
    T getObject() throws Exception;
    //Bean的類型
    Class<?> getObjectType();
    //true是單例,false是非單例  在Spring5.0中此方法利用了JDK1.8的新特性變成了default方法,傳回true
    boolean isSingleton();
           

同時MapperFactory還繼承了SqlSessionDaoSupport類,SqlSessionDaoSupport類又繼承了Spring的DaoSupport類,利用了Spring中InitializingBean這個擴充點,在屬性設定之後對bean進行操作,我們來看SqlSessionDaoSupport類和DaoSupport類都是做什麼的

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  //設定sqlSessionFactory,由于mapperScan最後執行了definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE),是以這個set方法會自動注入進spring容器中
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }
  //設定sqlSessionTemplate,spring-mybatis的核心類之一,替代了普通mybatis中的DefaultSqlSession類,這個類控制sqlSession,包含一個内部類用來執行動态代理,同時sqlSessionTemplate類線程安全,可以在spring中作為單例bean使用
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }

  /**
   * Users should use this method to get a SqlSession to call its statement methods
   * This is SqlSession is managed by spring. Users should not commit/rollback/close it
   * because it will be automatically done.
   *
   * @return Spring managed thread safe SqlSession
   */
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  }

}

//實作InitializingBean接口,會執行afterPropertiesSet()方法,在mybatis整合了spring後會執行MapperFactoryBean類中的checkDaoConfig()方法
public abstract class DaoSupport implements InitializingBean {

	/** Logger available to subclasses */
	protected final Log logger = LogFactory.getLog(getClass());


	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

	/**
	 * Abstract subclasses must override this to check their configuration.
	 * <p>Implementors should be marked as {@code final} if concrete subclasses
	 * are not supposed to override this template method themselves.
	 * @throws IllegalArgumentException in case of illegal configuration
	 */
	protected abstract void checkDaoConfig() throws IllegalArgumentException;

	/**
	 * Concrete subclasses can override this for custom initialization behavior.
	 * Gets called after population of this instance's bean properties.
	 * @throws Exception if DAO initialization fails
	 * (will be rethrown as a BeanInitializationException)
	 * @see org.springframework.beans.factory.BeanInitializationException
	 */
	protected void initDao() throws Exception {
	}

}
           

把斷點打在MapperFactoryBean類中的checkDaoConfig()方法上,繼續執行,我們看到執行了configuration.addMapper(this.mapperInterface),點進去,我們看到跳入了org.apache.ibatis.session.Configuration類中的addMapper方法

//mapper注冊器,添加和擷取mapper的實際類
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
           

 于是乎再進入一層

//mapper被加入到MapperProxyFactory類中
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        //加入到map中後立即解析mapper中的注解,目的是拿到mapper中的注解sql語句
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }


public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

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

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  //動态代理回調方法
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  //mapper代理類的執行個體化方法
  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
           

繼續往下執行,同時注意控制台的輸出情況,看到控制台輸出一句Finished creating instance of bean 'userMapper',此時mapper執行個體化完成,既然執行個體化完成了,那麼就要傳回執行個體化好的mapper,由于bd中放的beanClass是mapperFactoryBean,是以mapper執行個體要從mapperFactoryBean的getObject方法中來獲得,來看代碼

//org.mybatis.spring.mapper.MapperFactoryBean#getObject
@Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }


//org.mybatis.spring.SqlSessionTemplate#getMapper
//sqlSessionTemplate作為sqlSession
 @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

//org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

//org.apache.ibatis.binding.MapperRegistry#getMapper
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 {
      //之前說過,這裡傳回的是個代理的對象,也就是經過動态代理的mapper
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
           

我們觀察變量的變化,發現最終傳回的object被MapperProxy所代理,到此為止,mapper已經被spring所管理,spring将通過動态代理技術使用代理類來執行mapper中的各種操作。

深入spring源碼 spring整合mybatis原了解析及spring擴充點分析前言開始總結

總結

經過自己動手用純注解的方式整合spring和mybatis,我們看到了mybatis基于spring做的許多擴充,同時也看到了spring的很多擴充點,比如ImportBeanDefinitionRegistrar、InitializingBean、FactoryBean,下面總結一下mybatis整合spring過程中所用到的幾個關鍵的類和這些類的基本功能

  • ClassPathMapperScanner:繼承Spring的ClassPathBeanDefinitionScanner,作用是在classPath中掃描mapper以及掃描之後處理beanDefinition,mapperFactoryBean在這個類中的processBeanDefinitions方法中被加入beanDefinition
  • MapperFactoryBean:繼承mybatis-spring的SqlSessionDaoSupport,實作了Spring的FactoryBean接口,作用是自定義bean,同時負責在mybatis的初始化結束之後添加mapper以及擷取mapper的代理對象
  • MapperRegistry:mapper注冊器,添加和擷取mapper的實際類
  • MapperProxy:mapper代理類,調用invoke方法來執行代理方法代理實際mapper中的操作及緩存mapper中的method
  • MapperMethod:mapper方法類,包含目标方法對象和sql指令對象,主要作用是執行目标方法的sql語句
  • SqlSessionTemplate:mybatis整合spring的核心類,這個類控制sqlSession,包含一個内部類用來執行動态代理