探究Spring原理

ML李嘉圖發表於2022-03-06

探究Spring原理

探究IoC原理

首先我們大致瞭解一下ApplicationContext的載入流程:

img

我們可以看到,整個過程極為複雜,一句話肯定是無法解釋的,所以我們就從ApplicationContext說起吧。

由於Spring的原始碼極為複雜,因此我們不可能再像瞭解其他框架那樣直接自底向上逐行幹原始碼了(可以自己點開看看,程式碼量非常之多),我們可以先從一些最基本的介面定義開始講起,自頂向下逐步瓦解,那麼我們來看看ApplicationContext最頂層介面是什麼,一直往上,我們會發現有一個AbstractApplicationContext類,我們直接右鍵生成一個UML類圖。

我們發現最頂層實際上是一個BeanFactory介面,那麼我們就從這個介面開始研究起。

我們可以看到此介面中定義了很多的行為:

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";

    Object getBean(String var1) throws BeansException;

    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);

    boolean containsBean(String var1);

    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;

    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;

    boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1) throws NoSuchBeanDefinitionException;

    @Nullable
    Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;

    String[] getAliases(String var1);
}

我們發現,其中最眼熟的就是getBean()方法了,此方法被過載了很多次,可以接受多種引數。

因此,我們可以斷定,一個IoC容器最基本的行為在此介面中已經被定義好了,也就是說,所有的BeanFactory實現類都應該具備容器管理Bean的基本能力,就像它的名字一樣,它就是一個Bean工廠,工廠就是用來生產Bean例項物件的。

我們可以直接找到此介面的一個抽象實現AbstractBeanFactory類,它實現了getBean()方法:

public Object getBean(String name) throws BeansException {
    return this.doGetBean(name, (Class)null, (Object[])null, false);
}

那麼我們doGetBean()接著來看方法裡面幹了什麼:

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = this.transformedBeanName(name);
    Object sharedInstance = this.getSingleton(beanName);
    Object beanInstance;
    if (sharedInstance != null && args == null) {
      ...

因為所有的Bean預設都是單例模式,物件只會存在一個,因此它會先呼叫父類的getSingleton()方法來直接獲取單例物件,如果有的話,就可以直接拿到Bean的例項。如果沒有會進入else程式碼塊,我們接著來看,首先會進行一個判斷:

if (this.isPrototypeCurrentlyInCreation(beanName)) {
    throw new BeanCurrentlyInCreationException(beanName);
}

這是為了解決迴圈依賴進行的處理,比如A和B都是以原型模式進行建立,而A中需要注入B,B中需要注入A,這時就會出現A還未建立完成,就需要B,而B這時也沒建立完成,因為B需要A,而A等著B,這樣就只能無限迴圈下去了,所以就出現了迴圈依賴的問題(同理,一個物件,多個物件也會出現這種情況)但是在單例模式下,由於每個Bean只會建立一個例項,Spring完全有機會處理好迴圈依賴的問題,只需要一個正確的賦值操作實現迴圈即可。那麼單例模式下是如何解決迴圈依賴問題的呢?

img

我們回到getSingleton()方法中,單例模式是可以自動解決迴圈依賴問題的:

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
  	//先從第一層列表中拿Bean例項,拿到直接返回
    if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
      	//第一層拿不到,並且已經認定為處於迴圈狀態,看看第二層有沒有
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized(this.singletonObjects) {
              	//加鎖再執行一次上述流程
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                      	//仍然沒有獲取到例項,只能從singletonFactory中獲取了
                        ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            singletonObject = singletonFactory.getObject();
                          	//丟進earlySingletonObjects中,下次就可以直接在第二層拿到了
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }

    return singletonObject;
}

看起來很複雜,實際上它使用了三層列表的方式來處理迴圈依賴的問題。包括:

  • singletonObjects
  • earlySingletonObjects
  • singletonFactories

當第一層拿不到時,會接著判斷這個Bean是否處於建立狀態isSingletonCurrentlyInCreation(),它會從一個Set集合中查詢,這個集合中儲存了已經建立但還未注入屬性的例項物件,也就是說處於正在建立狀態,如果說發現此Bean處於正在建立狀態(一定是因為某個Bean需要注入這個Bean的例項),就可以斷定它應該是出現了迴圈依賴的情況。

