说明
本文以SpringMVC容器为例,来看看容器创建和扫描的过程,这里我们只主要介绍基于xml的父容器的扫描实现,如有不正确的地方欢迎指教。
前提知识
sevlet容器的启动原理
- 基于web.xml的启动方式,servlet容器在启动的时候会解析WEB-INF/web.xml文件
- 父子容器的创建。其中父容器创建是根据ContextLoaderListener的监听机制去实现的
具体容器依赖关系图
具体容器的类型
具体的创建流程
- 调用org.springframework.web.context.ContextLoaderListener#contextInitialized方法,完成父容器的创建。
- 然后会调用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;
}
- 接下来看看创建的具体的容器的类型
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);
}
}
}
- 获取默认的容器类型,上面的方法会从默认的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());
}
}
- ContextLoader.properties的文件具体内容如下
org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
-
由上可见SpringMVC启动的时候,创建的父容器的类型为XmlWebApplicationContext,它的类图结构如下,类型为AbstractRefreshableConfigApplicationContext
以上就完成了具体容器的创建过程,接下来就到了容器的配置和刷新过程,刷新的方法中就会完成扫描的过程扫描过程
配置及刷新容器
- 如上所述,接下来就会到配置刷新容器的方法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));
}
}
- 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>
- org.springframework.context.support.AbstractApplicationContext#refresh,spring容器最重要的方法,没有之一。该方法的具体内容不在此文介绍之内,我们此处只介绍关于xml容器的扫描是怎样完成的。
- 具体在中实现
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
- refreshBeanFactory是个抽象方法,具体的实现有两个分别为:
- GenericApplicationContext
- AbstractRefreshableApplicationContext
扫描执行
- 此处由上面创建的具体容器的类型可知,我们调用的是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);
}
}
- 该内部最重要的方法就是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);
}
}
}
- 然后根据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);
}
}
- 自定义标签的解析原理见:spring自定义xml标签的原理
- 最终注册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实现类