日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

spring 源码分析(1)-xml文件解析

發布時間:2025/3/17 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 spring 源码分析(1)-xml文件解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

我們在最開始接觸spring的時候,看到不少書spring入門的例子如下

ApplicationContext atx = new ClassPathXmlApplicationContext("application.xml"); atx.getBean("benefitService");

上面這個例子第一行是表示如何初始化一個spring 容器,第二表示如何從一個已經初始化后的spring容器中按bean id得到這個bean, 絕大部分spring應用中,bean都是按業務模板和層次分別配置在不同的xml文件中, spring容器根據配置的xml文件名路徑去分別解析這些xml 配置文件,生成相應的BeanDefinition 實例,一個bean對應一個BeanDefinition, 解析完成bean 的xml配置文件之后,spring容器就開始初始bean,大概的過程如下:

這篇文章主要分析第一個階段,即xml配置文件 ---->BeanDefinition這個過程,首先根據IDE工具看一下ClassPathXmlApplicationContext 這個類的繼承關系:

通過這個繼續關系,發現ClassPathXmlApplicationContext也是間接實現了ResourceLoader這個接口, ResourceLoader的實現類主要用于根據給定的資源文件地址返回對應的Resource,在本例中,這個資源文件就是application.xml;
接著往下看

public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null); } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();} }

代碼到了含有三個參數的構造方法,主要有三個步驟

super(parent)

這個步驟主要是調父類的構造器初始化容器的parent對象,這示例中,parent這個參數為空,其次是初始化資源模式解析器resourcePatternResolver,是一個實現了ResourceLoader的類,源碼如下 :

public AbstractApplicationContext(ApplicationContext parent) {this.parent = parent;this.resourcePatternResolver = getResourcePatternResolver(); } protected ResourcePatternResolver getResourcePatternResolver() {return new PathMatchingResourcePatternResolver(this); } public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {Assert.notNull(resourceLoader, "ResourceLoader must not be null");this.resourceLoader = resourceLoader; }

setConfigLocations(configLocations)

這行代碼,主要就是初始化configLocations這個數組字段,源碼如下:

private String[] configLocations; public void setConfigLocations(String[] locations) {if (locations != null) {Assert.noNullElements(locations, "Config locations must not be null");this.configLocations = new String[locations.length];for (int i = 0; i < locations.length; i++) {this.configLocations[i] = resolvePath(locations[i]).trim();}}else {this.configLocations = null;} }

其中resolvePath主要解析并填充資源路徑中的一些系統占位符,
如開始符:${,
結束符: }
分割符: :

refresh()

前面兩步基本上都是容器本身的設置初始化,這個步驟才是spring 容器解析,創建初始化bean的關鍵步驟,點時去,我們發現這個方法長,只分析xml解析的過程,其它的在這里不一 一細說,在refresh方法中,第二代碼是這樣的:

ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

直接進入org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory()這個方法,看一下這個方法體:

@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;}} catch (IOException ex) {throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);} }

