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

简介: 【小家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;



相关文章
|
存储 Java 文件存储
微服务——SpringBoot使用归纳——Spring Boot使用slf4j进行日志记录—— logback.xml 配置文件解析
本文解析了 `logback.xml` 配置文件的详细内容,包括日志输出格式、存储路径、控制台输出及日志级别等关键配置。通过定义 `LOG_PATTERN` 和 `FILE_PATH`,设置日志格式与存储路径;利用 `&lt;appender&gt;` 节点配置控制台和文件输出,支持日志滚动策略(如文件大小限制和保存时长);最后通过 `&lt;logger&gt;` 和 `&lt;root&gt;` 定义日志级别与输出方式。此配置适用于精细化管理日志输出,满足不同场景需求。
3126 1
|
XML Java 数据库连接
微服务——SpringBoot使用归纳——Spring Boot集成MyBatis——基于 xml 的整合
本教程介绍了基于XML的MyBatis整合方式。首先在`application.yml`中配置XML路径,如`classpath:mapper/*.xml`,然后创建`UserMapper.xml`文件定义SQL映射,包括`resultMap`和查询语句。通过设置`namespace`关联Mapper接口,实现如`getUserByName`的方法。Controller层调用Service完成测试,访问`/getUserByName/{name}`即可返回用户信息。为简化Mapper扫描,推荐在Spring Boot启动类用`@MapperScan`注解指定包路径避免逐个添加`@Mapper`
968 0
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
704 70
|
9月前
|
设计模式 Java 开发者
如何快速上手【Spring AOP】?从动态代理到源码剖析(下篇)
Spring AOP的实现本质上依赖于代理模式这一经典设计模式。代理模式通过引入代理对象作为目标对象的中间层,实现了对目标对象访问的控制与增强,其核心价值在于解耦核心业务逻辑与横切关注点。在框架设计中,这种模式广泛用于实现功能扩展(如远程调用、延迟加载)、行为拦截(如权限校验、异常处理)等场景,为系统提供了更高的灵活性和可维护性。
1327 0
|
XML Java 数据格式
【SpringFramework】Spring IoC-基于XML的实现
本文主要讲解SpringFramework中IoC和DI相关概念,及基于XML的实现方式。
343 69
|
XML Java 数据格式
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
本文介绍了在使用Spring框架时,如何通过创建`applicationContext.xml`配置文件来管理对象。首先,在resources目录下新建XML配置文件,并通过IDEA自动生成部分配置。为完善配置,特别是添加AOP支持,可以通过IDEA的Live Templates功能自定义XML模板。具体步骤包括:连续按两次Shift搜索Live Templates,配置模板内容,输入特定前缀(如spring)并按Tab键即可快速生成完整的Spring配置文件。这样可以大大提高开发效率,减少重复工作。
1083 1
使用idea中的Live Templates自定义自动生成Spring所需的XML配置文件格式
|
监控 Java 应用服务中间件
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
588 6
|
监控 JavaScript 数据可视化
建筑施工一体化信息管理平台源码,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
智慧工地云平台是专为建筑施工领域打造的一体化信息管理平台,利用大数据、云计算、物联网等技术,实现施工区域各系统数据汇总与可视化管理。平台涵盖人员、设备、物料、环境等关键因素的实时监控与数据分析,提供远程指挥、决策支持等功能,提升工作效率,促进产业信息化发展。系统由PC端、APP移动端及项目、监管、数据屏三大平台组成,支持微服务架构,采用Java、Spring Cloud、Vue等技术开发。
623 7
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
363 0