很好的一個 Spring MVC 解讀---

摘要: 作爲一個開發者一直有這樣一個感覺:在使用框架時,如果我不知道它做了什麼,那我可能永遠不會安心,直到完全弄明白它的源碼。如果你有與我相同的感受,那這個系列博文可能對你有很大幫助,因爲它不是教你怎麼使用Spring MVC,而是告訴你這傢伙做了什麼,你爲什麼要這樣使用它,如果不這樣做會發生麼。。。

Spring MVC 解讀---<context:component-scan/>

    註解是騎士魂牽夢繞的美麗公主,也是騎士的無法擺脫的噩夢...


一、<context:component-scan/>

    想必@Component,@Repository,@Service,@Controller幾個常用的Type-Level的Spring MVC註解,大家都很清楚他們的意思跟用途。標記爲@Component的類,在使用註解配置的情況下,系統啓動時會被自動掃描,並添加到bean工廠中去(省去了配置文件中寫bean定義了),另外三個分別表示MVC三層模式中不同層中的組件,他們都是被@Component標記的,所以也會被自動掃描。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//這裏。。。
public @interface Repository {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//這裏。。。
public @interface Service {
    String value() default "";
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component//這裏。。。
public @interface Controller {
    String value() default "";
}

    爲了達到以上效果,我們還需在xml配置文件中加入如下定義

<context:component-scan base-package="com.springrock..."/>

    這樣Spring就可以正確的處理我們定義好的組件了,重要的是這些都是自動的,你甚至不知道他是怎麼做的,做了什麼?如果不瞭解反射,可能真的感到喫驚了,但即便如此,我也想知道它到底做了什麼?什麼時候做的?

二、BeanDefinitionParser

    經過仔細的源碼閱讀,我找到了這個接口BeanDefinitionParser,文檔描述說,它是一個用來處理自定義,頂級(<beans/>的直接兒子標籤)標籤的接口抽象。可以實現它來將自定義的標籤轉化爲 BeanDefinition類。下面是它的接口定義

BeanDefinition parse(Element element, ParserContext parserContext);

    其中Element是Dom api 中的元素,ParserContext則是用來註冊轉換來的bean 工廠。

    或許你開始惱火說這麼多跟上面有什麼關係,好吧,下面便是我真正要說的,我們來看下它有哪些實現類:

    看到了吧,ComponentScanBeanDefinitionParser,正是我們想要的,他就是用來將<context:component-scan/>標籤轉化爲bean 的解析類。那他做了什麼呢?

public BeanDefinition parse(Element element, ParserContext parserContext) {
        String[] basePackages = StringUtils.tokenizeToStringArray(
                                            element.getAttribute(BASE_PACKAGE_ATTRIBUTE),
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

        // Actually scan for bean definitions and register them.
        ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
        Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
        registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

        return null;
    }

   很明顯他會獲得<component-scan/>的base-package屬性,然後解析所需解析的包路徑,然後他會創建一個ClassPathBeanDefinitionScanner對象,並委託它來執行對路徑下文件的掃描,然後將獲得的BeanDefinitions註冊到bean工廠中。是不是很清晰?

    我想你會急切的知道ClassPathBeanDefinitionScanner 做了什麼?

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
        for (String basePackage : basePackages) {
            //這裏是重點,找到候選組件
            Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                //.....
                //.....
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = 
                                            new BeanDefinitionHolder(candidate, beanName);
                    beanDefinitions.add(definitionHolder);
                    //註冊到工廠中
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }                        
        }
        return beanDefinitions;
    }

    重點是繼承自父類ClassPathScanningCandidateComponentProvider 的findCandidateComponents方法,意思就是找到候選組件,然後註冊到工廠中,那麼它是怎麼找到候選組件的呢?

我們再看看

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
        Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + "/" + this.resourcePattern;
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = this.metadataReaderFactory.
                                                                getMetadataReader(resource);
                        if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = 
                                              new ScannedGenericBeanDefinition(metadataReader);
                            if (isCandidateComponent(sbd)){
                                candidates.add(sbd);
                            }
                        }
                    }
                }
            }
        }
        return candidates;
    }

   首先獲取路徑下的資源Resource,然後判斷資源是否可讀,並且獲取可讀資源的MetadataReader對象,然後再調用isCandidateComponent(MetadataReader)判段是否是候選組件,如果是,則生成該metadataReader的ScannedGenericBeanDefinition對象。最後判斷ScannedGenericBeanDefinition是否爲候選的,如果是則添加到工廠中。

