Spring Framework自動裝配setAutowireMode和Mybatis案例的源碼探究


  由前文可得知, Spring Framework的自動裝配有兩種方式:xml配置和注解配置;

  自動裝配的類型有:

  (1)xml配置中的byType根據類型查找(@Autowired注解是默認根據類型查找,類型查找不到會使用名稱查找);

  (2)xml配置中的byName根據名稱查找,它是xml配置中根據setter方法來查找(@Resource注解也是根據名稱查找,但它是根據屬性名稱來查找,跟setter方法無關,所以setter方法可以不用寫,可以使用type指定類);

  如果沒有上面的在xml中進行配置和使用了@Autowired、@Resource等注解,那如何判斷一個類中的屬性要不要自動裝配和會不會自動裝配?

  如果不會自動裝配,除了使用上面的xml配置和注解方式外,還有其他方式可以導致屬性自動裝配嗎?

  自動裝配有類型限制嗎?比如哪些類型無法自動裝配?

  以下面的案例進行探討:

@Component
public class Userclazz {
}

@Component
public class UserService {

    private Userclazz userclazz;

    public Userclazz getUserclazz() {
        return userclazz;
    }

    public void setUserclazz(Userclazz userclazz) {
        this.userclazz = userclazz;
    }
}

   測試:

BeanFactory beanFactory  = new AnnotationConfigApplicationContext(Config.class);
System.out.println(beanFactory.getBean(UserService.class).getUserclazz());
//=====結果======
null

   從上面可以看到,上面的案例中無法進行自動裝配,如果我們利用 ImportBeanDefinitionRegistrar 接口的特性來修改BeanDefition,如下所示,則可以解決自動裝配的問題:

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        GenericBeanDefinition definition= (GenericBeanDefinition) registry.getBeanDefinition("userService");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        registry.registerBeanDefinition("userService",definition);
    }
}
@Configuration
@ComponentScan("com.hrh")
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyBatisConfig {}

  從上面可以看到,使用了 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); 這行代碼可以導入變量自動裝配,根據類型的自動注入 setAutowireMode 有3種模型:

  1)AUTOWIRE_NO:Spring Framework 默認,不自動裝配,但會對加了@Autowired 注解的屬性進行自動裝配;

  2)AUTOWIRE_BY_TYPE:不需要加任何注解,會根據類型進行自動裝配,它是根據當前屬性的 setter 方法來進行自動裝配,如果當前屬性有 setter 方法,會進行自動裝配,如果沒有則不會進行自動裝配;從下圖可以看出,Spring 容器在初始化時發現 UserService 有 setter 方法會調用,所以如果有屬性會自動裝配;【AUTOWIRE_BY_TYPE跟setter方法有關,跟屬性沒關

   3)AUTOWIRE_BY_NAME:根據名稱進行自動裝配,也是根據當前屬性的 setter 方法來進行自動裝配;

   前文講到的 @MapperScan 注解進行掃描包解析將每個 Mapper 接口轉換成對應的 MapperFactoryBean,那它是使用上面的哪種類型呢?從下面的代碼可以看出它也是使用 AUTOWIRE_BY_TYPE 的:

