Spring基于XML扫描到创建BeanDefinition的原理

说明

本文以SpringMVC容器为例,来看看容器创建和扫描的过程,这里我们只主要介绍基于xml的父容器的扫描实现,如有不正确的地方欢迎指教。

前提知识

sevlet容器的启动原理

  1. 基于web.xml的启动方式,servlet容器在启动的时候会解析WEB-INF/web.xml文件
  2. 父子容器的创建。其中父容器创建是根据ContextLoaderListener的监听机制去实现的

具体容器依赖关系图

在这里插入图片描述

具体容器的类型

具体的创建流程

  1. 调用org.springframework.web.context.ContextLoaderListener#contextInitialized方法,完成父容器的创建。
  2. 然后会调用org.springframework.web.context.ContextLoader#initWebApplicationContext方法,下面只展示方法的核心内容。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
			if (this.context == null) {
				// 1该方法创建的具体的容器类型,不同的子容器类型的扫描入口时不一样的
				this.context = createWebApplicationContext(servletContext);
			}
			if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					//2配置和刷新容器的方法
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			return this.context;
}
  1. 接下来看看创建的具体的容器的类型
public static final String CONTEXT_CLASS_PARAM = "contextClass";

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {
		//主要是通过该方法去决定容器的类型的,返回class并进行反射实例化
		Class<?> contextClass = determineContextClass(sc);
		return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
}
protected Class<?> determineContextClass(ServletContext servletContext) {
		//获取自定义配置的容器contextClass
		String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);
		if (contextClassName != null) {
			try {
				return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load custom context class [" + contextClassName + "]", ex);
			}
		}
		else {
		    //默认的容器类型
			contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());
			try {
				return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());
			}
			catch (ClassNotFoundException ex) {
				throw new ApplicationContextException(
						"Failed to load default context class [" + contextClassName + "]", ex);
			}
		}
	}

  1. 获取默认的容器类型,上面的方法会从默认的classPath路径下的ContextLoader.properties文件中获取WebApplicationContext.class的具体实现类
    private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
	private static final Properties defaultStrategies;
	static {
		try {
			ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
			defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
		}
		catch (IOException ex) {
			throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage());
		}
	}
  1. ContextLoader.properties的文件具体内容如下
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
  1. 由上可见SpringMVC启动的时候,创建的父容器的类型为XmlWebApplicationContext,它的类图结构如下,类型为AbstractRefreshableConfigApplicationContext
    在这里插入图片描述
    以上就完成了具体容器的创建过程,接下来就到了容器的配置和刷新过程,刷新的方法中就会完成扫描的过程

    扫描过程

配置及刷新容器

  1. 如上所述,接下来就会到配置刷新容器的方法org.springframework.web.context.ContextLoader#configureAndRefreshWebApplicationContext,具体内容如下
public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";"
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		wac.setServletContext(sc);
		//1.获取并设置配置文件中的contextConfigLocation属性,后续过程中会根据这个contextConfigLocation的值去完成扫描
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}

		//创建并调用用户配置的ApplicationContextInitializer
		customizeContext(sc, wac);
		wac.refresh();
	}

该内容与本文内容无关,只是稍微提一下而已

        public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
        public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
	    String globalClassNames = servletContext.getInitParameter(GLOBAL_INITIALIZER_CLASSES_PARAM);
		if (globalClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(globalClassNames, INIT_PARAM_DELIMITERS)) {
				classes.add(loadInitializerClass(className));
			}
		}

		String localClassNames = servletContext.getInitParameter(CONTEXT_INITIALIZER_CLASSES_PARAM);
		if (localClassNames != null) {
			for (String className : StringUtils.tokenizeToStringArray(localClassNames, INIT_PARAM_DELIMITERS)) {
				classes.add(loadInitializerClass(className));
			}
		}
  1. configLocationParam 通常是我们在web.xml中配置的,如
    web.xml内容如下:
<web-app version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <!--对应上述configLocationParam,父容器加载的配置文件,包含一些需要扫描的内容-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
</web-app>

applicationContext.xml的内容如下:

<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd">


    <context:component-scan base-package="com.huangjie">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
    <bean id="testService" class="com.huangjie.service.TestService" scope="prototype"></bean>
</beans>
  1. org.springframework.context.support.AbstractApplicationContext#refresh,spring容器最重要的方法,没有之一。该方法的具体内容不在此文介绍之内,我们此处只介绍关于xml容器的扫描是怎样完成的。
  2. 具体在中实现
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		return getBeanFactory();
	}
  1. refreshBeanFactory是个抽象方法,具体的实现有两个分别为:
  • GenericApplicationContext
  • AbstractRefreshableApplicationContext
    在这里插入图片描述

扫描执行

  1. 此处由上面创建的具体容器的类型可知,我们调用的是AbstractRefreshableApplicationContext的refreshBeanFactory方法,内容如下:
protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			customizeBeanFactory(beanFactory);
			//主要的扫描,并创建beanDefinition的方法
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
  1. 该内部最重要的方法就是loadBeanDefinitions,它完成了xml容器的解析和创建BeanDefinition的过程,具体调用应该是org.springframework.web.context.support.XmlWebApplicationContext#loadBeanDefinitions(org.springframework.beans.factory.support.DefaultListableBeanFactory)方法,内容如下:
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		beanDefinitionReader.setEnvironment(getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		initBeanDefinitionReader(beanDefinitionReader);
		//完成load
		loadBeanDefinitions(beanDefinitionReader);
	}

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
		//此处会获取到前面容器配置的configLocations,然后完成对用的文档解析,创建和添加BeanDefinition
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			for (String configLocation : configLocations) {
				reader.loadBeanDefinitions(configLocation);
			}
		}
	}
  1. 然后根据configLocations创建resource对象,并进行加载,该方法流程较多,就不一一展示了。,最终调用的是org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
//public static final String BEAN_ELEMENT = "bean";
public static final String BEAN_ELEMENT = BeanDefinitionParserDelegate.BEAN_ELEMENT;

public static final String NESTED_BEANS_ELEMENT = "beans";

public static final String ALIAS_ELEMENT = "alias";

public static final String NAME_ATTRIBUTE = "name";

public static final String ALIAS_ATTRIBUTE = "alias";

public static final String IMPORT_ELEMENT = "import";

public static final String RESOURCE_ATTRIBUTE = "resource";

public static final String PROFILE_ATTRIBUTE = "profile";
	
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标签的处理
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
		    //创建并注册BeanDefinition
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}
  1. 自定义标签的解析原理见:spring自定义xml标签的原理
  2. 最终注册BeanDefinition
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				//注册beanDefinition
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

如何自定义注解扫描(参考parseCustomElement该方法的具体实现)

  • 定义自己的XSD文件
  • 将自己的标签放入spring.handlers文件中
  • 实现NamespaceHandlerSupport的init方法
  • 定义自己的BeanDefinitionParser实现类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值