這個方法的大意是這樣的:

  • 先判斷有沒有beanFactory, 如果已經存在beanFactory,就先銷毀beanFactory中所有的bean,再關閉這個beanFactory
  • 創建一個DefaultListableBeanFactory 類型實例的beanFactory;
  • 設置beanFactory的id,其中id是根據類名生成的,具體代碼是:

    obj.getClass().getName() + "@" + getIdentityHexString(obj)
  • 定置化beanFactory, 主要是設置: Bean是否需求覆蓋重寫,是否允bean循環引用,參數自動發現和注釋字段bean匹配解析,例如Autowired注解;
    5.解析xml文檔,并生成BeanDefinition對象實例集合;
  • 重點看第5個步驟,即loadBeanDefinitions(beanFactory)這行代碼,根據繼承關系,直接進入org.springframework.web.context.support.XmlWebApplicationContext.loadBeanDefinitions(DefaultListableBeanFactory)這個方法

    @Override protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {// Create a new XmlBeanDefinitionReader for the given BeanFactory.XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);// Configure the bean definition reader with this context's// resource loading environment.beanDefinitionReader.setResourceLoader(this);beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,// then proceed with actually loading the bean definitions.initBeanDefinitionReader(beanDefinitionReader);loadBeanDefinitions(beanDefinitionReader); }

    這個方法的邏輯也很清晰

  • 創建一個XmlBeanDefinitionReader 對象,這個類主要定義讀取的Document,并注冊BeanDefinition的功能;
  • 設置beanDefinitionReader 對象的ResourceLoader,即ClassPathXmlApplicationContext類對象;
  • 設置beanDefinitionReader 對象的解析對象:ResourceEntityResolver
  • 初始化beanDefinitionReader 對象,在spring框架中,這個方法為空,是一個重寫spring框架的擴展點;
  • protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {}
  • 正式解析xml文件,并生成BeanDefinitions, 方法體如下:
  • protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {String[] configLocations = getConfigLocations();if (configLocations != null) {for (String configLocation : configLocations) {reader.loadBeanDefinitions(configLocation);}} }

    針對每個資源文件,重點看下面這兩行代碼:

    Resource resource = resourceLoader.getResource(location); int loadCount = loadBeanDefinitions(resource);

    第一行代碼主是獲取路徑中以classpath:開頭的xml文件,當然也有很多其它前綴開頭的資源,org.springframework.util.ResourceUtils 這個類中有詳細的說明
    第二主要是生成加載BeanDefinitions

    public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {return loadBeanDefinitions(new EncodedResource(resource)); }

    前面都是一些準備過程,接下來,到了真正執行loadBeanDefinitions

  • getValidationModeForResource這個方法主要是獲取xml文件的驗證模式,主要有DTD和XSD兩種驗證模式,這兩種驗證模式,spring都支持,方法源碼如下:
  • DTD模式示例:

    XSD模式示例:

  • 加載xml,并得到轉換成對應的Document實例
  • 解析并注冊xml文件定義相關的bean;
    重點看一下步驟3:
  • 相關的方法源碼如下:

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {// 使用 DefaultBeanDefinitionDocumentReader實例化BeanDefinitionDocumentReader對象BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();// 記錄統計前已經加載的BeanDefinition數量int countBefore = getRegistry().getBeanDefinitionCount();// 加載并注冊beandocumentReader.registerBeanDefinitions(doc, createReaderContext(resource));// 記錄本次加載的 BeanDefinition數量return getRegistry().getBeanDefinitionCount() - countBefore; }

    重點分析 documentReader.registerBeanDefinitions(doc, createReaderContext(resource))這行代

    public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {this.readerContext = readerContext;logger.debug("Loading bean definitions");Element root = doc.getDocumentElement();BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);preProcessXml(root);parseBeanDefinitions(root, delegate);postProcessXml(root); }

    registerBeanDefinitions這個方法的邏輯也比較清晰

  • 賦值readerContext
  • 獲取xml對象加載包裝后對應Document對象
  • 創建解析器 BeanDefinitionParserDelegate 類型的對象實例:delegate
  • 處理xml文件的前置處理,默認為空,這是一個擴展點;
  • 解析xml文件,并注冊到spring容器中
  • 處理xml文件的后軒處理,默認為空,這也是一個擴展點;
    重點看一下parseBeanDefinitions這個方法,方法實現源碼如下:
  • 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)) {//默認標簽bean的解析//<bean id="benefitService" class="com.tmall.xxx">parseDefaultElement(ele, delegate);} else {//自定義標簽bean的解析<tx:annotation-driven /><dubbo:service timeout="3000" interface="com.tmall.xxx" ref="xxxx"/><context:component-scan base-package="com.tmall.xxx"/>delegate.parseCustomElement(ele);}}}} else {//自定義標簽bean的解析<context:component-scan base-package="com.tmall.xxx"/>delegate.parseCustomElement(root);} }

    這個方法主要是針對bean的xml配置文件中默認標簽和自定義標簽分別進行解析
    默認標簽的解析方法parseDefaultElement的方法體如下:

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { // 對import標簽的解析處理importBeanDefinitionResource(ele);}else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) { // 對alias標簽的解析處理processAliasRegistration(ele);}else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {//對bean標簽的解析處理processBeanDefinition(ele, delegate);}else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {//對beanS標簽的解析處理processBeanDefinition(ele, delegate);} }

    到這個方法,我們終于看到與我們平時寫spring bean配置文件相關的代碼,下面結bean標簽的解析處理代碼進行分析
    bean標簽的解析最終會到parseBeanDefinitionElement方法,這個方法的部分代碼如下:

    這個方法的主要工作內容包括:

  • 提供元素中的id和name屬性
  • 進一步解析其他所有屬性并統一封裝到AbstractBeanDefinition類型的實例中
  • 如果檢測到bean沒有指定的beanName,那么使用默認的規則為此Bean生成beanName
    4.將獲取到的信息封裝到BeanDefinitionHolder的實例中。
  • 步驟2中對其它標簽的解析過程部分源碼如下:

    public AbstractBeanDefinition parseBeanDefinitionAttributes(Element ele, String beanName,BeanDefinition containingBean, AbstractBeanDefinition bd) {if (ele.hasAttribute(SCOPE_ATTRIBUTE)) { // scope屬性// Spring 2.x "scope" attributebd.setScope(ele.getAttribute(SCOPE_ATTRIBUTE));if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) { // singleton 屬性error("Specify either 'scope' or 'singleton', not both", ele);}}else if (ele.hasAttribute(SINGLETON_ATTRIBUTE)) {// Spring 1.x "singleton" attributebd.setScope(TRUE_VALUE.equals(ele.getAttribute(SINGLETON_ATTRIBUTE)) ?BeanDefinition.SCOPE_SINGLETON : BeanDefinition.SCOPE_PROTOTYPE);}else if (containingBean != null) {// Take default from containing bean in case of an inner bean definition.bd.setScope(containingBean.getScope());}if (ele.hasAttribute(ABSTRACT_ATTRIBUTE)) { // abstract屬性bd.setAbstract(TRUE_VALUE.equals(ele.getAttribute(ABSTRACT_ATTRIBUTE)));}String lazyInit = ele.getAttribute(LAZY_INIT_ATTRIBUTE); // lazy-init屬性if (DEFAULT_VALUE.equals(lazyInit)) {lazyInit = this.defaults.getLazyInit();}bd.setLazyInit(TRUE_VALUE.equals(lazyInit));String autowire = ele.getAttribute(AUTOWIRE_ATTRIBUTE); //autowire屬性bd.setAutowireMode(getAutowireMode(autowire));String dependencyCheck = ele.getAttribute(DEPENDENCY_CHECK_ATTRIBUTE);bd.setDependencyCheck(getDependencyCheck(dependencyCheck));if (ele.hasAttribute(DEPENDS_ON_ATTRIBUTE)) { // depends-on屬性String dependsOn = ele.getAttribute(DEPENDS_ON_ATTRIBUTE);bd.setDependsOn(StringUtils.tokenizeToStringArray(dependsOn, BEAN_NAME_DELIMITERS));}// autowire-candidate 屬性String autowireCandidate = ele.getAttribute(AUTOWIRE_CANDIDATE_ATTRIBUTE);if ("".equals(autowireCandidate) || DEFAULT_VALUE.equals(autowireCandidate)) {String candidatePattern = this.defaults.getAutowireCandidates();if (candidatePattern != null) {String[] patterns = StringUtils.commaDelimitedListToStringArray(candidatePattern);bd.setAutowireCandidate(PatternMatchUtils.simpleMatch(patterns, beanName));}}else {bd.setAutowireCandidate(TRUE_VALUE.equals(autowireCandidate));}// primary 屬性if (ele.hasAttribute(PRIMARY_ATTRIBUTE)) {bd.setPrimary(TRUE_VALUE.equals(ele.getAttribute(PRIMARY_ATTRIBUTE)));}// init-method屬性if (ele.hasAttribute(INIT_METHOD_ATTRIBUTE)) {String initMethodName = ele.getAttribute(INIT_METHOD_ATTRIBUTE);if (!"".equals(initMethodName)) {bd.setInitMethodName(initMethodName);}}else {if (this.defaults.getInitMethod() != null) {bd.setInitMethodName(this.defaults.getInitMethod());bd.setEnforceInitMethod(false);}}// destroy-method屬性if (ele.hasAttribute(DESTROY_METHOD_ATTRIBUTE)) {String destroyMethodName = ele.getAttribute(DESTROY_METHOD_ATTRIBUTE);if (!"".equals(destroyMethodName)) {bd.setDestroyMethodName(destroyMethodName);}}else {if (this.defaults.getDestroyMethod() != null) {bd.setDestroyMethodName(this.defaults.getDestroyMethod());bd.setEnforceDestroyMethod(false);}}// factory-method 屬性if (ele.hasAttribute(FACTORY_METHOD_ATTRIBUTE)) {bd.setFactoryMethodName(ele.getAttribute(FACTORY_METHOD_ATTRIBUTE));}// factory-bean 屬性if (ele.hasAttribute(FACTORY_BEAN_ATTRIBUTE)) {bd.setFactoryBeanName(ele.getAttribute(FACTORY_BEAN_ATTRIBUTE));}return bd; }// 解析購造函數constructor-arg標簽元素 public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {parseConstructorArgElement((Element) node, bd);}} } //解析property屬性 public void parsePropertyElements(Element beanEle, BeanDefinition bd) {NodeList nl = beanEle.getChildNodes();for (int i = 0; i < nl.getLength(); i++) {Node node = nl.item(i);if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {parsePropertyElement((Element) node, bd);}} }

    其它的標簽解析過程請參考下面這個類org.springframework.beans.factory.xml.BeanDefinitionParserDelegate

    上面解析分析了默認標簽bean的源碼解析過程,下面再來看一下自定標簽的解析過程,方法位置:org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(Element, BeanDefinition)源碼如下:

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {//獲取對應的命名空間String namespaceUri = getNamespaceURI(ele);// 根據命名空間得到對相應的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;}// 調用自定義的NamespaceHandler handler進行解析return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }

    由于spring自定義標簽大家在平時用得比較少,由于時間關系和篇幅關系,本來就不對自定義標簽進行詳細分析,下一篇文章會結合spring源碼對自定義標簽的使用和解析原理進行詳細的介紹分析

    由于時間關系,文中有些地方沒有寫細致,講得不夠清楚,可能不少地方還會出現低級的錯誤,請大家指正。

    總結

    以上是生活随笔為你收集整理的spring 源码分析(1)-xml文件解析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。