Mybatis的初始化和結合Spring Framework後初始化的源碼探究

  帶着下面的問題進行學習:

  (1)Mybatis 框架或 Spring Framework 框架對數據層 Mapper 接口做了代理,那是做了 JDK 動態代理還是 CGLIB 代理?

  (2)Mapper 接口使用和不使用 @Mapper 註解有什麼區別?

  (3)Spring Framework 框架引入 Mybatis 的 jar 包後,Spring Framework 是怎麼管理的?

  (4)@MapperScan註解的作用是什麼?

  在探究上面的問題前,先了解什麼是 FactoryBean,FactoryBean 和 BeanFactory有什麼區別?

  BeanFactory 是 Spring Framework 中的一個 Bean 工廠(AnnotationConfigApplicationContext、ClassPathXmlApplicationContext等),可以產生類,它有一個 getBean 方法可以獲取到類;

  FactoryBean 是一個 Bean,受 Spring Framework 管理的一個對象,定義 Bean 有好幾種方式,比如 xml 的 <bean> 標籤,註解 @Bean、@Service 等;

  下面是案例:實現 FactoryBean 接口,重寫方法

public class TempBean {
    public void query(){
        System.out.println("TempBean");
    }
}
@Configuration("MyFactroyBean")
public class MyFactroyBean implements FactoryBean {
    public void test() {
        System.out.println("MyFactroyBean MyFactroyBeanTest");
    }

    @Override
    public Object getObject() throws Exception {
        return new TempBean();
    }

    @Override
    public Class<?> getObjectType() {
        return TempBean.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context= new AnnotationConfigApplicationContext(MyFactroyBean.class);
        MyFactroyBean myFactroyBean = (MyFactroyBean) context.getBean("&MyFactroyBean");
        myFactroyBean.test();
        TempBean tempBean = (TempBean) context.getBean("MyFactroyBean");
        tempBean.query();
    }
//=========結果======

   MyFactroyBean MyFactroyBeanTest
   TempBean

   如果你的類實現了 FactoryBean,那麼 Spring 存在兩個對象:

    一個是 getObject() 返回的對象:當前類指定的名字;
    一個是當前類:"&"+當前類指定的名字;

  當實現了 FactoryBean 後,Spring 在容器實例化過程中,會對項目中的自定義類進行實例化,當前類會被代理成一個 CGLIB 類(使用了@Configuration註解),當你獲取當前類時,傳遞“&xx”,Spring 會進行判斷,如果獲取是當前類,返回代理類對象,如果是獲取 getObject 返回的對象,會直接調用 getObject 方法中的 new xxx();

  下面是Spring容器初始化時,在執行一些後置處理器(ConfigurationClassPostProcessor是對項目的 ComponentScan 註解的路徑、@Configuration 註解的類進行掃描解析,超級重要的一個類過程中,對實現了 FactoryBean 接口的子類進行代理:

  下面是對實現了 FactoryBean 接口的子類的獲取:先判斷是不是 FactoryBean 類型,如果是加前綴“&"再進行獲取;因爲前面已經對子類進行了代理,並且存入了 beanDefinitionMap

  但即使上面加了前綴”&“,在後面還是會剔除掉,那怎麼判斷是獲取當前類還是獲取getObject方法中的類呢?在 Spring 容器過程中,只會對當前類(實現了 FactoryBean 的子類)進行實例化,當獲取時,getSingleton 直接從緩存中獲取(每個對象實例化後會放入緩存中 singletonObjects)共享對象,然後 getObjectForBeanInstance 獲取實例對象返回,在獲取實例對象過程中,會判斷是不是 FactoryBean 類型,如果是直接將代理對象返回;

  下面判斷是否是一個FactoryBean對象:

    public static boolean isFactoryDereference(@Nullable String name) {
        return (name != null && name.startsWith(BeanFactory.FACTORY_BEAN_PREFIX));//FACTORY_BEAN_PREFIX=”&"
    }

  如果是獲取 getObject() 返回的對象,跟上面一樣,但在判斷是不是一個 FactoryBean 對象時,由於name沒有“&”的前綴,所以走下面的流程,不會直接 return beanInstance

  FactroyBean是一個 Bean(可以當作一個業務類,比如連接數據庫做一些業務操作),當一個類的依賴關係很複雜時,只需要提供一個簡單的接口給外部使用時(將內部的一些關係進行封裝維護好)可以使用 FactroyBean 來實現,比如 mybatis 的 SqlSessionFactoryBean 是一個 FactoryBean 實現類,裏面的 getObject 方法有一個 afterPropertiesSet 方法,內部將很多依賴(Configuration 配置、TransactionFactory 事務工廠等)進行了處理封裝維護到了 SqlSessionFactoryBean,外部只需要傳入一個 DataSource 數據源即可,添加 MapperScan 掃描 xml 文件會自動把 xml 的信息維護到 SqlSessionFactoryBean中;

 

  那麼 SqlSessionFactoryBean 什麼時候被調用呢?可以看下idea調試的方法調用棧:

 

  

  從上面幾張圖片可以看出,當IOC容器Context進行類(Service)初始化時,發現有屬性變量(Mapper),會對屬性變量(Mapper)進行創建獲取自動注入進去,其中會獲取到Mapper封裝成的一個MapperFactoryBean,執行到 SqlSessionFactoryBean的 afterPropertiesSet 方法對Mapper接口對應的xml進行解析獲取;

  MapperFactoryBean 也是一個 FactoryBean,每個 Mapper 接口都會轉換成一個 MapperFactoryBean,所以 doCreateBean() 創建獲取時可以通過 mapperInterface(Mapper接口)獲取到對應的 MapperFactoryBean,它的值是存儲到 Configuration的 MapperRegistry變量中;

  那它是什麼時候存儲到 Configuration的 MapperRegistry變量中的呢?通過下面的圖片可以看出,是在解析完xml後放入的:

  當 Mapper 接口在 MapperFactoryBean#getObject() 時,它對Mapper接口進行了代理:MapperRegistry#getMapper()

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //獲得代理對象工廠:裏面包含Mapper接口信息
    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);
    }
  }

  下面是 MapperProxyFactory#newInstance() 的源碼:從中可以看到,Mapper 接口對應的代理對象是使用了 JDK 動態代理產生的;【解決了上文的第一個問題,其實從 Mapper 是接口也可以猜出是使用 JDK 動態實現的】

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
    protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

   其中 MapperProxy 是一個 InvocationHandler 實現類,從前文可以得知調用 Mapper.xxx方法 會進入 MapperProxy#invoke(),最後會執行 mapperMethod.execute(sqlSession, args) 進行SQL查詢返回結果;

  綜上所述:每個Mapper接口轉化成一個MapperFactoryBean,當調用Mapper接口方法執行JDBC操作時,Mapper 接口通過 MapperFactoryBean#getObject() 對Mapper接口進行了 JDK 動態代理。

  對於第三個問題“Spring Framework 框架引入 Mybatis 的 jar 包後,Spring Framework 是怎麼管理的?”

  從前文得出每個 Mapper 接口都會轉換成一個 MapperFactoryBean,而 MapperFactoryBean 繼承了 SqlSessionDaoSupport(mybatis-spring-xx.jar),SqlSessionDaoSupport 繼承了 DaoSupport(spring-tx.jar),DaoSupport 實現了 InitializingBean接口,所以Spring容器初始化會執行DaoSupport#afterPropertiesSet方法,會執行裏面的checkDaoConfig方法,MapperFactoryBean 重寫了checkDaoConfig方法,所以最後會執行MapperFactoryBean#checkDaoConfig方法:

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 {
    }
}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;
  
  @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);//對接口進行xml解析
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
}
Configuration#addMapper
  public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
  