earlySingletonObjects相當於是專門處理迴圈依賴的表,一般包含singletonObjects中的全部例項,如果這個裡面還是沒有,接著往下走,這時會從singletonFactories中獲取(所有的Bean初始化完成之後都會被丟進singletonFactories,也就是隻建立了,但是還沒進行依賴注入的時候)在獲取到後,向earlySingletonObjects中丟入此Bean的例項,並將例項從singletonFactories中移除。

我們最後再來梳理一下流程,還是用我們剛才的例子,A依賴於B,B依賴於A:

  1. 假如A先載入,那麼A首先進入了singletonFactories中,注意這時還沒進行依賴注入,A中的B還是null
    • singletonFactories:A
    • earlySingletonObjects:
    • singletonObjects:
  2. 接著肯定是注入A的依賴B了,但是B還沒初始化,因此現在先把B給載入了,B構造完成後也進了singletonFactories
    • singletonFactories:A,B
    • earlySingletonObjects:
    • singletonObjects:
  3. 開始為B注入依賴,發現B依賴於A,這時又得去獲取A的例項,根據上面的分析,這時候A還在singletonFactories中,那麼它會被丟進earlySingletonObjects,然後從singletonFactories中移除,然後返回A的例項(注意此時A中的B依賴還是null)
    • singletonFactories:B
    • earlySingletonObjects:A
    • singletonObjects:
  4. 這時B已經完成依賴注入了,因此可以直接丟進singletonObjects中
    • singletonFactories:
    • earlySingletonObjects:A
    • singletonObjects:B
  5. 然後再將B注入到A中,完成A的依賴注入,A也被丟進singletonObjects中,至此迴圈依賴完成,A和B完成例項建立
    • singletonFactories:
    • earlySingletonObjects:
    • singletonObjects:A,B

經過整體過程梳理,關於Spring如何解決單例模式的迴圈依賴理解起來就非常簡單了。

現在讓我們回到之前的地方,原型模式下如果出現迴圈依賴會直接丟擲異常,如果不存在會接著向下:

//BeanFactory存在父子關係
BeanFactory parentBeanFactory = this.getParentBeanFactory();
//如果存在父BeanFactory,同時當前BeanFactory沒有這個Bean的定義
if (parentBeanFactory != null && !this.containsBeanDefinition(beanName)) {
  	//這裡是因為Bean可能有別名,找最原始的那個名稱
    String nameToLookup = this.originalBeanName(name);
    if (parentBeanFactory instanceof AbstractBeanFactory) {
      	//向父BeanFactory遞迴查詢
        return ((AbstractBeanFactory)parentBeanFactory).doGetBean(nameToLookup, requiredType, args, typeCheckOnly);
    }

    if (args != null) {
      	//根據引數查詢
        return parentBeanFactory.getBean(nameToLookup, args);
    }

    if (requiredType != null) {
      	//根據型別查詢
        return parentBeanFactory.getBean(nameToLookup, requiredType);
    }

  	//各種找
    return parentBeanFactory.getBean(nameToLookup);
}

也就是說,BeanFactory會先看當前是否存在Bean的定義,如果沒有會直接用父BeanFactory各種找。這裡出現了一個新的介面BeanDefinition,既然工廠需要生產商品,那麼肯定需要拿到商品的原材料以及製作配方,我們的Bean也是這樣,Bean工廠需要拿到Bean的資訊才可以去生成這個Bean的例項物件,而BeanDefinition就是用於存放Bean的資訊的,所有的Bean資訊正是從XML配置檔案讀取或是註解掃描後得到的。

我們接著來看,如果此BeanFactory不存在父BeanFactory或是包含了Bean的定義,那麼會接著往下走,這時只能自己建立Bean了,首先會拿到一個RootBeanDefinition物件:

try {
    if (requiredType != null) {
        beanCreation.tag("beanType", requiredType::toString);
    }

    RootBeanDefinition mbd = this.getMergedLocalBeanDefinition(beanName);

下面的內容就非常複雜了,但是我們可以知道,它一定是根據對應的型別(單例、原型)進行了對應的處理,最後自行建立一個新的物件返回。一個Bean的載入流程為:

首先拿到BeanDefinition定義,選擇對應的構造方法,通過反射進行例項化,然後進行屬性填充(依賴注入),完成之後再呼叫初始化方法(init-method),最後如果存在AOP,則會生成一個代理物件,最後返回的才是我們真正得到的Bean物件。

最後讓我們回到ApplicationContext中,實際上,它就是一個強化版的BeanFactory,在最基本的Bean管理基礎上,還新增了:

  • 國際化(MessageSource)
  • 訪問資源,如URL和檔案(ResourceLoader)
  • 載入多個(有繼承關係)上下文
  • 訊息傳送、響應機制(ApplicationEventPublisher)
  • AOP機制

我們發現,無論是還是的構造方法中都會呼叫refresh()方法來重新整理應用程式上下文:

public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
    this();
    this.register(componentClasses);
    this.refresh();
}

