V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
git00ll
V2EX  ›  Java

各位彦祖们,关于 spring 和 springmvc 不知道这样理解对不对

  •  
  •   git00ll · 2021-02-09 13:43:52 +08:00 · 1789 次点击
    这是一个创建于 1143 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以前没使用 springboot 的时候

    spring 容器是靠 listener 启动的,因为 listener 优先于 servlet 启动,所以先创建 spring 容器,称之为容器 A 。 dispatcherServlet 是随 servlet 启动的,dispatcherServlet 启动时会在初始化方法里创建容器,称之为容器 B,并从 ServletContext 内获取容器 A, 将容器 A 设为容器 B 的父容器,这样就存在父子两个容器。

    而现在到了 pringboot 的情况下

    同样也是先创建的 spring 容器,称之为容器 A,然后 dispatcherServlet 是以 Bean 的形式自动装配, 因为 dispatcherServlet 实现了 ApplicationContextAware 接口,所以会将容器 A 设置到 dispatcherServlet 里面,这样 springmvc 本身就不会再次 创建子容器,共享 spring 的容器,只有一个容器。

    总结就是:

    以前非 springboot 的情况下,springmvc 会创建子容器,挂在 spring 容器下面。 现在有了 springboot,springboot 启动时,直接将自己的容器设置给了 springmvc,这样 springmvc 就不会创建子容器了。 请教各位彦祖们,这样的理解应该是对的吧。

    4 条回复    2021-02-18 09:38:11 +08:00
    huifer
        1
    huifer  
       2021-02-09 15:58:17 +08:00
    Spring 容器的类型取决于启动类使用的是什么,如果是 `ClassPathXmlApplicationContext` 那么上下文类型是这个,如果是 `FileSystemXmlApplicationContext` 那么上下文类型是这个。
    Spring MVC 中的容器类型是 `XmlWebApplicationContext` 至于 dispatcherServlet 它只是做请求转发,容器的启动本身还是 XmlWebApplicationContext,ApplicationContextAware 接口的实现只是生命周期中的一环。
    SpringBoot 中对于容器上下文的定义如下

    protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
    try {
    switch (this.webApplicationType) {
    case SERVLET:
    contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
    break;
    case REACTIVE:
    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    break;
    default:
    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
    }
    }
    catch (ClassNotFoundException ex) {
    throw new IllegalStateException(
    "Unable create a default ApplicationContext, "
    + "please specify an ApplicationContextClass",
    ex);
    }
    }
    return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }

    父子容器应该是说一个单纯的 ApplicationContext 在 `ClassPathXmlApplicationContext` 中有构造函数

    public ClassPathXmlApplicationContext(
    String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
    throws BeansException {

    super(parent);
    // 设置本地配置信息
    setConfigLocations(configLocations);
    if (refresh) {
    refresh();
    }
    }

    这里的 parent 才是父容器
    git00ll
        2
    git00ll  
    OP
       2021-02-09 16:43:48 +08:00
    @huifer
    以下代码来自于
    org.springframework.web.servlet.FrameworkServlet#initWebApplicationContext
    DispatcherServlet 继承此类。

    下面是他初始化容器的地方,方法的第三行有一个 if 判断,他判断如果 webApplicationContext 不为空,就直接使用。如果为空会在下面创建一个。
    那什么情况下非空呢?据我观察现在使用 springboot 启动时,就是非空的,而以前使用 xml 文件的方式就是空的。所以我认为,使用旧的方式,会产生两个容器,springmvc 容器和 spring 容器,他们是父子关系。而使用 springboot 的方式,是共享同一个容器。


    protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
    WebApplicationContextUtils.getWebApplicationContext(getServletContext());
    WebApplicationContext wac = 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()) {
    // The context has not yet been refreshed -> provide services such as
    // setting the parent context, setting the application context id, etc
    if (cwac.getParent() == null) {
    // The context instance was injected without an explicit parent -> set
    // the root application context (if any; may be null) as the parent
    cwac.setParent(rootContext);
    }
    configureAndRefreshWebApplicationContext(cwac);
    }
    }
    }
    if (wac == null) {
    // No context instance was injected at construction time -> see if one
    // has been registered in the servlet context. If one exists, it is assumed
    // that the parent context (if any) has already been set and that the
    // user has performed any initialization such as setting the context id
    wac = findWebApplicationContext();
    }
    if (wac == null) {
    // No context instance is defined for this servlet -> create a local one
    wac = createWebApplicationContext(rootContext);
    }

    if (!this.refreshEventReceived) {
    // Either the context is not a ConfigurableApplicationContext with refresh
    // support or the context injected at construction time had already been
    // refreshed -> trigger initial onRefresh manually here.
    onRefresh(wac);
    }

    if (this.publishContext) {
    // Publish the context as a servlet context attribute.
    String attrName = getServletContextAttributeName();
    getServletContext().setAttribute(attrName, wac);
    if (this.logger.isDebugEnabled()) {
    this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
    "' as ServletContext attribute with name [" + attrName + "]");
    }
    }

    return wac;
    }
    young1lin
        3
    young1lin  
       2021-02-10 12:28:55 +08:00   ❤️ 1
    直接看源码,Spring 和 Spring MVC 的确实是那样的,会优先在父容器里面找 Bean,找得到,找不到再去子容器。所谓的父子容器,其实是有实现那个层次性接口 HierarchicalBeanFactory 。ApplicationContext 默认继承这个接口,所以所有类型的 ApplicationContext 默认都是有层次性结构的,就看你子类有没有真的加了 parentBeanFactory 。

    Spring Boot 的话,我看了那个 SpringApplication 那个类,就是根据你那个 webApplication 的类来反射找当前是什么类型的应用,是 Servlet 的还是 WebFlux 的,然后 createApplicationContext,就是反射创建一个。更深层次的规则其实如下的:

    1.当 DispatcherHandler 存在时,并且 DispatcherServlet 不存在时,这时为 Reactive 应用,就是仅依赖 WebFlux 时。
    2.当 Servlet 和 ConfigurableWebApplicationContext 均不存在时,当前应用为非 Web 应用,即 WebApplicationType.NONE,因为这些是 Spring Web MVC 必需的依赖。
    3.当 Spring WebFlux 和 Spring Web MVC 同时存在时,还是 Servlet 应用。

    这是我稍微整理的 Spring Boot 相关的思维导图。

    https://www.processon.com/view/link/5ff6897f07912930e0207859
    SkyLine7
        4
    SkyLine7  
       2021-02-18 09:38:11 +08:00
    @young1lin 写的非常清晰 感谢~
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1179 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 18:20 · PVG 02:20 · LAX 11:20 · JFK 14:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.