MapperRegistry#addMapper  
  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 {
        //爲每個接口配置一個MapperProxyFactory,供後續Mapper接口進行JDK代理
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // 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();//XML解析
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

   @MapperScan 註解是導入了一個 MapperScannerRegistrar(是實現了 ImportBeanDefinitionRegistrar 接口的,作用是動態往 BeanDefinitionMap 添加 BeanDefinition),的作用是對包路徑的 Mapper 接口和對應的XML配置信息進行掃描解析,將所有 Mapper 接口的class信息掃描成對應的 BeanDefition, MapperFactoryBean 類型的 BeanDefition,同時爲這個BeanDefition提供一個有參構造方法,參數是 class,在後面Mapper接口進行實例化的 JDK 代理時可以根據這個 class 返回對應的代理對象;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  
  @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();
    scanner.doScan(StringUtils.toStringArray(basePackages));//包掃描解析
  }
}

   下面是掃描解析包路徑的主要邏輯:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  /**
   * Calls the parent search that will search and register all the candidates.
   * Then the registered objects are post processed to set them as
   * MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //將每個Mapper解析成BeanDefinitionHolder存放到set集合中
    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 {
      //對BeanDefinitionHolder集合進行處理
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

  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
      //給每個BeanDefition提供一個有參構造方法,在後面Mapper接口進行實例化的JDK代理時可以根據這個class返回對應的代理對象;
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //Mapper接口轉換成MapperFactoryBean
      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() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }
}

   綜上所述:在 Spring 容器初始化過程中,在對Mapper接口進行實例化的過程中,@MapperScan 註解主要是解析包路徑將 Mapper 接口解析成 MapperFactoryBean 的 BeanDefition,這是 Mapper 接口在實例化之前做的事情,在實例化中和之後的過程中,主要利用 Spring的InitializingBean 接口的特性(每個實現了 InitializingBean 接口的子類有一個 afterPropertiesSet 方法,在實例化過程中會執行)來實現對 Mapper 接口信息的初始化,比如 sql 語句的初始化,將這些信息緩存起來放到一個 Map 中:

   上面的是 Mybatis 和 Spring Framework 結合後的初始化流程,那麼單獨的 Mybatis 初始化流程是怎樣的呢?通過下面的案例(前文有詳細案例)進行探究:

        String resource = "mybatis-config2.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory =
                new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        PersonMapper mapper = sqlSession.getMapper(PersonMapper.class);
        mapper.list();
        mapper.list();

   通過 debug 出來的方法調用棧可以看出:通過 SqlSessionFactoryBuilder 對 Mapper 接口和 XML 配置進行解析的信息也是存儲到 Configuration 的 mappedStatements 中;

  sqlSession.getMapper() 直接從 DefaultSqlSession 中獲取,還是跟上面一樣在解析完存放到 Configuration 的 MapperRegistry 變量中,代理還是走 JDK 動態代理:

  綜上所述: Mybatis 和 Spring Framework 結合後對 Mapper 接口的初始化過程中會轉換成 MapperFactoryBean 類型,然後利用 Spring的InitializingBean 接口的特性來實現對 Mapper 接口信息的初始化,再將這些信息緩存起來放到 Configuration 的 mappedStatements 中;而Mybatis直接解析完放到 Configuration 的 mappedStatements 中;

  最後剩餘的一個問題:Mapper 接口使用和不使用 @Mapper 註解有什麼區別?

  通過查看@Mapper註解的註釋,發現這個註解只是一個標識功能,使用與不使用沒什麼區別,不會影響系統的功能;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章