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

2021-02-09 13:43:52 +08:00
 git00ll

以前没使用 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 就不会创建子容器了。 请教各位彦祖们,这样的理解应该是对的吧。

1808 次点击
所在节点    Java
4 条回复
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
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
2021-02-10 12:28:55 +08:00
直接看源码,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
2021-02-18 09:38:11 +08:00
@young1lin 写的非常清晰 感谢~

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/752608

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX