SpringMVC的父子容器你都了解了吗?
环境:Spring5.3.23配置文件
如果我们不使用基于注解的方式,那么在Spring Web项目一般我们都会在web.xml文件中做如下的配置:
web.xml org.springframework.web.context.ContextLoaderListener contextConfigLocation /WEB-INF/app-context.xml app org.springframework.web.servlet.DispatcherServlet contextConfigLocation 1 app /app/* 父容器初始化
通过在web.xml中还会配置一个监听器,而这个监听器的作用就是初始化父容器的 org.springframework.web.context.ContextLoaderListener
上面配置的监听程序是用来初始化父容器。 public class ContextLoader { private WebApplicationContext context; private static final Properties defaultStrategies; public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; static { try { private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; // 从类路径下查找ContextLoader.properties文件 ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); // 加载配置文件;默认情况下会从spring-web-xxx.jar包下的org.springframework.web.context包下有该文件 // 文件内容:org.springframework.web.context.WebApplicationContext= // org.springframework.web.context.support.XmlWebApplicationContext // 也就是默认情况下实例化的是XmlWebApplicationContext容器对象 defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } } protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 获取容器ApplicationContext具体的类的Class对象 Class<?> contextClass = determineContextClass(sc); // 实例化XmlWebApplicationContext对象,该对象就是基于xml的配置的解析类 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); } protected Class<?> determineContextClass(ServletContext servletContext) { public static final String CONTEXT_CLASS_PARAM = "contextClass"; // 获取在web.xml中配置的context-param参数名称为contextClass;这里其实就是配置你要使用哪个ApplicationContext容器对象 String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); // 如果配置了使用你配置的ApplicationContext容器的具体对象 if (contextClassName != null) { try { return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); } } else { // 如果没有在web.xml配置文件中配置contextClass参数,则通过下面的方式获取 // 这里获取的就是上面static代码段中加载的配置 contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); try { return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); } } } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac,ServletContext sc) { wac.setServletContext(sc); // 获取在web.xml中配置的上下文参数(context-param), 名称为contextConfigLocation的参数值 String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM); if (configLocationParam != null) { // 如果配置了设置配置文件路径 wac.setConfigLocation(configLocationParam); } // 执行刷新,也就是解析处理上面配置的spring配置文件 // 如果没有配置上面的contextConfigLocation参数,那么会读取默认的applicationContext.xml配置文件 // 查看XmlWebApplicationContext类 wac.refresh(); } } public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { try { if (this.context == null) { // 创建容器,在父类中创建出XmlWebApplicationContext对象 this.context = createWebApplicationContext(servletContext); } // mlWebApplicationContext实现了ConfigurableWebApplicationContext接口 if (this.context instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context; // 确定此应用程序上下文是否处于活动状态,即它是否已刷新至少一次且尚未关闭。 if (!cwac.isActive()) { // ... // 刷新上下文 configureAndRefreshWebApplicationContext(cwac, servletContext); } } // 将实例化的ApplicationContext保存到ServletContext对象中 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); ClassLoader ccl = Thread.currentThread().getContextClassLoader(); if (ccl == ContextLoader.class.getClassLoader()) { currentContext = this.context; } else if (ccl != null) { currentContextPerThread.put(ccl, this.context); } return this.context; } } } public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; protected String[] getDefaultConfigLocations() { if (getNamespace() != null) { return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; } else { return new String[] {DEFAULT_CONFIG_LOCATION}; } } protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { loadBeanDefinitions(beanDefinitionReader); } protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException { // 调用父类AbstractRefreshableWebApplicationContext方法 String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { reader.loadBeanDefinitions(configLocation); } } } } // XmlWebApplicationContext在执行refresh的时候有这么一步 public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext { public void refresh() { ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); } protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { refreshBeanFactory(); return getBeanFactory(); } } public abstract class AbstractRefreshableWebApplicationContext extends AbstractRefreshableConfigApplicationContext implements ConfigurableWebApplicationContext, ThemeSource { public String[] getConfigLocations() { return super.getConfigLocations(); } } public abstract class AbstractRefreshableConfigApplicationContext extends AbstractRefreshableApplicationContext implements BeanNameAware, InitializingBean { protected String[] getConfigLocations() { // 如果没有配置,则调用默认的方法调用子类XmlWebApplicationContext重写的方法 return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations()); } } public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext { protected final void refreshBeanFactory() throws BeansException { // 调用子类XmlWebApplicationContext重写的方法 loadBeanDefinitions(beanFactory); } }
总结: 从配置文件中获取contextClass参数
如果没有则读取spring-web-xxx.jar中org.springframework.web.context包下的ContextLoader.properties文件
目的就是容器实例化具体 ApplicationContext 那个子类对象。
刷新初始化ApplicationContext对象
这里具体的类是 XmlWebApplicationContext 对象。
1. 首先是设置要读取的配置文件
读取web.xml中配置的 参数contextConfigLocation,如果没有设置该 参数,则使用默认的/WEB-INF/applicationContext.xml 。
2. 调用refresh初始化spring容器
保存spring容器对象
实例化后会将该容器对象保存到 ServletContext 中,以 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE 为name存入。
子容器初始化
子容器的初始化就是 DispatcherServlet 配置的 参数 public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware { public final void init() throws ServletException { // 这里代码的作用就是用来读取DispatcherServlet配置的contextConfigLocation参数,然后设置到 // FrameworkServlet中的contextConfigLocation属性中 // 这里就是通过BeanWrapper来完成此操作 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } } // 到这里就设置完了配置在DispatcherServlet中的contextConfigLocation参数 // 初始化Servlet Bean initServletBean(); } }
FrameworkServlet public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware { public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class; public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet"; // 该值在父类的init方法中已经通过BeanWrapper设置搞定了,当如如果没有配置会有默认机制的,下面会看到 private String contextConfigLocation; protected final void initServletBean() throws ServletException { this.webApplicationContext = initWebApplicationContext(); } protected WebApplicationContext initWebApplicationContext() { // 从ServletContext中读取在上面(初始化父容器)设置的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE // 容器对象 WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; // ... if (wac == null) { // 查找容器 // 这里默认还是返回null,这里就是看你在配置DispatcherServlet的时候有没有配置contextAttribute属性 // 如果你设置了该属性,会从ServletContext中读取配置的contextAttribute属性对应的值(该值必须是WebApplicationContext) // 如果不是则抛出异常 wac = findWebApplicationContext(); } if (wac == null) { // 到这还没有容器对象,则创建容器对象,同时这里的rootContext会作为父容器 wac = createWebApplicationContext(rootContext); } return wac; } protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) { return createWebApplicationContext((ApplicationContext) parent); } protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { // 这里就是获取要实例化的默认ApplictionContext对象,XmlWebApplicationContext Class<?> contextClass = getContextClass(); // 实例化 ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); wac.setEnvironment(getEnvironment()); // 设置父容器 wac.setParent(parent); // 获取要读取加载的配置文件,如果你没有则就是null String configLocation = getContextConfigLocation(); // 如果你没有配置,不进入 if (configLocation != null) { wac.setConfigLocation(configLocation); } // 刷新上下文 configureAndRefreshWebApplicationContext(wac); return wac; } protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) { wac.setServletContext(getServletContext()); wac.setServletConfig(getServletConfig()); // 这里就非常关键了,记住你这里有了namespace的值 wac.setNamespace(getNamespace()); wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); ConfigurableEnvironment env = wac.getEnvironment(); if (env instanceof ConfigurableWebEnvironment) { ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig()); } postProcessWebApplicationContext(wac); applyInitializers(wac); // 这里refresh的过程就和上面初始化父容器的流程一样了,会查找使用的那些xml配置文件 wac.refresh(); } public String getNamespace() { // 根据你配置的xxx名称拼接 // 默认就返回:xxx-servlet(xxx就是你配置的servlet名称) return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX); } public String getContextConfigLocation() { return this.contextConfigLocation; } } // 如果你没有配置contextConfigLocation,那么就找默认的配置xml文件 public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"; public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/"; public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml"; protected String[] getDefaultConfigLocations() { // 上面看到了我们的getNamespace设置了值,则进入 if (getNamespace() != null) { // 这里就拼接成:/WEB-INF/xxx-servlet.xml(也就是,如果你没有为DispatcherServlet配置contextConfigLocation属性) return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX}; } else { return new String[] {DEFAULT_CONFIG_LOCATION}; } } }
总结: 读取Servlet配置参数
读取配置 DispatcherServlet 时配置的参数,通过 BeanWrapper 设置当前Servlet对应的属性中。
实例化Spring容器对象
1. 实例化Spring容器对象
2. 设置容器对象的配置文件
如果你为Servlet配置了contextConfigLocation,则使用该参数对应的值作为子容器解 析的xml配置文件
3. 设置名称空间namespace
根据你配置的servlet名称 + "-servlet"作为名称空间
刷新Spring容器
调用refresh方法;如果你没有配置contextConfigLocation,则会查找默认的配置文件,而这个默认配置在 XmlWebApplicationContext 已经重写了,会判断当前的namespace是否为空,不为空则返回 /WEB-INF/xxx-servlet.xml (xxx: 取值根据你配置的servlet-name)。
完毕!!!
SpringBoot对Spring MVC都做了哪些事?(一)
SpringBoot对Spring MVC都做了哪些事?(二)
SpringBoot对Spring MVC都做了哪些事?(三)
SpringBoot对Spring MVC都做了哪些事?(四)
Spring中的@Configuration注解你真的了解吗?
Spring MVC 异常处理方式
Spring中字段格式化的使用详解
Spring MVC 异步请求方式
Spring容器这些扩展点你都清楚了吗?
Spring 自定义Advisor以编程的方式实现AOP
SpringBoot WebFlux整合Spring Security进行权限认证
SpringBoot项目中应用Spring Batch批处理框架,处理大数据新方案
Spring Security权限控制系列(七)