• Spring MVC 四:Context层级


    这一节我们来回答上篇文章中避而不谈的有关什么是RootApplicationContext的问题。

    这就需要引入Spring MVC的有关Context Hierarchy的问题。Context Hierarchy意思就是Context层级,既然说到Context层级,说明在Spring MVC项目中,可能(或者说可以)存在不止一个Context。

    Context是上下文的意思,我们可以直接理解为容器,上一篇文章我们提到了ServletApplicationContext的概念,可以理解为Servlet容器。

    除此之外,Spring项目中肯定还有IoC容器,我们今天就来聊一下这两个容器之间的关系。

    为什么需要容器

    我们可以这样理解:凡是具有生命周期的对象,也就是说对象并不是在当前线程创建、使用完成之后就销毁的,而是在当前线程使用完成之后不销毁、其他线程还需要继续使用的。这种对象就需要一个容器来保存。

    比如Spring的单例Bean,在Spring初始化的过程中就会创建并存入Ioc容器,之后应用使用过程中从Ioc容器中获取。

    Servlet对象(比如DispatchServlet)也是这样,在Web应用初始化的过程中创建,Controller、ViewResolver、ExceptionHandler等对象随之也完成创建,这些对象在整个应用的生命周期中会反复使用,因此,Servlet相关的对象也必须要有一个容器来存储。

    Spring把容器称之为上下文,Context。

    我们可以把Servlet相关的容器叫做WebApplicationContext,因为Servlet的出现就是为了解决Web应用的,所以自然而然的,我们可以把Servlet容器称为WebApplicationContext。

    相对应的,Spring Ioc容器,我们可以称之为RootApplicationContext:根容器。

    Servlet和根容器的关系

    下图一目了然的说明了两者之间的关系:
    在这里插入图片描述

    Servlet容器存放Controller、VIewResolver、HanderMapping等DispatcherServlet的相关对象,根容器可以存放其他Service、Repositories等对象。

    一个DispatcherServlet可以对应的有一个Servlet容器,一个Web应用可以有多个DispatcherServlet(这种应用其实比较少见),所以一个应用可以有多个Servlet容器。但是一般情况下,即使有多个Servlet容器,一个应用也希望只有一个根容器,以便在不同的Servlet容器之间共享根容器的对象。

    举例

    我们下面用几个例子来说明两者之间的关系。

    还是延用上一篇文章的例子,并做如下简单的改造。

    首先,增加一个单例bean,以便启用Spring IoC容器。我们只是简单引入IoC容器,单例bean不需要太复杂,

    package org.example.service;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Component;
    
    @Component
    public class UserService {
        @Autowired
        ApplicationContext app;
        public String getUserInfo(){
            System.out.println(" application in UserService:"+ app);
            return "This is userinfo...from UserService...";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    只有一个方法,返回String。不过为了能说明当前单例Bean所处的容器,我们通过@Autowired引入ApplicationContext对象(希望大家还记得这一点,我们前面讲过通过什么样的方式,能够在应用中拿到Bean所处的Application对象),这样的话我们就能够知道当前Spring应用的Ioc容器具体是哪个对象。

    其次,我们需要新增一个Spring Ioc容器的配置类,我们称之为RootConfiguration,配置类仅指定扫描路径即可:

    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan("org.example.service")
    public class RootConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后,Controller改造一下,与UserService一样,引入ApplicationContext(Controller中的ApplicationContext,我们可以人为他就是Servlet容器),log打印一下具体的Context,同时我们打印一下当前Servlet容器的父容器:

    package org.example.controller;
    
    import org.example.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.servlet.DispatcherServlet;
    import org.springframework.web.servlet.ModelAndView;
    
    @Controller
    public class HelloWorldController {
        @Autowired
        UserService userService;
        @Autowired
        ApplicationContext app;
        @GetMapping("/hello")
        @ResponseBody
        public String hello(ModelAndView model){
            DispatcherServlet d;
            String userInfo = userService.getUserInfo();
            System.out.println("app in controller:"+app);
            System.out.println("servletContext's parent Context"+app.getParent());
            return "

    "+userInfo+"

    "; } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    OK,准备工作完成,开始验证。

    举例1 根容器不是必须存在

    首先,根容器不是必须存在的。

    但是由于我们增加了UserService这个bean,所以必须有Ioc容器,我们必须为IoC容器指定配置文件的包扫描路径。

    既然我们说根容器不是必须存在,那么,意思就是说,Servlet容器要同时充当IoC容器的角色。

    所以我们必须在MvcConfiguration中增加包扫描路径:

    package org.example.configuration;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan({"org.example.service","org.example.controller"})
    //@ComponentScan({"org.example.controller"})
    public class MvcConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    MvcInitializer中的getRootConfigClasses()返回null,则应用初始化的过程中就不会创建根容器(通过查看createRootApplicationContext方法源码得出的结论):

    public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class[] getRootConfigClasses() {
            return null;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    启动应用,测试结果:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XBftrdeQ-1693234193599)(/img/bVc9m07)]
    说明应用是可以正常运行的,后台log:

     application in UserService:WebApplicationContext for namespace 'dispatcher-servlet', started on Tue Aug 22 22:35:34 CST 2023
    app in controller:WebApplicationContext for namespace 'dispatcher-servlet', started on Tue Aug 22 22:35:34 CST 2023
    servletContext's parent Contextnull
    
    • 1
    • 2
    • 3

    后台log说明,Servlet容器和Spring IoC容器是同一个,他们的父容器是null。

    指定Ioc容器为父容器

    首先修改MvcConfiguration,指定Servlet容器只扫描Controller包:

    package org.example.configuration;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan({"org.example.controller"})
    public class MvcConfiguration {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    MvcInitializer中的getRootConfigClasses()返回我们新增的RootConfiguration,则应用初始化的过程中就会创建根容器:

    public class MvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
        @Override
        protected Class[] getRootConfigClasses() {
            return new Class[] {RootConfiguration.class};
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    启动应用,测试:

     application in UserService:Root WebApplicationContext, started on Tue Aug 22 22:44:08 CST 2023
    app in controller:WebApplicationContext for namespace 'dispatcher-servlet', started on Tue Aug 22 22:44:09 CST 2023, parent: Root WebApplicationContext
    servletContext's parent ContextRoot WebApplicationContext, started on Tue Aug 22 22:44:08 CST 2023
    
    • 1
    • 2
    • 3

    由测试结果可知:
    1. UserService所处的是根容器。
    2. Controller所处的是WebApplicationContext容器。
    3. WebApplicationContext容器的父容器是根容器。

    测试结果验证了我们前面的推论,而且,Spring底层自动将Servlet容器的父容器设置为了根容器!在什么地方设置的?

    Spring MVC框架在DispatcherServlet的初始化过程中(init方法),initWebApplicationContext的时候设置ServletContext的父容器为根容器,下一节分析DispatcherSevlet初始化代码的时候会有详细说明。

    上一篇 Spring MVC 三 :基于注解配置

  • 相关阅读:
    Git 的基本概念和使用方式以及常见错误的处理方法
    Centos - openldap
    关于Maven的笔记
    Git 学习笔记 | Git 基本操作命令
    个人博客网站一揽子:Docker建站(Nginx、Wordpress、MySql)
    RHCA之路---EX280(8)
    yolov3原理记录
    golang中常用的空接口数据类型转换方法
    【无标题】
    网络编程扩展
  • 原文地址:https://blog.csdn.net/weixin_44612246/article/details/132549945