mybatis-spring整合源碼解析

Mybatis對Spring的整合實現(xiàn)

本文只討論整合Spring,Mybatis是如何整合到Spring生態(tài)中的

接口掃描的MapperScan的實現(xiàn)和擴展

@MapperScan, 元標注了@Import注解蝇刀,導入了一個MapperScannerRegistrarConfiguration Class , 申明如下

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware

我們都知道ImportBeanDefinitionRegistrar 是Spring注入Configuration Class到容器中的一種常見手段, 常見的還有@Import螟加,ImportSelector.. , 該實現(xiàn)類的核心邏輯在registerBeanDefinitions()中,如下

 //importingClassMetadata 為當前標注了@Import的Configuration Class的注解元信息
//BeanDefinitionRegistry registry 為當前BeanFacatory的引用
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   //1. 獲取@MapperScan注解的屬性信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
     // 2. 注冊一個名為 MapperScannerConfigurer的 Bean到IOC容器中
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }
    
  // 具體注冊Bean的邏輯
  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
        //1. 構造BeanDefinition
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
        //2. 設置自定義的注解熊泵,在后面自定義掃描有大用處
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }
        //3. 設置自定義的接口 Class仰迁,在后面自定義掃描有大用處
    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      builder.addPropertyValue("markerInterface", markerInterface);
    }
    
    //....省略部分PropertyValues的屬性賦值

MapperScannerConfigurer的用處以及實現(xiàn)原理

是一個BeanDefinitionRegistryPostProcessor的實現(xiàn)類,該類型會在Spring容器啟動刷新時進行回調

查看源碼發(fā)現(xiàn)其類的聲明如下

//1. 發(fā)現(xiàn)其是一個BeanDefinitionRegistryPostProcessor 顽分, 該類型接口會在IOC容器刷新的時候進行回調
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
  
//2. 能在回調方法中發(fā)現(xiàn)其核心做了兩件事情
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders(); //解析相關包名占位符
    }
        //2.1 創(chuàng)建自定義的ClassPathBeanDefinitionScanner(Spring中@ComponentScan核心處理類)并添加自定義的掃描類型
    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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
   //2.2 進行掃描獲取BeanDefinition徐许,并注冊到容器中
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

接下來我們來看ClassPathMapperScanner組件的用處 , 他其實是擴展了spring的@ComponentScan的組件掃描方式卒蘸,核心看registerFilters()方法雌隅,里面添加了要掃描的TypeFilter的方式

  public void registerFilters() {
    boolean acceptAllInterfaces = true; //1. 這個標志位是否要掃描包下所有的接口
        
        //2. 這里的annotationClass是前面注冊MapperScannerConfigurer時傳遞進來的自定義注解屬性
    if (this.annotationClass != null) {
        // 2.1 這里添加IncludeFilter表示,要添加一個允許的掃描注解缸沃,只要標注了該注解就會被ClassLoader掃描到
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

        //3. 這里的annotationClass是前面注冊MapperScannerConfigurer時傳遞進來的自定義接口Class
    if (this.markerInterface != null) {
      //3.1 掃描自定義的接口類型舔箭,并且
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          //不能實現(xiàn)類Class,只能是抽象接口或者抽象類
          return false;
        }
      });
      acceptAllInterfaces = false;
    }
        //4. 如果沒有自定義注解或者自定義接口掃描仆潮,那么添加一個TypeFilter默認全部掃描所有
    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
  }

掃描時如何根據IncludeFilter/ExcludeFilter進行掃描和過濾傀广?核心方法調用鏈如下

//調用鏈
//ClassPathMapperScanner#doScan() -> ClassPathBeanDefinitionScanner#doScan() -> ClassPathScanningCandidateComponentProvider#findCandidateComponents()  -> scanCandidateComponents()
 
 //其中scanCandidateComponents()方法具體實現(xiàn)如下
  private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<>();
        try {
            //1. 獲取傳遞進來的掃描包路徑
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      //2. 使用ResourceLoader加載資源
            Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
            boolean traceEnabled = logger.isTraceEnabled();
            boolean debugEnabled = logger.isDebugEnabled();
            for (Resource resource : resources) {
                if (traceEnabled) {
                    logger.trace("Scanning " + resource);
                }
                if (resource.isReadable()) {
                    try {
           //3. 使用ASM進行元信息讀取
                        MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            //4. 這里很關鍵里面會進行IncludeFilter和ExcludeFilter的判斷,也是能自定義擴展組件掃描的核心方法
                        if (isCandidateComponent(metadataReader)) {
            // 5. 拼裝成BeanDefinition翘单,后面會給BeanDefinition設置beanClass為MapperFactoryBean代理對象
             //6. 最后注冊到IOC容器中吨枉,此時我們已經可以使用Mybatis的Mapper來完成依賴注入和依賴查找了
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setSource(resource);
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
                            }
    //省略部分無關源碼...

其中isCandidateComponent()實現(xiàn)如下

    protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) { //遍歷所有的ExcludeFilter蹦渣,若有匹配的則返回false不進行掃描
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return false;
            }
        }
            //遍歷所有的IncludeFilter,若匹配則進行Conditional條件注解判斷貌亭,這里includeFilters中就包括了之前
            //ClassPathMapperScanner#registerFilters()方法中注冊的includeFilters柬唯。這也是為什么我們配置了
            // @MapperScan(basePakages="xxxx")就能掃描到xxx包下的所有類到ioc容器中的所有原理
        for (TypeFilter tf : this.includeFilters) { 
            if (tf.match(metadataReader, getMetadataReaderFactory())) {
                return isConditionMatch(metadataReader);
            }
        }
        return false;
    }
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市圃庭,隨后出現(xiàn)的幾起案子锄奢,更是在濱河造成了極大的恐慌,老刑警劉巖剧腻,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拘央,死亡現(xiàn)場離奇詭異,居然都是意外死亡恕酸,警方通過查閱死者的電腦和手機堪滨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蕊温,“玉大人袱箱,你說我怎么就攤上這事∫迕” “怎么了发笔?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長凉翻。 經常有香客問我了讨,道長,這世上最難降的妖魔是什么制轰? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任前计,我火速辦了婚禮,結果婚禮上垃杖,老公的妹妹穿的比我還像新娘男杈。我一直安慰自己,他們只是感情好调俘,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布伶棒。 她就那樣靜靜地躺著,像睡著了一般彩库。 火紅的嫁衣襯著肌膚如雪肤无。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天骇钦,我揣著相機與錄音宛渐,去河邊找鬼。 笑死,一個胖子當著我的面吹牛窥翩,可吹牛的內容都是我干的畴蹭。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼鳍烁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了繁扎?” 一聲冷哼從身側響起幔荒,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎梳玫,沒想到半個月后爹梁,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡提澎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年姚垃,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盼忌。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡积糯,死狀恐怖,靈堂內的尸體忽然破棺而出谦纱,到底是詐尸還是另有隱情看成,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布跨嘉,位于F島的核電站川慌,受9級特大地震影響,放射性物質發(fā)生泄漏祠乃。R本人自食惡果不足惜梦重,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望亮瓷。 院中可真熱鬧琴拧,春花似錦、人聲如沸寺庄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斗塘。三九已至赢织,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間馍盟,已是汗流浹背于置。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贞岭,地道東北人八毯。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓搓侄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親话速。 傳聞我的和親對象是個殘疾皇子讶踪,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內容