【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)(中)

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 【小家Spring】Spring容器(含父子容器)的启动过程源码级别分析(含web.xml启动以及全注解驱动,和ContextLoader源码分析)(中)

initWebApplicationContext方法如下:创建一个web子容器,并且和上面Spring已经创建好了的父容器关联上


  protected WebApplicationContext initWebApplicationContext() {
    // 从ServletContext中把上面已经创建好的根容器拿到手
    WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = null;
    //但是,但是,但是此处需要注意了,因为本处我们是注解驱动的,在上面已经看到了,我们new DispatcherServlet出来的时候,已经传入了根据配置文件创建好的子容器web容器,因此此处肯定是不为null的,因此此处会进来,和上面一样,完成容器的初始化、刷新工作,因此就不再解释了~
    if (this.webApplicationContext != null) {
      // A context instance was injected at construction time -> use it
      wac = this.webApplicationContext;
      if (wac instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
        if (!cwac.isActive()) {
          if (cwac.getParent() == null) {
            //此处吧根容器,设置为自己的父容器
            cwac.setParent(rootContext);
          }
          //根据绑定的配置,初始化、刷新容器
          configureAndRefreshWebApplicationContext(cwac);
        }
      }
    }
    //若是web.xml方式,会走这里,进而走findWebApplicationContext(),因此此方法,我会在下面详细去说明,这里占时略过
    if (wac == null) {
      wac = findWebApplicationContext();
    }
    if (wac == null) {
      wac = createWebApplicationContext(rootContext);
    }
    // 此处需要注意了:下面有解释,refreshEventReceived和onRefresh方法,不会重复执行~
    if (!this.refreshEventReceived) {
      onRefresh(wac);
    }
    //我们是否需要吧我们的容器发布出去,作为ServletContext的一个属性值呢?默认值为true哦,一般情况下我们就让我true就好
    if (this.publishContext) {
      // Publish the context as a servlet context attribute.
      // 这个attr的key的默认值,就是FrameworkServlet.SERVLET_CONTEXT_PREFIX,保证了全局唯一性
      // 这么一来,我们的根容器、web子容器其实就都放进ServletContext上下文里了,拿取都非常的方便了。   只是我们一般拿这个容器的情况较少,一般都是拿跟容器,比如那个工具类就是获取根容器的~~~~~~
      String attrName = getServletContextAttributeName();
      getServletContext().setAttribute(attrName, wac);
    }
    return wac;
  }


备注,在DispatcherServlet的doService方法里都有这样的一段代码,方便我们非常方便获取到一些参数,比如web子容器等等


    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());


image.png

FrameworkServlet策略式的实现了监听方法,监听应用的刷新事件。当我们刷新应用的时候(比如上面执行refresh()方法,这里就会执行,并且打上标记说已经执行过了),然而onRefresh()是一个模版方法,具体实现交给子类,这样子DispatcherServlet就可以做做初始化web组件的一些事情了~ 这种设计模式可谓非常优秀,


这就是为何会抽象出FrameworkServlet的原因,因为它设计的初衷不仅仅只想支持到Servlet


所以此处就不得不说一下,子类自己实现的onRefresh()方法:

  @Override
  protected void onRefresh(ApplicationContext context) {
    initStrategies(context);
  }
  //初始化Spring MVC的9大组件(至此,才算全部初始化完成了~不容器啊)
  protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
  }


至于初始化Spring MVC9大组件的详细工作问题,此处也不展开了,参见此篇博文:~~~~~~~~~~~~~~~~~~


当看到这句日志,就能证明整个Spring父子容器全部初始化、启动完成了~

14:42:47.354 [RMI TCP Connection(2)-127.0.0.1] INFO  o.s.web.servlet.DispatcherServlet - FrameworkServlet 'dispatcher': initialization completed in 1674475 ms

这里顺便解释一下SpringMVC中的Servlet的三个层次:


1.HttpServletBean直接继承自java的HttpServlet,其作用是将Servlet中配置的参数设置到相应的Bean属性上

2.FrameworkServlet初始化了WebApplicationContext

3.DispatcherServlet初始化了自身的9个组件(本文重点)


为了协助理解,画了一个时序图如下:

image.png


配置多个DispatcherServletServlet


从本文中我们看到Spring的容器存在父子容器的。因此我们可以很容器的配置多个web子容器,然后父容器都是Root容器,这是被允许的。


当然一般情况下,定义多个 dispatcherservlect 实际上是没有什么特别的用处的。但能够解决可能存在的jar包内的访问路径冲突问题。


比较常用的一个应用场景为:web请求和rest请求分离处理。

比如/rest/api/v1/全部为rest风格的请求,返回json数据不返回页面,交给一个。

/page/api/v1前缀的就返回渲染的页面,交给另外一个

当然静态资源的请求,也可以用对应的处理方式~~~~~~


Spring MVC是有提供配置多个web子容器的能力的,但是使用的时候,路径方面要谨慎处理

ContextLoader类


在继续讲解web.xml方式启动之前,我觉得有必要深入讲解一些ContextLoader这个类。因为从上面我们发现,初始化Spring的根容器,ContextLoaderListener所有事情都是委派给它来完成的,因此我们来讲解一下。