//下面是掃描解析包路徑的主要邏輯:
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);
      }
    }
  }
}

   對於“自動裝配有類型限制嗎?比如哪些類型無法自動裝配?”的問題,以 MapperFactoryBean 為例來進行探討:

  從上面可以看到 MapperFactoryBean 的屬性有 mapperInterface、addToConfig,對應的 setter 方法有 setAddToConfig,所以屬性 addToConfig 可能會進行自動裝配【為什么說可能呢?因為自動裝配可能存在類型限制,有些類型可能無法進行自動裝配】; 它的父類 SqlSessionDaoSupport 有屬性 sqlSession 和 externalSqlSession,對應的 setter 方法有 setSqlSessionFactory、setSqlSessionTemplate,所以 sqlSession 和 externalSqlSession 可能會進行自動裝配,結合前文 Mybatis一級緩存和結合Spring Framework后失效的源碼探究 就可以知道,sqlSession 一定會進行自動裝配,由此才會進行 new SqlSessionTemplate,后面才會有 SqlSessionTemplate 對象的調用,至於 SqlSessionFactory 如何來的,其實我們在配置類中就已經實例化了,所以會直接從配置類中拿,SqlSessionFactoryBean 是一個 FactoryBean,所以獲取時從 getObject 方法獲取,而該方法返回的就是一個 SqlSessionFactory;

  那么為什么不直接使用 @Autowired 注解呢,其實是為了解耦,如果加了該注解,就永遠需要依賴 Spring Framework 來進行編譯,如果不用注解,可以利用 ImportBeanDefinitionRegistrar 接口特性和 setAutowireMode 的3種模型 來實現自動裝配。

   從前文Mybatis的初始化和結合Spring Framework后初始化的源碼探究可以得知當UserService在實例化過程中發現有屬性變量,會對屬性變量進行實例化,所以需要在 AbstractAutowireCapableBeanFactory#populateBean 方法處打斷點查看上面的哪些屬性會進行自動裝配:

  從上圖可以看出最開始它有一個 addToConfig 變量,但這個變量不是上面提到的 MapperFactoryBean 的 addToConfig 屬性變量,而是 @MapperScan 掃描時注入的:

  當上面的 AbstractAutowireCapableBeanFactory#populateBean 方法繼續執行后發現 pvs 發生了改變,屬性變成了兩個,從而可以判斷 autowireByType 方法就是獲取類的屬性:

  接下來查看 autowireByType 方法,從下面可以看出,它拿出了兩個屬性 sqlSessionFactory  和 sqlSessionTemplate【因為 SqlSessionDaoSupport 的 setter 方法有 setSqlSessionFactory、setSqlSessionTemplate】,為什么沒有拿出 addToConfig 屬性變量呢?因為前面已經拿出包含了:

  所以接下來查看AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties 方法是如何篩選過濾只剩下兩個的:從下面看出 pds(屬性描述器)拿出了所有的屬性,有9個,第一個是 addToConfig,是MapperFactoryBean 的屬性 addToConfig,第二個是 class ,是因為每個類的父類都是 Object 類,而 Object 類中有一個 getClass 方法,由此可以得知 pds(屬性描述器)會對類中所有的 readMethod(getXXX方法)和 writeMethod(setXXX方法)都拿出來,其中對 singleton 做了優化成 isSingleton(對Boolean類型的屬性)

  下面是展開每個屬性所獲取的對應位置:

 

 

 

 

  查看屬性過濾的詳細代碼:pd.getWriteMethod() != null 從中可以看到如果 writeMethod 是空的會過濾掉,所以上面的屬性 class、object、objectType、singleton 都過濾掉了【所以對應了前面的 AUTOWIRE_BY_TYPE 模型只關心 setter方法】;isExcludedFromDependencyCheck 設置一些接口不進行自動裝配(前面沒有進行設置,所以可以忽略);pvs.contains 由於前面 pvs 已經包含了 addToConfig ,所以 MapperFactoryBean 的屬性 addToConfig 不會自動裝配;BeanUtils.isSimpleProperty 是對一些簡單類型進行過濾

    protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
        Set<String> result = new TreeSet<String>();
        PropertyValues pvs = mbd.getPropertyValues();
        //獲取所有屬性
        PropertyDescriptor[] pds = bw.getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            //進行屬性過濾
            if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
                    !BeanUtils.isSimpleProperty(pd.getPropertyType())) {
                result.add(pd.getName());
            }
        }
        return StringUtils.toStringArray(result);
    }

  下面是 BeanUtils.isSimpleProperty 的過濾規則:從下面的 isSimpleValueType 可以看出對於 char字符串、number、date、Class 類型等的進行過濾,由此可以得出 MapperFactoryBean 的屬性 mapperInterface 被過濾掉不會自動裝配,所以最后只剩下 sqlSessionFactory  和 sqlSessionTemplate 兩個會自動裝配,即 SqlSessionDaoSupport 的屬性 sqlSession 和 externalSqlSession 會自動裝配;

    public static boolean isSimpleProperty(Class<?> clazz) {
        Assert.notNull(clazz, "Class must not be null");
        return isSimpleValueType(clazz) || (clazz.isArray() && isSimpleValueType(clazz.getComponentType()));
    }
    public static boolean isSimpleValueType(Class<?> clazz) {
        return (ClassUtils.isPrimitiveOrWrapper(clazz) ||
                Enum.class.isAssignableFrom(clazz) ||
                CharSequence.class.isAssignableFrom(clazz) ||
                Number.class.isAssignableFrom(clazz) ||
                Date.class.isAssignableFrom(clazz) ||
                URI.class == clazz || URL.class == clazz ||
                Locale.class == clazz || Class.class == clazz);
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM