Spring的初始化和XML解析的實(shí)現(xiàn)
前言
Spring是什么?它是一個(gè)應(yīng)用程序框架,為應(yīng)用程序的開(kāi)發(fā)提供強(qiáng)大的支持,例如對(duì)事務(wù)處理和持久化的支持等;它也是一個(gè)bean容器,管理bean對(duì)象的整個(gè)生命周期,維護(hù)bean的各種存在狀態(tài),例如bean對(duì)象的實(shí)例化、銷(xiāo)毀、bean的單實(shí)例和多實(shí)例狀態(tài)等。
Spring作為Java發(fā)展史上不可忽視的存在,說(shuō)他重新定義了Java也不為過(guò)。它功能強(qiáng)大,著實(shí)為日常開(kāi)發(fā)提供了大大的便利。表面越簡(jiǎn)單的東西,背后越復(fù)雜。
從本章節(jié)開(kāi)始,我們一起分析Spring的源碼,看它到底是怎么樣來(lái)實(shí)現(xiàn)我們常說(shuō)常用的諸如IOC、Annotation、AOP、事務(wù)等功能的。
1、Spring的入口
在我們的項(xiàng)目中,web.xml必不可少,其中就定義了Spring的監(jiān)聽(tīng)器。
<listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>
我們來(lái)看ContextLoaderListener類(lèi),可以看到它實(shí)現(xiàn)了ServletContextListener接口,
contextInitialized就是Spring初始化的入口方法。
Spring還有一個(gè)入口,叫做org.springframework.web.servlet.DispatcherServlet,它們之間是父子容器的關(guān)系,最終都會(huì)調(diào)用到同一個(gè)方法org.springframework.context.support.AbstractApplicationContext.refresh()。
2、初始化
Spring的初始化第一步就是要加載配置文件,然后解析里面的配置項(xiàng)。
ok,我們來(lái)到XmlWebApplicationContext類(lèi)的loadBeanDefinitions方法。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { reader.loadBeanDefinitions(configLocation); } } }
可以看到,configLocations是一個(gè)數(shù)組,它獲取的就是配置文件。在筆者的項(xiàng)目中,只有一個(gè)配置文件,名字是applicationContext.xml。下一步就是通過(guò)loadBeanDefinitions這個(gè)方法解析這個(gè)配置文件。
3、解析XML配置
首先把一個(gè)配置文件封裝成一個(gè)Resource對(duì)象,然后獲取Resource對(duì)象的輸入流,轉(zhuǎn)換成InputSource對(duì)象,最后解析成Document對(duì)象。下面代碼只保留了主要部分。
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,轉(zhuǎn)成Resource對(duì)象 Resource[] resources=resourceLoader).getResources(location); //獲取resources對(duì)象的輸入流 再轉(zhuǎn)成JDK的InputSource對(duì)象,最后解析成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對(duì)象,它的Root節(jié)點(diǎn)信息如下:
[ [#text:], [context:component-scan: null], [#text:], [bean: null], [#text:], [bean: null], [#text:], [bean: null], [#text:], [bean: null], [#text:], [#comment: 指定了表現(xiàn)層資源的前綴和后綴 viewClass:JstlView表示JSP模板頁(yè)面需要使用JSTL標(biāo)簽庫(kù) prefix 和suffix:查找視圖頁(yè)面的前綴和后綴,比如傳進(jìn)來(lái)的邏輯視圖名為hello,則該該 jsp視圖頁(yè)面應(yīng)該存放在“WEB-INF/jsp/hello.jsp”], [#text:], [bean: null], [#text: ] ]
4、加載Bean信息
上一步我們看到Spring已經(jīng)把a(bǔ)pplicationContext.xml這個(gè)配置文件解析成了Document對(duì)象,接下來(lái)就是關(guān)鍵的一步。先看源碼
//這里拿到的是Document對(duì)象的根節(jié)點(diǎn),根節(jié)點(diǎn)信息參考上圖 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; //這里有兩個(gè)分支。 //一個(gè)是處理默認(rèn)的節(jié)點(diǎn)(import、alias、bean、beans) //一個(gè)是處理自定義的節(jié)點(diǎn)(context:component-scan) if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { delegate.parseCustomElement(root); } }
4.1 component-scan的解析
首先定位到自定義解析方法delegate.parseCustomElement(ele);
最終調(diào)用了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext),不過(guò)它是怎么調(diào)用到這個(gè)類(lèi)的呢?說(shuō)起來(lái)就比較有意思了。
我們先來(lái)看Spring里面的一個(gè)配置文件,/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
這里面配置了不同標(biāo)簽的處理類(lèi),比如context標(biāo)簽處理類(lèi)就是ContextNamespaceHandler,然后通過(guò)反射實(shí)例化這個(gè)處理類(lèi),調(diào)用它的init()方法。init()方法里面它又注冊(cè)了一堆處理類(lèi),其中就有我們很感興趣的component-scan。
public NamespaceHandler resolve(String namespaceUri) { //handlerMappings里有個(gè)方法loadAllProperties(),獲取Spring所有的配置項(xiàng) 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 //通過(guò)反射,實(shí)例化這個(gè)ContextNamespaceHandler,然后調(diào)用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就可以通過(guò)component-scan這個(gè)標(biāo)簽,拿到ComponentScanBeanDefinitionParser類(lèi),調(diào)用它的parse()方法。
public BeanDefinition parse(Element element, ParserContext parserContext) { //獲取包掃描路徑,對(duì)應(yīng)配置文件中的base-package="com.viewscenes.netsupervisor" String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE); basePackage = parserContext.getReaderContext().getEnvironment(). resolvePlaceholders(basePackage); //這里可能有多個(gè)包路徑,分割成數(shù)組 String[] basePackages = StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); /** * configureScanner 配置掃描器。 * scanner.doScan 掃描執(zhí)行 * registerComponents 這里重點(diǎn)是對(duì)registerComponents的支持 * * @return */ ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element); Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages); registerComponents(parserContext.getReaderContext(), beanDefinitions, element); return null; }
4.1.1 configureScanner 配置掃描器
這里面重點(diǎn)就是注冊(cè)了默認(rèn)的過(guò)濾器。use-default-filters,默認(rèn)值是true,如果配置文件配置了此屬性的值為false,有些注解就加不進(jìn)來(lái),到下一步掃描的時(shí)候就注冊(cè)不了Bean。
protected void registerDefaultFilters() { //這個(gè)就是配置的use-default-filters,如果配置了false。那么下面的 // Component、ManagedBean、Named注解都不會(huì)被掃描到 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. } //...未完 } }
4.1.2 doScan掃描
doScan分為三個(gè)步驟。
- findCandidateComponents 掃描包路徑下的所有class文件,過(guò)濾有Component注解的類(lèi),轉(zhuǎn)換成BeanDefinition對(duì)象,加入一個(gè)LinkedHashSet中。
- 循環(huán)上一步返回的LinkedHashSet,設(shè)置基本屬性,比如setLazyInit、setScope。
- 注冊(cè)BeanDefinition對(duì)象,向Map容器中緩存beanName和BeanDefinition,向List中加入beanName。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>(); for (String basePackage : basePackages) { //findCandidateComponents方法掃描class文件,判斷Component注解,轉(zhuǎn)成BeanDefinition對(duì)象返回。 //值得注意的是,Component不止是@Component,還有 //@Controller、@Service、@Repository,因?yàn)樵谶@三個(gè)注解上面還有個(gè)@Component。 //這就相當(dāng)于它們都是Component的子注解。 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); for (BeanDefinition candidate : candidates) { ScopeMetadata scopeMetadata = this.scopeMetadataResolver. resolveScopeMetadata(candidate); //設(shè)置屬性,沒(méi)有配置的都是默認(rèn)值 candidate.setScope(scopeMetadata.getScopeName()); candidate.setxxx(scopeMetadata.getxxxName()); String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry); //registerBeanDefinition方法 注冊(cè)BeanDefinition,等同于下面兩句 //this.beanDefinitionMap.put(beanName, beanDefinition); //this.beanDefinitionNames.add(beanName); registerBeanDefinition(definitionHolder, this.registry); } } return beanDefinitions; }
最后整個(gè)方法返回的就是beanDefinition對(duì)象的Set集合,以?xún)蓚€(gè)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] ]
4.1.3 對(duì)annotation-config的支持
我們知道,在Spring配置文件有個(gè)配置是context:annotation-config 但如果配置了context:component-scan 就不必再配置config,這是因?yàn)樵诮馕鯿omponent-scan的時(shí)候已經(jīng)默認(rèn)添加了annotation-config的支持,除非你手動(dòng)設(shè)置了annotation-config="false",不過(guò)這可不太妙,因?yàn)樵贗OC的時(shí)候就沒(méi)辦法支持@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)); } ......未完 } }
4.2 bean標(biāo)簽的解析
bean標(biāo)簽的解析,就是默認(rèn)的處理方法。
獲取bean標(biāo)簽的id,并且把beanName賦值為id,設(shè)置別名。新建AbstractBeanDefinition對(duì)象,通過(guò)反射設(shè)置beanClass,解析property屬性名稱(chēng)和值。
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); //最后返回已經(jīng)包含了beanName、class對(duì)象和一系列方法的BeanDefinition對(duì)象 return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray); } public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, BeanDefinition containingBean) { String className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); try { //根據(jù)className反射設(shè)置setBeanClass和setBeanClassName AbstractBeanDefinition bd = createBeanDefinition(className, parent); //設(shè)置默認(rèn)方法 setScope、setLazyInit、setAutowireMode... parseBeanDefinitionAttributes(ele, beanName, containingBean, bd); //設(shè)置property屬性 <bean><property name="id" value="1001"></property></bean> parsePropertyElements(ele, bd); return bd; } return null; }
注冊(cè)BeanDefinition對(duì)象,和component-scan掃描的bean注冊(cè)一樣。向容器中填充對(duì)象。
不管是XML配置的Bean,還是通過(guò)component-scan掃描注冊(cè)的Bean它們最后都是殊途同歸的,會(huì)轉(zhuǎn)換成一個(gè)BeanDefinition對(duì)象。記錄著這個(gè)Bean對(duì)象的屬性和方法,最后都注冊(cè)到容器中,等待在實(shí)例化和IOC的時(shí)候遍歷它們。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Mac OS X 下 IntelliJ IDEA、jEdit 等 Java 程序中文標(biāo)點(diǎn)輸入無(wú)效的完美解決方法
Mac OS X 下基于 Java 的程序會(huì)出現(xiàn)中文標(biāo)點(diǎn)輸入無(wú)效的問(wèn)題,在中文輸入法狀態(tài),可以輸入中文字,但輸入中文標(biāo)點(diǎn)最后上去的是英文標(biāo)點(diǎn).這篇文章主要介紹了Mac OS X 下 IntelliJ IDEA、jEdit 等 Java 程序中文標(biāo)點(diǎn)輸入無(wú)效的完美解決方法,需要的朋友可以參考下2016-10-10spring cloud gateway使用 uri: lb://方式配置時(shí),服務(wù)名的特殊要求
這篇文章主要介紹了spring cloud gateway使用 uri: lb://方式配置時(shí),服務(wù)名的特殊要求,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12Java語(yǔ)法基礎(chǔ)之函數(shù)的使用說(shuō)明
函數(shù)就是定義在類(lèi)中的具有特定功能的一段小程序,函數(shù)也稱(chēng)為方法2013-07-07Java使用Redisson分布式鎖實(shí)現(xiàn)原理
Redisson分布式鎖 之前的基于注解的鎖有一種鎖是基本redis的分布式鎖,這篇文章主要介紹了Java使用Redisson分布式鎖實(shí)現(xiàn)原理,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-10-10Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)
這篇文章主要給大家介紹了關(guān)于Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09@FeignClient注解中屬性contextId的使用說(shuō)明
這篇文章主要介紹了@FeignClient注解中屬性contextId的使用說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06springBoot啟動(dòng)輸出三行日志控制臺(tái)自動(dòng)停止操作
這篇文章主要介紹了springBoot啟動(dòng)輸出三行日志控制臺(tái)自動(dòng)停止操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08Springboot 整合通用mapper和pagehelper展示分頁(yè)數(shù)據(jù)的問(wèn)題(附github源碼)
這篇文章主要介紹了Springboot 整合通用mapper和pagehelper展示分頁(yè)數(shù)據(jù)(附github源碼),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09