此方法在講解完AOP原理之後,再進行講解。綜上,有關IoC容器的大部分原理就講解完畢了。

探究AOP原理

前面我們提到了PostProcessor,它其實是Spring提供的一種後置處理機制,它可以讓我們能夠插手Bean、BeanFactory、BeanDefinition的建立過程,相當於進行一個最終的處理,而最後得到的結果(比如Bean例項、Bean定義等)就是經過後置處理器返回的結果,它是整個載入過程的最後一步。

而AOP機制正是通過它來實現的,我們首先來認識一下第一個介面BeanPostProcessor,它相當於Bean初始化的一個後置動作,我們可以直接實現此介面:

//注意它後置處理器也要進行註冊
@Component
public class TestBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName);  //列印bean的名稱
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
}

我們發現,此介面中包括兩個方法,一個是postProcessAfterInitialization用於在Bean初始化之後進行處理,還有一個postProcessBeforeInitialization用於在Bean初始化之前進行處理,注意這裡的初始化不是建立物件,而是呼叫類的初始化方法,比如:

@Component
public class TestBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("我是之後:"+beanName);
        return bean;   //這裡返回的Bean相當於最終的結果了,我們依然能夠插手修改,這裡返回之後是什麼就是什麼了
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("我是之前:"+beanName);
        return bean;   //這裡返回的Bean會交給下一個階段,也就是初始化方法
    }
}
@Component
public class TestServiceImpl implements TestService{

    public TestServiceImpl(){
        System.out.println("我是構造方法");
    }

    @PostConstruct
    public void init(){
        System.out.println("我是初始化方法");
    }

    TestMapper mapper;

    @Autowired
    public void setMapper(TestMapper mapper) {
        System.out.println("我是依賴注入");
        this.mapper = mapper;
    }
  	
  	...

而TestServiceImpl的載入順序為:

我是構造方法
我是依賴注入
我是之前:testServiceImpl
我是初始化方法
我是之後:testServiceImpl

現在我們再來總結一下一個Bean的載入流程:

[Bean定義]首先掃描Bean,載入Bean定義 -> [依賴注入]根據Bean定義通過反射建立Bean例項 -> [依賴注入]進行依賴注入(順便解決迴圈依賴問題)-> [初始化Bean]BeanPostProcessor的初始化之前方法 -> [初始化Bean]Bean初始化方法 -> [初始化Bean]BeanPostProcessor的初始化之前後方法 -> [完成]最終得到的Bean載入完成的例項

利用這種機制,理解Aop的實現過程就非常簡單了,AOP實際上也是通過這種機制實現的,它的實現類是AnnotationAwareAspectJAutoProxyCreator,而它就是在最後對Bean進行了代理,因此最後我們得到的結果實際上就是一個動態代理的物件(有關詳細實現過程,這裡就不進行列舉了,感興趣的可以繼續深入)

那麼肯定有人有疑問了,這個類沒有被註冊啊,那按理說它不應該參與到Bean的初始化流程中的,為什麼它直接就被載入了呢?

還記得@EnableAspectJAutoProxy嗎?我們來看看它是如何定義就知道了:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;

    boolean exposeProxy() default false;
}

我們發現它使用了@Import來註冊AspectJAutoProxyRegistrar,那麼這個類又是什麼呢,我們接著來看:

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }

    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      	//註冊AnnotationAwareAspectJAutoProxyCreator到容器中
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }

            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }

    }
}

它實現了介面,這個介面也是Spring提供的一種Bean載入機制,它支援直接向容器中新增Bean定義,容器也會載入這個Bean:

  • ImportBeanDefinitionRegistrar類只能通過其他類@Import的方式來載入,通常是啟動類或配置類。
  • 使用@Import,如果括號中的類是ImportBeanDefinitionRegistrar的實現類,則會呼叫介面中方法(一般用於註冊Bean)
  • 實現該介面的類擁有註冊bean的能力。

我們可以看到此介面提供了一個BeanDefinitionRegistry正是用於註冊Bean的定義的。

