百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

SpringMVC的父子容器你都了解了吗?

yuyutoo 2024-10-26 16:07 4 浏览 0 评论

环境:Spring5.3.23


配置文件

如果我们不使用基于注解的方式,那么在Spring Web项目一般我们都会在web.xml文件中做如下的配置:

web.xml

 <web-app>
   <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
   </listener>
   <context-param>
     <param-name>contextConfigLocation</param-name>
     <param-value>/WEB-INF/app-context.xml</param-value>
   </context-param>
   <servlet>
     <servlet-name>app</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value></param-value>
     </init-param>
     <load-on-startup>1</load-on-startup>
   </servlet>
   <servlet-mapping>
     <servlet-name>app</servlet-name>
     <url-pattern>/app/*</url-pattern>
   </servlet-mapping>
 </web-app>

父容器初始化

通过在web.xml中还会配置一个监听器,而这个监听器的作用就是初始化父容器的

 <listener>
   <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>

上面配置的监听程序是用来初始化父容器。

 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中配置的<context-param>参数contextConfigLocation,如果没有设置该 参数,则使用默认的/WEB-INF/applicationContext.xml
    2. 调用refresh初始化spring容器

  • 保存spring容器对象
    实例化后会将该容器对象保存到ServletContext中,以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为name存入。

子容器初始化

子容器的初始化就是DispatcherServlet配置的<init-param>参数

 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() {
     // 根据你配置的<servlet-name>xxx</servlet-name>名称拼接
     // 默认就返回: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权限控制系列(七)

相关推荐

野路子科技!2步教你把手机改造成一个FTP服务器,支持PC互传

哈喽,大家好,我是野路子科技,今天来给大家带来一个教程,希望大家喜欢。正如标题所言,就是教大家如何把售价改造成FTP服务器,而这个时候估计有朋友会问了,把手机改造成FTP服务器有什么用呢?现在有Q...

不得不看:别样于Server-U的群晖文件存储服务器的搭建与使用

我先前的作品中,有着关于Server-U的ftp文件存储服务器的搭建与访问的头条文章和西瓜视频,而且我们通过各种方式也给各位粉丝介绍了如何突破局域网实现真正意义上的公网访问机制技术。关于Server-...

Qt三种方式实现FTP上传功能_qt引入qftp库

FTP协议FTP的中文名称是“文件传输协议”,是FileTransferProtocol三个英文单词的缩写。FTP协议是TCP/IP协议组中的协议之一,其传输效率非常高,在网络上传输大的文件时,经...

Filezilla文件服务器搭建及客户端的使用

FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本,具备所有的FTP软件功能。可控性、有条理的界面和管理多站点的简化方式使得Filezilla客户端版成为一个方便高效的FTP客户...

美能达柯美/震旦复印机FTP扫描怎么设置?

好多网友不知道怎么安装美能达/震旦复印机扫描,用得最多是SMB和FTP扫描,相对于SMB来说,FTP扫描安装步骤更为便捷,不容易出问题,不需要设置文件夹共享,所以小编推荐FTP来扫描以美能达机器为例详...

CCD(简易FTP服务器软件)_简单ftp服务器软件

CCD简易FTP服务器软件是一款很方便的FPT搭建工具,可以将我们的电脑快速变成一个FPT服务器。使用方法非常简单,只要运行软件就会自动生效,下载银行有该资源。该工具是不提供操作界面的,其他用户可以输...

Ubuntu系统搭建FTP服务器教程_ubuntu架设服务器

在Ubuntu系统上搭建FTP服务器是文件传输的一个非常实用方法,适合需要进行大量文件交换的场景。以下是一步步指导,帮助您在Ubuntu上成功搭建FTP服务器。1.安装vsftpd软件...

理光FTP扫描设置教程_理光ftp扫描设置方法

此教程主要用来解决WIN10系统下不能使用SMB文件夹扫描的问题,由于旧的SMB协议存在安全漏洞,所以微软在新的系统,WIN8/WIN10/SERVER201220162018里使用了新的SMB传...

纯小白如何利用wireshark学习网络技术

写在前面工欲善其事必先利其器!熟悉掌握一种神器对以后的工作必然是有帮助的,下面我将从简单的描述Wireshark的使用和自己思考去写,若有错误或不足还请批评指正。...

京东买13盘位32GB内存NAS:NAS系统安装设置教程

本内容来源于@什么值得买APP,观点仅代表作者本人|作者:yasden你没有看错,我在京东自营商城购买硬件,组装了一台13盘位,32GB内存的NAS,硬盘有13个盘位!CPU是AMD的5500!本文...

FileZilla搭建FTP服务器图解教程_filezilla server搭建ftp服务器

...

python教程之FTP相关操作_python ftps

ftplib类库常用相关操作importftplibftp=ftplib.FTP()ftp.set_debuglevel(2)#打开调试级别2,显示详细信息ftp.connect(“I...

xftp怎么用,xftp怎么用,具体使用方法

Xftp是一款界面化的ftp传输工具,用起来方便简单,这里为大家分享下Xftp怎么使用?希望能帮到有需要的朋友。IIS7服务器管理工具可以批量管理、定时上传下载、同步操作、数据备份、到期提醒、自动更新...

树莓派文件上传和下载,详细步骤设置FTP服务器

在本指南中,详细记录了如何在树莓Pi上设置FTP。设置FTP可以在网络上轻松地将文件传输到Pi上。FTP是文件传输协议的缩写,只是一种通过网络在两个设备之间传输文件的方法。还有一种额外的方法,你可以用...

win10电脑操作系统,怎么设置FTP?windows10系统设置FTP操作方法

打印,打印,扫描的日常操作是每一个办公工作人员的必需专业技能,要应用FTP作用扫描文件到电脑上,最先要必须一台可以接受文件的FTP服务器。许多软件都需要收费标准进行,但人们还可以应用Windows的系...

取消回复欢迎 发表评论: