spring 容器是靠 listener 启动的,因为 listener 优先于 servlet 启动,所以先创建 spring 容器,称之为容器 A 。 dispatcherServlet 是随 servlet 启动的,dispatcherServlet 启动时会在初始化方法里创建容器,称之为容器 B,并从 ServletContext 内获取容器 A, 将容器 A 设为容器 B 的父容器,这样就存在父子两个容器。
同样也是先创建的 spring 容器,称之为容器 A,然后 dispatcherServlet 是以 Bean 的形式自动装配, 因为 dispatcherServlet 实现了 ApplicationContextAware 接口,所以会将容器 A 设置到 dispatcherServlet 里面,这样 springmvc 本身就不会再次 创建子容器,共享 spring 的容器,只有一个容器。
以前非 springboot 的情况下,springmvc 会创建子容器,挂在 spring 容器下面。 现在有了 springboot,springboot 启动时,直接将自己的容器设置给了 springmvc,这样 springmvc 就不会创建子容器了。 请教各位彦祖们,这样的理解应该是对的吧。
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 才是父容器 |
2
git00ll OP @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; } |
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 |