因此,當我們打上了@EnableAspectJAutoProxy註解之後,首先會通過@Import載入AspectJAutoProxyRegistrar,然後呼叫其registerBeanDefinitions方法,然後使用工具類註冊AnnotationAwareAspectJAutoProxyCreator到容器中,這樣在每個Bean建立之後,如果需要使用AOP,那麼就會通過AOP的後置處理器進行處理,最後返回一個代理物件。

我們也可以嘗試編寫一個自己的ImportBeanDefinitionRegistrar實現,首先編寫一個測試Bean:

public class TestBean {
    
    @PostConstruct
    void init(){
        System.out.println("我被初始化了!");
    }
}
public class TestBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(Student.class).getBeanDefinition();
        registry.registerBeanDefinition("lbwnb", definition);
    }
}

觀察控制檯輸出,成功載入Bean例項。

BeanPostProcessor差不多的還有BeanFactoryPostProcessor,它和前者一樣,也是用於我們自己處理後置動作的,不過這裡是用於處理BeanFactory載入的後置動作,BeanDefinitionRegistryPostProcessor直接繼承自BeanFactoryPostProcessor,並且還新增了新的動作postProcessBeanDefinitionRegistry,你可以在這裡動態新增Bean定義或是修改已經存在的Bean定義,這裡我們就直接演示BeanDefinitionRegistryPostProcessor的實現:

@Component
public class TestDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println("我是Bean定義後置處理!");
        BeanDefinition definition = BeanDefinitionBuilder.rootBeanDefinition(TestBean.class).getBeanDefinition();
        registry.registerBeanDefinition("lbwnb", definition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        System.out.println("我是Bean工廠後置處理!");
    }
}

在這裡註冊Bean定義其實和之前那種方法效果一樣。

最後,我們再完善一下Bean載入流程(加粗部分是新增的):

[Bean定義]首先掃描Bean,載入Bean定義 -> [Bean定義]Bean定義和Bean工廠後置處理 -> [依賴注入]根據Bean定義通過反射建立Bean例項 -> [依賴注入]進行依賴注入(順便解決迴圈依賴問題)-> [初始化Bean]BeanPostProcessor的初始化之前方法 -> [初始化Bean]Bean初始化方法 -> [初始化Bean]BeanPostProcessor的初始化之前後方法 -> [完成]最終得到的Bean載入完成的例項

最後我們再來研究一下ApplicationContext中的refresh()方法:

public void refresh() throws BeansException, IllegalStateException {
    synchronized(this.startupShutdownMonitor) {
        StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
        this.prepareRefresh();
        ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
      	//初始化Bean工廠
        this.prepareBeanFactory(beanFactory);

        try {
            this.postProcessBeanFactory(beanFactory);
            StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
          	//呼叫所有的Bean工廠、Bean註冊後置處理器
            this.invokeBeanFactoryPostProcessors(beanFactory);
          	//註冊Bean後置處理器(包括Spring內部的)
            this.registerBeanPostProcessors(beanFactory);
            beanPostProcess.end();
          	//國際化支援
            this.initMessageSource();
          	//監聽和事件廣播
            this.initApplicationEventMulticaster();
            this.onRefresh();
            this.registerListeners();
          	//例項化所有的Bean
            this.finishBeanFactoryInitialization(beanFactory);
            this.finishRefresh();
        } catch (BeansException var10) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
            }

            this.destroyBeans();
            this.cancelRefresh(var10);
            throw var10;
        } finally {
            this.resetCommonCaches();
            contextRefresh.end();
        }

    }
}

我們可以給這些部分分別打上斷點來觀察一下此方法的整體載入流程。

Mybatis整合原理

通過之前的瞭解,我們再來看Mybatis的@MapperScan是如何實現的,現在理解起來就非常簡單了。

我們可以直接開啟檢視:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})
@Repeatable(MapperScans.class)
public @interface MapperScan {
    String[] value() default {};

