您好,登錄后才能下訂單哦!
如何進行的加載及配置文件的解析,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。
spring是個包含很多個模塊的框架。其中核心部分有四個,bean,core,context和Expresion Language.
core和bean是spring的基礎模塊。提供Ioc(控制反轉)和依賴注入特性,這些的基礎概念是BeanFactory。即bean工廠,一個可以創建各種bean的工廠。我們使用spring來過將項目的時候,都離不開bean,Bean模塊包含訪問配置文件,創建和管理bean以及進行Ioc和DI操作相關的所有類。
既然bean這么重要,那么就來分析一下bean的加載過程就順理成章了。只分析比較核心的,能夠根據這些方法想起其中細節的一些方法,所以有些地方不會太深入,適合對spring有一定了解的讀者,深究就留給自己去研究吧。
<bean id="xxx" class="com.xx.xx.Person"/>
如上,bean的定義是如此簡單,但是加載過程是怎樣的呢?為了簡便起見,這里還是會使用已經過時的DefaultListableBeanFactory的子類XmlBeanFactory來做分析,源碼使用的是spring 5.1.5.RELEASE。這里是學習工程
先給一個類圖,了解一下,有個印象,后續會逐步熟悉的。
AliasRegistry: 定義對alias的簡答年增刪改等操作; SimpleAliasRegistry: 主要是用map作為alias的緩存,并對接口AliasRegistry進行實現; SingletonBeanRegistry: 定義對單例的注冊及獲取; BeanFactory: 定義獲取bean及bean的各種屬性; DefaultSingletonBeanRegistry: 對接口SingletonBeanRegistry各函數的實現; hierarchicalBeanFactory: 繼承BeanFactory,也就是在beanFactory定義的功能的基礎上增加了對ParrentFactory的支持; BeanDefintionRegistry: 定義對BeanDefinition的各種增刪改操作; FactoryBeanRegistrySupport: 在DefaultSingletonBeanRegistry基礎上增加了對FactoryBean的特殊處理功能; ConfigurableBeanFactory: 提供配置Factory的各種方法; ListableBeanFactory: 根據各種條件獲取備案的配置清單; AbstractBeanFactory: 綜合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能。 AutowireCapableBeanFacotry: 提供創建bean、自動注入、初始化以及應用bean的后置處理器; AbstractAutowireCapableBeanFactory: 綜合AbstractBeanFactory并對接口AutowireCapableBeanFactory進行實現; ConfigurableListableBeanFactory: BeanFactory配置清單,指定胡烈類型及接口等; DefaultListableBeanFactory: 綜合上面內所有功能,主要是對Bean注冊后的處理;
XmlBeanFactory是DefaultListableBeanFactory的子類,標記為已經過時,里面增加了XmlBeanDefinitionReader類型的reader屬性。雖然已經標記為過時,但是即使是DefaultListableBeanFactory,最終在spring初始化的過程中,也是使用的XmlBeanDefinitionReader來讀取的配置文件。因此不影響我們對代碼的分析。
spring的配置文件是實現bean配置的最常用方式(這里不討論springboot),而讀取xml配置文件的就是XmlBeanDefinitionReader。先來看一下會使用到的一些類的功能。
ResourceLoader: 定義資源加載器,主要應用于根據給定的資源文件地址返回對應你的Resource; BeanDefinitionReader: 主要定義資源文件讀取并轉換為BeanDefinition的各個功能; EnvironmentCapable: 定義獲取Environment方法; DocumentLoader: 定義從資源文件加載到轉換為Document的功能; AbstractBeanDefinitionReader: 對EnvironmentCapable、BeanDefinitionReader類定義的功能進行實現; BeanDefinitionDocumentReader: 定義讀取Document并注冊BeanDefinition功能; BeanDefinitionParserDelegate: 定義解析Element的各種方法;
XmlBeanDefinitionReader通過繼承AbstractBeanDefinitionReader中的方法,來使用ResourseLoader將資源文件路徑轉換為對應的Resource文件;通過DocumentLoader對Resource文件進行轉換,將Resource文件轉換為Document文件;通過實現接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader類對Document進行解析,并使用BeanDefinitionParserDelegate對Element進行解析。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException { super(parentBeanFactory); // 開始加載并讀取xml文件 this.reader.loadBeanDefinitions(resource); }
構造方法中super調用了父類,一直跟到底,會在AbstractAutowireCapableBeanFactory中發現以下代碼:
public AbstractAutowireCapableBeanFactory() { super(); // 忽略指定接口的自動裝配功能 ignoreDependencyInterface(BeanNameAware.class); ignoreDependencyInterface(BeanFactoryAware.class); ignoreDependencyInterface(BeanClassLoaderAware.class); }
回到構造函數的this.reader.loadBeanDefinitions(resource);
// XmlBeanDefinitionReader public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException { return loadBeanDefinitions(new EncodedResource(resource)); } public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { //... // 檢查當前線程正正在加載的resource Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } //如果當前準備解析的encodeResource正在加載,則拋出異常 if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { //獲取resource的inputStream InputStream inputStream = encodedResource.getResource().getInputStream(); try { //包裝成inputSource InputSource inputSource = new InputSource(inputStream); if (encodedResource.getEncoding() != null) { inputSource.setEncoding(encodedResource.getEncoding()); } //開始加載 return doLoadBeanDefinitions(inputSource, encodedResource.getResource()); } finally { inputStream.close(); } } //... } protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { //將resource轉成document,這里頭會去驗證xml文件,DTD或者XSD,不同的類型會用不用的解析方式,喜歡的自己去看吧 Document doc = doLoadDocument(inputSource, resource); //解析document并注冊bean int count = registerBeanDefinitions(doc, resource); //... //返回加載的bean的數量 return count; } } public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { //創建document解析器 BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader(); int countBefore = getRegistry().getBeanDefinitionCount(); //使用document解析器解析document //這里createReaderContext()創建的是XmlReaderContext documentReader.registerBeanDefinitions(doc, createReaderContext(resource));//***① return getRegistry().getBeanDefinitionCount() - countBefore; } public XmlReaderContext createReaderContext(Resource resource) { return new XmlReaderContext(resource, this.problemReporter, this.eventListener, this.sourceExtractor, this, getNamespaceHandlerResolver()); } public NamespaceHandlerResolver getNamespaceHandlerResolver() { if (this.namespaceHandlerResolver == null) { this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver(); } return this.namespaceHandlerResolver; } protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() { ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader()); //返回默認的命名空間解析器,這個東西會用來獲取解析標簽的NamespaceHandler return new DefaultNamespaceHandlerResolver(cl); }
看方法***①:
//DefaultBeanDefinitionDocumentReader.java public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) { this.readerContext = readerContext; //解析document doRegisterBeanDefinitions(doc.getDocumentElement()); } protected void doRegisterBeanDefinitions(Element root) { BeanDefinitionParserDelegate parent = this.delegate; //創建默認的BeanDefinitionParserDelegate,用來解析自定義標簽 this.delegate = createDelegate(getReaderContext(), root, parent); //...profile的處理,如果在web.xml中定義了profile,則只解析相應的文件 if (this.delegate.isDefaultNamespace(root)) { String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE); if (StringUtils.hasText(profileSpec)) { String[] specifiedProfiles = StringUtils.tokenizeToStringArray( profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS); // We cannot use Profiles.of(...) since profile expressions are not supported // in XML config. See SPR-12458 for details. if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) { if (logger.isDebugEnabled()) { logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource()); } return; } } } //留給子類擴展 preProcessXml(root); //真正開始解析 parseBeanDefinitions(root, this.delegate); //留給子類擴展 postProcessXml(root); } 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; if (delegate.isDefaultNamespace(ele)) { //解析默認標簽 parseDefaultElement(ele, delegate); } else { //解析自定義標簽 delegate.parseCustomElement(ele); } } } } else { //解析自定義標簽 delegate.parseCustomElement(root); } }
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { //解析import標簽,如果有import標簽,其實就會遞歸解析了 importBeanDefinitionResource(ele); } else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { //解析別名標簽 processAliasRegistration(ele); } else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) { //解析bean標簽 processBeanDefinition(ele, delegate); } else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) { // 解析beans標簽,其實就是把當前節點繼續丟去解析而已,產生了遞歸 doRegisterBeanDefinitions(ele); } }
對默認標簽的解析就不多寫了,無非不過就是怎么去讀取文件,怎么解決文件中的各種數據,喜歡的就自己深究下去吧,再細下去就太多了,為了給自己一個印象,我這里不會深究下去。
先看個demo吧。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:myname="http://www.wt.com/schema/user" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.wt.com/schema/user http://www.wt.com/schema/user.xsd"> <myname:user id="user" name="wt" age="23" email="taow"/> </beans>
指定命名空間myname="http://www.wt.com.schema/user",看下面META-INF/Spring.handlers配置定義了當前命名空間所使用的解析器;schemaLocation中的http://www.wt.com/schema/user.xsd指定文件所在目錄,看下面META-INF/Spring.schemas的配置,指定了xsd的文件所在目錄。
# META-INF/Spring.handlers # 定義指定namespace解析器,格式namespace = NamespaceHandler http\://www.wt.com/schema/user=com.wt.test.customtag.MyNamespaceHandler
# META-INF/Spring.schemas # 定義xml約束文檔xsd所在目錄 http\://www.wt.com/schema/user.xsd=META-INF/user.xsd
<!-- META-INF/user.xsd --> <?xml version="1.0" encoding="UTF-8"?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.wt.com/schema/user" elementFormDefault="qualified"> <element name="user"> <complexType> <attribute name="id" type="string" /> <attribute name="email" type="string" /> <attribute name="name" type="string" /> <attribute name="age" type="int" /> </complexType> </element> </schema>
//注冊自定義的命名空間解析器 public class MyNamespaceHandler extends NamespaceHandlerSupport { public void init() { //注冊解析器 registerBeanDefinitionParser("user", new UserBeanDefinitionParser()); } } //該方法在父類NamespaceHandlerSupport中,父類實現了NamespaceHandler public BeanDefinition parse(Element element, ParserContext parserContext) { BeanDefinitionParser parser = findParserForElement(element, parserContext); return (parser != null ? parser.parse(element, parserContext) : null); }
//標簽解析器 public class UserBeanDefinitionParser extends AbstractSingleBeanDefinitionParser { @Override protected Class<?> getBeanClass(Element element) { return User.class; } @Override protected void doParse(Element element, BeanDefinitionBuilder bean) { String name = element.getAttribute("name"); String age = element.getAttribute("age"); String email = element.getAttribute("email"); if (StringUtils.hasText(name)) { bean.addPropertyValue("name", name); } if (StringUtils.hasText(age)) { bean.addPropertyValue("age", age); } if (StringUtils.hasText(email)) { bean.addPropertyValue("email", email); } } }
以上就是所有的自定義標簽的配置。實現NamespaceHandler,在init方法中注冊我們自定義的解析器。其實邏輯只是我們調用namespaceHandler的parse方法來解析自定義標簽。但真正的解析肯定就是在parse方法中調用我們自己的解析器來解析而已。
現在開始分析,自定義標簽是通過delegate.parseCustomElement(ele)來解析的。dalegate就是注釋中提到的創建的默認的BeanDefinitionParserDelegate。
//BeanDefinitionParserDelegate.java public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); } public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) { //獲取namespaceURI,也就是我們定義的命名空間http://www.wt.com/schema/user String namespaceUri = getNamespaceURI(ele); if (namespaceUri == null) { return null; } //使用nameSpaceHandlerResolver,根據namespaceUri獲取自定義的標簽解析器NamespaceHandler, NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } //解析自定義標簽 return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
我們自定義的MyNamespaceHandler是就是通過NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);這一行來獲取的。直接跟進去,進入resolve方法。至于getNamespaceHandlerResolver()在哪里創建的之前提到過,這里就不繼續說了,是默認的。
//DefaultNamespaceHandlerResolver.java public NamespaceHandler resolve(String namespaceUri) { //獲取所有的命名空間->命名空間解析器的映射,就是Spring.handlers中的映射 Map<String, Object> handlerMappings = getHandlerMappings(); Object handlerOrClassName = handlerMappings.get(namespaceUri); if (handlerOrClassName == null) { return null; } //第一次加載的時候是String->String,但是如果已經獲取過,就會實例化而變成String->NamespaceHandler并加入緩存, //所以這里先判斷是不是已經初始化過,如果是就不用再初始化了 else if (handlerOrClassName instanceof NamespaceHandler) { return (NamespaceHandler) handlerOrClassName; } else { //第一次加載,根據全限定類名初始化 String className = (String) handlerOrClassName; try { Class<?> handlerClass = ClassUtils.forName(className, this.classLoader); //... NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass); //執行init方法,我們自定義的namespaceHandler在這個方法中注冊了我們自己的解析器 namespaceHandler.init(); //加入當前實例,避免下次進入再次實例化 handlerMappings.put(namespaceUri, namespaceHandler); return namespaceHandler; } //... } } private Map<String, Object> getHandlerMappings() { Map<String, Object> handlerMappings = this.handlerMappings; if (handlerMappings == null) { synchronized (this) { handlerMappings = this.handlerMappings; if (handlerMappings == null) { //... try { //從文件中獲取映射關系 Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader); //... handlerMappings = new ConcurrentHashMap<>(mappings.size()); CollectionUtils.mergePropertiesIntoMap(mappings, handlerMappings); this.handlerMappings = handlerMappings; } //... } } } return handlerMappings; } public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers"; public DefaultNamespaceHandlerResolver() { this(null, DEFAULT_HANDLER_MAPPINGS_LOCATION); }
在getHandlerMappings()方法中,我們注意看這行代碼Properties mappings = PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);這樣代碼就是從Spring.handlers文件中獲取所有的映射關系。在上面的代碼最后面,貼上了默認的地址,也就是META-INF/Spring.handlers。
這樣就拿到了我們自定義的NamespaceHandler,接下來就是調用我們的MyNameSpaceHandler的parse方法來解析咯(實際上在這里頭會調用我們自己的解析器UserBeanDefinitionParser哦,比較簡單,自己看咯)。
我們熟悉的spring-aop就是通過自定義標簽來實現的哦,使用的是AopNamespaceHandler哦。 還有springmvc的<annotation-driven/>也是哦,使用的是MvcNamespaceHandler哦。
xml的解析內容比較少,但是如果不熟悉spring的話看起來還是有一定難度的。這里重在給自己勾勒初一個大致的輪廓,在這其中可以回憶起其中的細節。
關于如何進行的加載及配置文件的解析問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。