常量解释:

  // init-param定义root根容器id的key
  public static final String CONTEXT_ID_PARAM = "contextId";
  // init-param 定义root根容器的配置文件的路径地址的key
  public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation";
  // init-param 自己可以指定一个WebApplicationContext的实现类(一般都不需要~)
  public static final String CONTEXT_CLASS_PARAM = "contextClass";
  // init-param 可以伴随着容器初始化的时候,我们自己做一些工作的类们。注意需要实现对应接口哦~ key
  public static final String CONTEXT_INITIALIZER_CLASSES_PARAM = "contextInitializerClasses";
  // 基本同上
  public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = "globalInitializerClasses";
  // 多值分隔符号
  private static final String INIT_PARAM_DELIMITERS = ",; \t\n";
  // 默认的配置文件  里面内容只有一句话: org.springframework.web.context.WebApplicationContext=org.springframework.web.context.support.XmlWebApplicationContext
  // 由此课件,它默认是采用XmlWebApplicationContext来初始化上下文的
  private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties";
  // ContextLoader在被实例化的时候,会执行下面的这个静态代码块。做了一件事:把默认的配置文件加载进来而已
  private static final Properties defaultStrategies;
  static {
    try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
    }
  }
  // 这两个属性主要拿到当前的容器上下文。其中static的工具方法ContextLoader.getCurrentWebApplicationContext是基于此的
  private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = new ConcurrentHashMap<>(1);
  @Nullable
  private static volatile WebApplicationContext currentContext;


有了上面的 一些配置:这句


if (this.context == null) {
    this.context = createWebApplicationContext(servletContext);
}


这时一般就会创建前点所述的XmlWebApplicationContext,至于我们怎么替换,下文会有介绍


最后介绍这个类的一个静态方法:

  public static WebApplicationContext getCurrentWebApplicationContext() {
    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    if (ccl != null) {
      WebApplicationContext ccpt = currentContextPerThread.get(ccl);
      if (ccpt != null) {
        return ccpt;
      }
    }
    return currentContext;
  }

此静态方法,可以在任何地方都获取到Spring容器(根容器),非常好用。

我们知道还有一种方法如下:


ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext()); 


这个毕竟要请求与request对象,相对来说麻烦点,比如在Service层呢,就不太好获取了嘛,所以我们就可以用这个啦


    @Autowired
    private HttpServletRequest request;
    @Override
    public Object hello() {
        ApplicationContext ctx1 = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
        WebApplicationContext ctx2 = ContextLoader.getCurrentWebApplicationContext();
        System.out.println(ctx1); //Root WebApplicationContext: startup date ...
        System.out.println(ctx1 == ctx2); //true
        return "service hello";
    }


由此看出,我们以后可以用这种方式来获取容器

FrameworkServlet


DispatcherServlet创建自己的WebApplicationContext并管理这个WebApplicationContext里面的 handlers/controllers/view-resolvers


简单理解,功能有点想ContextLoader。FrameworkServlet实现了ApplicationContextAware接口的setApplicationContext()方法,可知DispatcherServlet的applicationContext来自FrameworkServlet。

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) {
    if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
      this.webApplicationContext = (WebApplicationContext) applicationContext;
      this.webApplicationContextInjected = true;
    }
  }

注意,注意,注意一句doc:Primarily added to support use in embedded servlet containers.,它是Spring4.0之后有的,只用于嵌入式的Servlet环境。war同期环境这里是不会执行的,因为它并不会以Bean的形式存在于Spring容器(说白了,就是init方法里的这段代码) image.png


生效了,就能注入了~


一些常量介绍:


//getNameSpace会返回你在web.xml中配置的servlet-name加上"-servlet",这个namespace会在之后application context加载spring MVC配置文件时候用到
//比如你给servlet取名叫 SpringMVCServlet,那么当Spring MVC初始化的时候,会去寻找名为/WEB-INF/SpringMVCServlet-servlet.xml的配置文件。  
// 不过个人建议:还是配置上配置文件比较好  不要用默认的
public static final String DEFAULT_NAMESPACE_SUFFIX = "-servlet";
public String getNamespace() {
  return (this.namespace != null ? this.namespace : getServletName() + DEFAULT_NAMESPACE_SUFFIX);
}
// 默认的容器类型。若没有配置contextClass就用它
public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;
// 这些值,都可以通过将Servlet初始化参数(init-param)设置到该组件上(如contextAttribute、contextClass、namespace、contextConfigLocation)
@Nullable
private String contextAttribute;
private Class<?> contextClass = DEFAULT_CONTEXT_CLASS;
@Nullable
private String contextId;
@Nullable
private String namespace;
@Nullable
private String contextConfigLocation;



相关文章
|
1天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
17天前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
|
17天前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
16天前
|
Java BI API
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
这篇文章介绍了如何在Spring Boot项目中整合iTextPDF库来导出PDF文件,包括写入大文本和HTML代码,并分析了几种常用的Java PDF导出工具。
157 0
spring boot 整合 itextpdf 导出 PDF,写入大文本,写入HTML代码,分析当下导出PDF的几个工具
|
17天前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
33 0
|
17天前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
57 0
|
9天前
|
存储 Docker 容器
docker中挂载数据卷到容器
【10月更文挑战第12天】
31 5
|
2天前
|
存储 Kubernetes C++
Kubernetes VS Docker Swarm:哪个容器编排工具更适合你?
随着容器技术的快速发展,容器编排工具成为了现代软件开发和运维的重要环节。在众多容器编排工具中,Kubernetes和Docker Swarm无疑是最受欢迎的两个。本文将从技术特性、易用性和社区支持三个方面,对Kubernetes和Docker Swarm进行比较,以帮助您选择更适合您需求的容器编排工具。
12 3
|
3天前
|
存储 缓存 Docker
docker中挂载数据卷到容器
【10月更文挑战第16天】
14 2
|
5天前
|
存储 关系型数据库 MySQL

热门文章

最新文章