    String[] basePackages() default {};
  	...

我們發現,和Aop一樣,它也是通過Registrar機制,通過@Import來進行Bean的註冊,我們來看看MapperScannerRegistrar是個什麼東西,關鍵程式碼如下:

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
        builder.addPropertyValue("annotationClass", annotationClass);
    }

    Class<?> markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
        builder.addPropertyValue("markerInterface", markerInterface);
    }

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

    Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
        builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    }

    String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
    if (StringUtils.hasText(sqlSessionTemplateRef)) {
        builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
    }

    String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
    if (StringUtils.hasText(sqlSessionFactoryRef)) {
        builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
    }

    List<String> basePackages = new ArrayList();
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText).collect(Collectors.toList()));
    basePackages.addAll((Collection)Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName).collect(Collectors.toList()));
    if (basePackages.isEmpty()) {
        basePackages.add(getDefaultBasePackage(annoMeta));
    }

    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    if (StringUtils.hasText(lazyInitialization)) {
        builder.addPropertyValue("lazyInitialization", lazyInitialization);
    }

    String defaultScope = annoAttrs.getString("defaultScope");
    if (!"".equals(defaultScope)) {
        builder.addPropertyValue("defaultScope", defaultScope);
    }

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

雖然很長很多,但是這些程式碼都是在新增一些Bean定義的屬性,而最關鍵的則是最上方的MapperScannerConfigurer,Mybatis將其Bean資訊註冊到了容器中,那麼這個類又是幹嘛的呢?

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    private String basePackage;

它實現了BeanDefinitionRegistryPostProcessor,也就是說它為Bean資訊載入提供了後置處理,我們接著來看看它在Bean資訊後置處理中做了什麼:

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

  	//初始化類路徑Mapper掃描器,它相當於是一個工具類,可以快速掃描出整個包下的類定義資訊
  	//ClassPathMapperScanner是Mybatis自己實現的一個掃描器,修改了一些掃描規則
    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(this.lazyInitialization)) {
        scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
    }

    if (StringUtils.hasText(this.defaultScope)) {
        scanner.setDefaultScope(this.defaultScope);
    }

  	//新增過濾器,這裡會配置為所有的介面都能被掃描(因此即使你不新增@Mapper註解都能夠被掃描並載入)
    scanner.registerFilters();
  	//開始掃描
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}

開始掃描後,會呼叫doScan()方法,我們接著來看(這是ClassPathMapperScanner中的掃描方法):

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  	//首先從包中掃描所有的Bean定義
    if (beanDefinitions.isEmpty()) {
        LOGGER.warn(() -> {
            return "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.";
        });
    } else {
      	//處理所有的Bean定義,實際上就是生成對應Mapper的代理物件,並註冊到容器中
        this.processBeanDefinitions(beanDefinitions);
    }

  	//最後返回所有的Bean定義集合
    return beanDefinitions;
}

通過斷點我們發現,最後處理得到的Bean定義發現此Bean是一個MapperFactoryBean,它不同於普通的Bean,FactoryBean相當於為普通的Bean新增了一層外殼,它並不是依靠Spring直接通過反射建立,而是使用介面中的方法:

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    @Nullable
    T getObject() throws Exception;

    @Nullable
    Class<?> getObjectType();

    default boolean isSingleton() {
        return true;
    }
}

通過getObject()方法,就可以獲取到Bean的例項了。

注意這裡一定要區分FactoryBean和BeanFactory的概念:

  • BeanFactory是個Factory,也就是 IOC 容器或物件工廠,所有的 Bean 都是由 BeanFactory( 也就是 IOC 容器 ) 來進行管理。
  • FactoryBean是一個能生產或者修飾生成物件的工廠Bean(本質上也是一個Bean),可以在BeanFactory(IOC容器)中被管理,所以它並不是一個簡單的Bean。當使用容器中factory bean的時候,該容器不會返回factory bean本身,而是返回其生成的物件。要想獲取FactoryBean的實現類本身,得在getBean(String BeanName)中的BeanName之前加上&,寫成getBean(String &BeanName)。

我們也可以自己編寫一個實現:

@Component("test")
public class TestFb implements FactoryBean<Student> {
    @Override
    public Student getObject() throws Exception {
        System.out.println("獲取了學生");
        return new Student();
    }

    @Override
    public Class<?> getObjectType() {
        return Student.class;
    }
}
public static void main(String[] args) {
    log.info("專案正在啟動...");
    ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class);
    System.out.println(context.getBean("&test"));   //得到FactoryBean本身(得加個&搞得像C語言指標一樣)
    System.out.println(context.getBean("test"));   //得到FactoryBean呼叫getObject()之後的結果
}

因此,實際上我們的Mapper最終就以FactoryBean的形式,被註冊到容器中進行載入了:

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

這樣,整個Mybatis的@MapperScan的原理就全部解釋完畢了。

在瞭解完了Spring的底層原理之後,我們其實已經完全可以根據這些實現原理來手寫一個Spring框架了。

相關文章