Spring原始碼分析(一)Spring的初始化和XML解析

清幽之地發表於2018-11-26

前言

Spring是什麼?它是一個應用程式框架,為應用程式的開發提供強大的支援,例如對事務處理和持久化的支援等;它也是一個bean容器,管理bean物件的整個生命週期,維護bean的各種存在狀態,例如bean物件的例項化、銷燬、bean的單例項和多例項狀態等。 Spring作為Java發展史上不可忽視的存在,說他重新定義了Java也不為過。它功能強大,著實為日常開發提供了大大的便利。表面越簡單的東西,背後越複雜。 從本章節開始,我們一起分析Spring的原始碼,看它到底是怎麼樣來實現我們常說常用的諸如IOC、Annotation、AOP、事務等功能的。

一、Spring的入口

在我們的專案中,web.xml必不可少,其中就定義了Spring的監聽器。

<listener>
      <listener-class>
            org.springframework.web.context.ContextLoaderListener
      </listener-class>
</listener>
複製程式碼

我們來看ContextLoaderListener類,可以看到它實現了ServletContextListener介面, contextInitialized就是Spring初始化的入口方法。 Spring還有一個入口,叫做org.springframework.web.servlet.DispatcherServlet,它們之間是父子容器的關係,最終都會呼叫到同一個方法org.springframework.context.support.AbstractApplicationContext.refresh()

二、初始化

Spring的初始化第一步就是要載入配置檔案,然後解析裡面的配置項。 ok,我們來到XmlWebApplicationContext類的loadBeanDefinitions方法。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		for (String configLocation : configLocations) {
			reader.loadBeanDefinitions(configLocation);
		}
	}
}
複製程式碼

可以看到,configLocations是一個陣列,它獲取的就是配置檔案。在筆者的專案中,只有一個配置檔案,名字是applicationContext.xml。下一步就是通過loadBeanDefinitions這個方法解析這個配置檔案。

三、解析XML配置

首先把一個配置檔案封裝成一個Resource物件,然後獲取Resource物件的輸入流,轉換成InputSource物件,最後解析成Document物件。下面程式碼只保留了主要部分。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) 
                         throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				//這裡的location就是配置檔案-applicationContext.xml,轉成Resource物件
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				//獲取resources物件的輸入流 再轉成JDK的InputSource物件,最後解析成Document
				InputStream inputStream = resources.getInputStream();
				InputSource inputSource = new InputSource(inputStream);
				Document doc = doLoadDocument(inputSource, resource);
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
	}
複製程式碼

applicationContext.xml配置檔案解析成Document物件,它的Root節點資訊如下:

