SpringMvc在SpringBoot环境和Web环境中上下文的关系
之前有人在我的项目中提出issue,咨询在SpringBoot中的bean是不是由谁来管理的问题(SpringMvc还是Spring)来管理(https://github.com/cmlbeliever/SpringBootLearning/issues/2)其实一开始我也是挺懵逼的,之前没有怎么了解过这些细节,既然提出问题了,当然要找出个所以然。在Web环境中,是分为SpringMvc管理的子容
之前有人在我的项目中提出issue,咨询在SpringBoot中的bean是不是由谁来管理的问题(SpringMvc还是Spring)来管理(https://github.com/cmlbeliever/SpringBootLearning/issues/2)
其实一开始我也是挺懵逼的,之前没有怎么了解过这些细节,既然提出问题了,当然要找出个所以然。
在Web环境中,是分为SpringMvc管理的子容器,和Spring管理的父容器。父子容器的关系就像类的继承一样,子容器可以获取父容器的bean,反之则是不可以的。
如何验证是两个容器呢?
只要在Controller与Service中实现ApplicationContextAware接口,就可已得知对应的管理容器:
Controller中:
Service中:
那么在SpringBoot中则是同一个容器管理的(AnnotationConfigEmbeddedWebApplicationContext):
Controller:
Service:
可以看出在boot环境中使用的是相同的容器管理的,因为AnnotationConfigEmbeddedWebApplicationContext是在包org.springframework.boot.context.embedded中的,那么就可以理解为boot容器管理bean。(当然也可以理解为Spring管理,因为AnnotationConfigEmbeddedWebApplicationContext也只是拓展WebApplicationContext)。
由于SpringMvc的入口都是DispatcherServlet,那是什么原因造成在Web环境中有父子容器,在boot环境中只有一个容器呢?这个当然需要剖析源码咯:
直接debug就可以了,在FrameworkServlet.initWebApplicationContext方法中初始化web上下文环境。
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//因为调用时Spring容器不是webApplicationContext类型的,所以在webApplicationContext对象时为空
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();
}
//这里是重点,从这里新建SpringMvc容器
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;
}
当web容器文不存在时,会去创建一个新的web容器:
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
//设置父容器,这里的parent即为spring上下文
wac.setParent(parent);
wac.setConfigLocation(getContextConfigLocation());
configureAndRefreshWebApplicationContext(wac);
return wac;
}
当web容器初始化成功后,发出ContextRefreshedEvent事件,
DispatcherServlet接收到事件后调用initStrategies方法进行web相关的mapping和adapter设置
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
在SpringBoot环境中,其他流程都是一致的,但是用的容器是AnnotationConfigEmbeddedWebApplicationContext而且还是WebApplicationContext子类,在FrameworkServlet.setApplicationContext时,就对webApplicationContext 进行了赋值
public void setApplicationContext(ApplicationContext applicationContext) {
if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) {
this.webApplicationContext = (WebApplicationContext) applicationContext;
this.webApplicationContextInjected = true;
}
}
所以在initWebApplicationContext方法初始化的时候用的就是这个全局的上下文,没有新建一个SpringMvc专用的Web上下文
总结:
在Web环境中是由Spring和SpringMvc两个容器组成的,在SpringBoot环境中只有一个容器AnnotationConfigEmbeddedWebApplicationContext。也就是可以说是由SpringBoot容器管理的。
SpringBootLearning是对springboot学习与研究项目,是根据实际项目的形式对进行配置与处理,欢迎star与fork。
[oschina 地址]
http://git.oschina.net/cmlbeliever/SpringBootLearning
[github 地址]
https://github.com/cmlbeliever/SpringBootLearning
更多推荐
所有评论(0)