三、includeFilters,excludeFilters

    可以看到經歷了兩次篩選,才找到最終的候選Bean,我們來看第一個過濾做了什麼?

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
        for (TypeFilter tf : this.excludeFilters) {//excludeFilters 是什麼?
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                return false;
            }
        }
        for (TypeFilter tf : this.includeFilters) {//includeFilters 是什麼?
            if (tf.match(metadataReader, this.metadataReaderFactory)) {
                AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();
                if (!metadata.isAnnotated(Profile.class.getName())) {
                    return true;
                }
                AnnotationAttributes profile = MetadataUtils.attributesFor(metadata, Profile.class);
                return this.environment.acceptsProfiles(profile.getStringArray("value"));
            }
        }
        return false;
    }

    我們看到這裏有兩個實例變量excludeFilters, includeFilters,然後用他們兩個去匹配傳遞進來的MetadataReader,如果與excludeFilter匹配成功返回false, 與includeFilter匹配成功返回true。那麼這兩個filter分別是什麼呢?我們打上斷點,調試運行發現

    默認情況下includeFilters是一個含有兩個值得List,分別是@Component註解和@ManageBean註解,而excludeFilter是個空List,好吧,現在豁然開朗了吧,原來就是它來篩選我們的@Component標記的類。當然我們可以自定義這兩個filters,只需在<context:component-scan/>標籤下加兩個子標籤即可, 像這樣:

<context:component-scan base-package="com.springrock">
       <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
       <context:include-filter type="annotation" expression="com.springrock.whatever.youcustomized.annotation"/>
</context:component-scan>

四、BeanDefinitionRegistry

    上面代碼中我們看到還有一個isCandidateComponent方法,它主要是判斷當前類是否是具體的,而非抽象類和接口,以及是否是可以獨立創建的沒有依賴的?鑑於與我們目前討論的主題不相關,所以略去,感興趣的話,可以自己查看下源碼。

    好了,我們既然知道了Spring是怎樣通過<context:component-scan/>來掃描,過濾我們的組件了,但是他是怎樣將我們定義的組件收集起來供後面的請求處理呢?

    我們來看下上面doScan方法中有

//註冊到工廠中
registerBeanDefinition(definitionHolder, this.registry);

    這樣一行代碼,很明顯是將beanDefinition註冊到,registry中了。那這個registry是什麼呢?是一個BeanDefinitionRegistry,下面是它的接口定義及繼承結構:

public interface BeanDefinitionRegistry extends AliasRegistry {
    void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException;
    void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;
    boolean containsBeanDefinition(String beanName);
    String[] getBeanDefinitionNames();
    int getBeanDefinitionCount();
    boolean isBeanNameInUse(String beanName);
}

    我們可以看到接口中定義了諸多beandefinition的註冊,刪除,獲取等方法,並且Spring爲我們提供了三個內部實現,那麼運行時,使用了那個實現呢?DefaultListableBeanFactory,是的就是它。它就是SpringMVC 中管理Bean的工廠了,我們來看下,它的registerBeanDefinition是怎樣實現的?

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
        synchronized (this.beanDefinitionMap) {
            Object oldBeanDefinition = this.beanDefinitionMap.get(beanName);
            if (oldBeanDefinition != null) {
                if (!this.allowBeanDefinitionOverriding) {
                    throw new BeanDefinitionStoreException();
                }
                else {
                    if (this.logger.isInfoEnabled()) {
                        this.logger.info("Overriding bean definition '" + beanName + "]");
                    }
                }
            }
            else {
                this.beanDefinitionNames.add(beanName);
                this.frozenBeanDefinitionNames = null;
            }
            this.beanDefinitionMap.put(beanName, beanDefinition);//添加到beanDefinitionMap中了。
        }
        resetBeanDefinition(beanName);
    }

    從上面的代碼可以看出,所有的beanDefinition都由實例變量beanDefinitionMap來保存管理,他是一個ConcurrentHashMap,beanName作爲鍵,beanDefinition對象作爲值。到這我們知道了我們的bean是怎樣被註冊管理的了。但是問題又來了,我們的系統是在什麼時候讀取<context:component-scan/>標籤,並且掃描我們的bean組件的呢?

當然是從ContextLoaderListener開始了入手分析了。

五、ContextLoader

    我們查看源碼(篇幅問題,不貼代碼了,很簡答)發現ContextLoaderListener將web application context的初始化動作委託給了ContextLoader了,那ContextLoader做了什麼呢?