[	
	[#text:], 
	[context:component-scan: null], 
	[#text:], 
	[bean: null], 
	[#text:], 
	[bean: null], 
	[#text:], 
	[bean: null],
	[#text:], 
	[bean: null], 
	[#text:], 
	[#comment:  指定了表現層資源的字首和字尾
    	viewClass:JstlView表示JSP模板頁面需要使用JSTL標籤庫
    	prefix 和suffix:查詢檢視頁面的字首和字尾,比如傳進來的邏輯檢視名為hello,則該該
						jsp檢視頁面應該存放在“WEB-INF/jsp/hello.jsp”], 
	[#text:], 
	[bean: null], 
	[#text: ]
]
複製程式碼

四、載入Bean資訊

上一步我們看到Spring已經把applicationContext.xml這個配置檔案解析成了Document物件,接下來就是關鍵的一步。先看原始碼

	//這裡拿到的是Document物件的根節點,根節點資訊參考上圖
	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					//這裡有兩個分支。
					//一個是處理預設的節點(import、alias、bean、beans)
					//一個是處理自定義的節點(context:component-scan)
					if (delegate.isDefaultNamespace(ele)) {
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}
複製程式碼

1、 component-scan的解析

首先定位到自定義解析方法delegate.parseCustomElement(ele); 最終呼叫了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext),不過它是怎麼呼叫到這個類的呢?說起來就比較有意思了。 我們先來看Spring裡面的一個配置檔案,/META-INF/spring.handlers

http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

複製程式碼

這裡面配置了不同標籤的處理類,比如context標籤處理類就是ContextNamespaceHandler,然後通過反射例項化這個處理類,呼叫它的init()方法。init()方法裡面它又註冊了一堆處理類,其中就有我們很感興趣的component-scan。

    public NamespaceHandler resolve(String namespaceUri) {
		//handlerMappings裡有個方法loadAllProperties(),獲取Spring所有的配置項
		Map<String, Object> handlerMappings = getHandlerMappings();
		Object handlerOrClassName = handlerMappings.get(namespaceUri);
		if (handlerOrClassName == null) {
			return null;
		}
		else if (handlerOrClassName instanceof NamespaceHandler) {
			return (NamespaceHandler) handlerOrClassName;
		}
		else {
			String className = (String) handlerOrClassName;
			try {
				//以context:component-scan舉例
                //這裡拿到的className就是org.springframework.context.config.ContextNamespaceHandler
				//通過反射,例項化這個ContextNamespaceHandler,然後呼叫init方法
				Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
				NamespaceHandler namespaceHandler = BeanUtils.instantiateClass(handlerClass);
				namespaceHandler.init();
				handlerMappings.put(namespaceUri, namespaceHandler);
				return namespaceHandler;
			}
		}
	}
	public void init() {
		registerBeanDefinitionParser("annotation-config",
             new AnnotationConfigBeanDefinitionParser());
		registerBeanDefinitionParser("component-scan",
             new ComponentScanBeanDefinitionParser());
        //...未完
	}
複製程式碼

最終Spring就可以通過component-scan這個標籤,拿到ComponentScanBeanDefinitionParser類,呼叫它的parse()方法。

	public BeanDefinition parse(Element element, ParserContext parserContext) {
		//獲取包掃描路徑,對應配置檔案中的base-package="com.viewscenes.netsupervisor"
		String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
		basePackage = parserContext.getReaderContext().getEnvironment().
              resolvePlaceholders(basePackage);
		//這裡可能有多個包路徑,分割成陣列
		String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
				ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);

		/**
		 * configureScanner 配置掃描器。
		 * scanner.doScan 掃描執行
		 * registerComponents 這裡重點是對registerComponents的支援
		 * 
		 * @return
		 */
		ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
		Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
		registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

		return null;
	}
複製程式碼

configureScanner 配置掃描器

這裡面重點就是註冊了預設的過濾器。use-default-filters,預設值是true,如果配置檔案配置了此屬性的值為false,有些註解就加不進來,到下一步掃描的時候就註冊不了Bean。

protected void registerDefaultFilters() {
	//這個就是配置的use-default-filters,如果配置了false。那麼下面的
    // Component、ManagedBean、Named註解都不會被掃描到
	if (useDefaultFilters) { 
		this.includeFilters.add(new AnnotationTypeFilter(Component.class));
		ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
		try {
			this.includeFilters.add(new AnnotationTypeFilter(
		    ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), 
                                                                          false));
			logger.debug("JSR-250 'javax.annotation.ManagedBean' 
                                              found and supported for component scanning");
		}
		catch (ClassNotFoundException ex) {
			// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
		}     
       //...未完
	}
}
複製程式碼

doScan掃描

doScan分為三個步驟。

  • findCandidateComponents 掃描包路徑下的所有class檔案,過濾有Component註解的類,轉換成BeanDefinition物件,加入一個LinkedHashSet中。
  • 迴圈上一步返回的LinkedHashSet,設定基本屬性,比如setLazyInit、setScope。
  • 註冊BeanDefinition物件,向Map容器中快取beanName和BeanDefinition,向List中加入beanName。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
		for (String basePackage : basePackages) {
			//findCandidateComponents方法掃描class檔案,判斷Component註解,轉成BeanDefinition物件返回。
			//值得注意的是,Component不止是@Component,還有
            //@Controller、@Service、@Repository,因為在這三個註解上面還有個@Component。
          //這就相當於它們都是Component的子註解。
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.
                                                            resolveScopeMetadata(candidate);
				//設定屬性,沒有配置的都是預設值
				candidate.setScope(scopeMetadata.getScopeName());
				candidate.setxxx(scopeMetadata.getxxxName());
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				//registerBeanDefinition方法 註冊BeanDefinition,等同於下面兩句
				//this.beanDefinitionMap.put(beanName, beanDefinition);
				//this.beanDefinitionNames.add(beanName);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
		return beanDefinitions;
	}
複製程式碼

最後整個方法返回的就是beanDefinition物件的Set集合,以兩個Controller為例。

[	
	Generic bean: class [com.viewscenes.netsupervisor.controller.IndexController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\IndexController.class], 
	
	Generic bean: class [com.viewscenes.netsupervisor.controller.UserController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\UserController.class]
]
複製程式碼

對annotation-config的支援

我們知道,在Spring配置檔案有個配置是context:annotation-config 但如果配置了context:component-scan 就不必再配置config,這是因為在解析component-scan的時候已經預設新增了annotation-config的支援,除非你手動設定了annotation-config="false",不過這可不太妙,因為在IOC的時候就沒辦法支援@Autowired等註解了。

protected void registerComponents(XmlReaderContext readerContext, 
                      Set<BeanDefinitionHolder> beanDefinitions, Element element) {
	boolean annotationConfig = true;
	if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
		annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
	}
	if (annotationConfig) { //判斷annotation-config屬性的值
		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
			beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}
			......未完
	}
}
複製程式碼

2、bean標籤的解析

bean標籤的解析,就是預設的處理方法。

  • 獲取bean標籤的id,並且把beanName賦值為id,設定別名。新建AbstractBeanDefinition物件,通過反射設定beanClass,解析property屬性名稱和值。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
		//獲取bean_id 
		String id = ele.getAttribute(ID_ATTRIBUTE);
		String beanName = id;
		AbstractBeanDefinition beanDefinition = 
                              parseBeanDefinitionElement(ele, beanName, containingBean);
		String[] aliasesArray = StringUtils.toStringArray(aliases);
		//最後返回已經包含了beanName、class物件和一系列方法的BeanDefinition物件
		return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, 
                                    String beanName, BeanDefinition containingBean) {
		String className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		try {
			//根據className反射設定setBeanClass和setBeanClassName
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
			//設定預設方法 setScope、setLazyInit、setAutowireMode...
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			//設定property屬性 <bean><property name="id" value="1001"></property></bean>
			parsePropertyElements(ele, bd);
			return bd;
		}
		return null;
}
複製程式碼
  • 註冊BeanDefinition物件,和component-scan掃描的bean註冊一樣。向容器中填充物件。
  • 不管是XML配置的Bean,還是通過component-scan掃描註冊的Bean它們最後都是殊途同歸的,會轉換成一個BeanDefinition物件。記錄著這個Bean物件的屬性和方法,最後都註冊到容器中,等待在例項化和IOC的時候遍歷它們。

相關文章