Spring MVC 解讀——

轉自:http://my.oschina.net/HeliosFly/blog/203149 做者:GoodLoser.

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

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

 

1、<context:component-scan/>

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@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配置文件中加入以下定義web

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

 

    這樣Spring就能夠正確的處理咱們定義好的組件了,重要的是這些都是自動的,你甚至不知道他是怎麼作的,作了什麼?若是不瞭解反射,可能真的感到吃驚了,但即使如此,我也想知道它到底作了什麼?何時作的?spring

2、BeanDefinitionParser

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

?
1
BeanDefinition parse(Element element, ParserContext parserContext);

 

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
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工廠中。是否是很清晰?ide

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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方法,意思就是找到候選組件,而後註冊到工廠中,那麼它是怎麼找到候選組件的呢?

咱們再看看

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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是否爲候選的,若是是則添加到工廠中。

3、includeFilters,excludeFilters

    能夠看到經歷了兩次篩選,才找到最終的候選Bean,咱們來看第一個過濾作了什麼?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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/>標籤下加兩個子標籤便可, 像這樣:

?
1
2
3
4
<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>

 

4、BeanDefinitionRegistry

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

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

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

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

 

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

?
1
2
3
4
5
6
7
8
9
10
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是怎樣實現的?

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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開始了入手分析了。

5、ContextLoader

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

?
1
2
3
4
5
6
7
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方法中進行了配置文件的加載和組件的掃描呢?必須是啊。。。

?
1
wac.refresh();

 

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

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

 

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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便會被賦值。

?
1
2
3
4
5
6
7
8
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,這個屬性文件以下:

?
1
2
org.springframework.web.context.WebApplicationContext
                               =org.springframework.web.context.support.XmlWebApplicationContext

 

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
             }
         }
     }

 

5、Bean Factory   

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

?
1
2
3
4
5
protected  ConfigurableListableBeanFactory obtainFreshBeanFactory() {
         refreshBeanFactory();
         ConfigurableListableBeanFactory beanFactory = getBeanFactory();
         return  beanFactory;
     }

 

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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來處理請求呢?等等,後面的文章咱們一一解答。

相關文章
相關標籤/搜索