if (this.context == null) {
     this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
     configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context,
                                             servletContext);
}

    上面的代碼片段便是ContextLoader中initWebApplicationContext方法中的關鍵一段。首先會創建一個WebApplicationContext對象,然後configure 並且refresh這個WebApplicactionContext對象,是不是在這個configureAndRefreshWebApplicationContext方法中進行了配置文件的加載和組件的掃描呢?必須是啊。。。

wac.refresh();

    方法的最後有一個調用了wac的refresh方法,這個wac呢就是前面創建的WebApplicationContext對象,也就是我們這個Web應用的上下文對象。具體是什麼呢?我們看一下createWebapplicationContext方法

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
        Class<?> contextClass = determineContextClass(sc);//這裏是關鍵
        ConfigurableWebApplicationContext wac =
                (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
        return wac;
    }

   這個方法先確定我們context的類型,調用了determineContextClass方法,

protected Class<?> determineContextClass(ServletContext servletContext) {
        //public static final String CONTEXT_CLASS_PARAM = "contextClass";
        String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
        if (contextClassName != null) {
            try {
                return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
            }
        }
        else {//defaultStrategies 是關鍵
            contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
            try {
                return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
            }
        }
    }

   這個方法先判斷我們servletContext中有沒有contextClass這個初始化屬性(在web.xml的init-param標籤中配置),通常我們不會配置這個屬性。那肯定是null了,所以它接着去查看defaultStrategy中有沒有相應屬性,那這個defaultStrategy是什麼呢?下面是ContextLoader中一個靜態代碼塊,也就說只要ContextLoader被加載,defaultStrategy便會被賦值。

static {
        try {
            //private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
            ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH,
                                             ContextLoader.class);
            defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
        }
    }

    很明顯,系統是去ClassPath下讀取一個Context.properties的屬性文件,並賦值給defaultStrategy,這個屬性文件如下:

org.springframework.web.context.WebApplicationContext
                              =org.springframework.web.context.support.XmlWebApplicationContext

   啊哈,終於找到了,原來是XmlWebApplicationContext啊,這就是我們的WebApplicationContext具體實現對象。

既然找到他了,那我們看看他的refresh()方法做了什麼呢?

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                invokeBeanFactoryPostProcessors(beanFactory);
                registerBeanPostProcessors(beanFactory);
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
        }
    }

五、Bean Factory   

這麼多代碼中,只有第二行與我們當前討論的主題有關,這一行會嘗試獲取一個新鮮的BeanFactory,這個BeanFactory與我們之前說的那個BeanDefinitionRegistry有什麼關係呢?繼續看代碼:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        refreshBeanFactory();
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        return beanFactory;
    }

   在getBeanFactory之前,先進行了一個refreshBeanFactory的操作來刷新當前的BeanFactory,我們以此來看一下:

@Override
    protected final void refreshBeanFactory() throws BeansException {
        if (hasBeanFactory()) {
            destroyBeans();
            closeBeanFactory();
        }
        try {
            DefaultListableBeanFactory beanFactory = createBeanFactory();
            beanFactory.setSerializationId(getId());
            customizeBeanFactory(beanFactory);
            loadBeanDefinitions(beanFactory);
            synchronized (this.beanFactoryMonitor) {
                this.beanFactory = beanFactory;
            }
        }
    }

    代碼依舊很清晰,先判斷有沒有BeanFactory,如果有,銷燬所有Bean,關閉BeanFactory,然後重新創建一個BeanFactory,並將其賦給beanFactory實例變量,有沒有發現這個beanFactory是個DefaultListableBeanFactory啊?我們上邊講到的bean definition registry也是個DefaultListableBeanFactory記得嗎?他們會不會是同一個呢?答案是yes。重點就在這個loadBeanDefinition(beanFactory)方法上了,很明顯:加載Bean Definition到bean工廠中,是不是與我們上邊講到的對上了?

loadBeanDefinition中,Spring會讀取xml配置文件,然後會讀取裏面的bean定義,這一切都是委託給了文章開頭的BeanDefinitionParser來完成的,可以看到除了<context:component-scan/>的Parser,還有<mvc:annotation-driven/>的parser,還有<interceptors/>的parser等。是不是比較清晰了?

    當然,我們的問題及好奇心遠不止這些,這篇文章只是講解了其中的一小個:系統的初始化做了什麼,在什麼時候加載我們定義的beans,我們定義的bean被放到了哪裏? 等等,現在問題又來了,我們怎樣使用我們的bean呢?或者說如果被標記爲@Autowire的屬性,是怎樣被自動裝配的呢?@RequestMapping怎樣工作的呢?Spring怎樣正確調用controller來處理請求呢?等等,後面的文章我們一一解答。

原文鏈接https://my.oschina.net/HeliosFly/blog/203149

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