SpringMVC 初始化流程分析

所以前面的 bw 实际上就代表当前 DispatcherServlet 对象。

通过 BeanWrapper 修改目标 Servlet 的相关属性时,有一个 initBeanWrapper 方法是空方法,开发者如有需要可以在子类中实现该方法,并且完成一些初始化操作。

属性配置完成后,最终调用 initServletBean 方法进行 Servlet 初始化,然而该方法也是一个空方法,在子类中实现。

这就是 HttpServletBean 所做的事情,比较简单,加载 Servlet 相关属性并设置给当前 Servlet 对象,然后调用 initServletBean 方法继续完成 Servlet 的初始化操作。

2.FrameworkServlet


从前面的介绍可知,FrameworkServlet 初始化的入口方法就是 initServletBean,因此我们就从 FrameworkServlet#initServletBean 方法开始看起:

@Override

protected final void initServletBean() throws ServletException {

//省略…

try {

this.webApplicationContext = initWebApplicationContext();

initFrameworkServlet();

}

catch (ServletException | RuntimeException ex) {

//省略…

}

}

这个方法原本挺长的,但是抛开日志打印异常抛出,剩下的核心代码其实就两行:

  1. initWebApplicationContext 方法用来初始化 WebApplicationContext。

  2. initFrameworkServlet 方法用来初始化 FrameworkServlet,但是这个方法是一个空方法,没有具体的实现。本来子类可以重写该方法做一些初始化操作,但是实际上子类并没有重写该方法,所以这个方法我们就暂且忽略之,不去分析了。

那么这里最为重要的其实就是 initWebApplicationContext 方法了,我们一起来看下:

protected WebApplicationContext initWebApplicationContext() {

WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContext wac = null;

if (this.webApplicationContext != null) {

wac = this.webApplicationContext;

if (wac instanceof ConfigurableWebApplicationContext) {

ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;

if (!cwac.isActive()) {

if (cwac.getParent() == null) {

cwac.setParent(rootContext);

}

configureAndRefreshWebApplicationContext(cwac);

}

}

}

if (wac == null) {

wac = findWebApplicationContext();

}

if (wac == null) {

wac = createWebApplicationContext(rootContext);

}

if (!this.refreshEventReceived) {

synchronized (this.onRefreshMonitor) {

onRefresh(wac);

}

}

if (this.publishContext) {

String attrName = getServletContextAttributeName();

getServletContext().setAttribute(attrName, wac);

}

return wac;

}

这里的逻辑也比较清晰:

  1. 首先获取 rootContext。在默认情况下,Spring 会将容器设置为 ServletContext 的一个属性,属性的 key 为 org.springframework.web.context.WebApplicationContext.ROOT,所以根据这个 key 就可以调用 ServletContext#getAttribute 方法获取到 rootContext 了。

  2. 获取 WebApplicationContext 实例,也就是给 wac 变量赋值的过程,这里存在三种可能性:1.如果已经通过构造方法给 webApplicationContext 赋值了,则直接将其赋给 wac 变量,同时,如果需要设置 parent 就设置,需要刷新就刷新。这种方式适用于 Servlet3.0 以后的环境,因为从 Servlet3.0 开始,才支持直接调用 ServletContext.addServlet 方法去注册 Servlet,手动注册的时候就可以使用自己提前准备好的 WebApplicationContext 了,这块松哥在我录制的 Spring Boot 视频中也讲过,感兴趣的小伙伴可以在公众号后台回复 vhr 查看视频详情;2.如果第一步没能成功给 wac 赋值,那么调用 findWebApplicationContext 方法尝试去 ServletContext 中查找 WebApplicationContext 对象,找到了就赋值给 wac;3.如果第二步没能成功给 wac 赋值,那么调用 createWebApplicationContext 方法创建一个 WebApplicationContext 对象并赋值给 wac,一般来说都是通过这种方式创建的 WebApplicationContext。这三套组合拳下来,wac 肯定是有值了。

  3. 当 ContextRefreshedEvent 事件没有触发时,调用 onRefresh 方法完成容器刷新(由于第一种和第三种获取 WebApplicationContext 的方式最终都会调用 configureAndRefreshWebApplicationContext 方法,然后发布事件,再将 refreshEventReceived 变量标记为 true,所以实际上只有第二种方式获取 wac 实例的时候,这里才会刷新,具体可以看下文分析)。

  4. 最后将 wac 保存到到 ServletContext 中。保存的时候会根据 publishContext 变量的值来决定是否保存,publishContext 可以在 web.xml 中配置 Servlet 时通过 init-param 进行配置,保存的目的是为了方便获取。

上面的这些步骤中,通过 createWebApplicationContext 方法创建 WebApplicationContext 对象需要和大家细说下,因为一般情况下就是通过这种方式创建的 WebApplicationContext。我们来看一下相关的方法:

protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {

Class<?> contextClass = getContextClass();

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());

wac.setParent(parent);

String configLocation = getContextConfigLocation();

if (configLocation != null) {

wac.setConfigLocation(configLocation);

}

configureAndRefreshWebApplicationContext(wac);

return wac;

}

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {

if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

// The application context id is still set to its original default value

// -> assign a more useful id based on available information

if (this.contextId != null) {

wac.setId(this.contextId);

}

else {

// Generate default id…

wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +

ObjectUtils.getDisplayString(getServletContext().getContextPath()) + ‘/’ + getServletName());

}

}

wac.setServletContext(getServletContext());

wac.setServletConfig(getServletConfig());

wac.setNamespace(getNamespace());

wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

// The wac environment’s #initPropertySources will be called in any case when the context

// is refreshed; do it eagerly here to ensure servlet property sources are in place for

// use in any post-processing or initialization that occurs below prior to #refresh

ConfigurableEnvironment env = wac.getEnvironment();

if (env instanceof ConfigurableWebEnvironment) {

((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());

}

postProcessWebApplicationContext(wac);

applyInitializers(wac);

wac.refresh();

}

这里一共涉及到两个方法:

createWebApplicationContext

首先获取到创建类型,并检查创建类型,没问题的话调用 instantiateClass 方法完成创建工作,然后给创建好的 wac 对象配置各种属性,配置的 configLocation 就是我们在 web.xml 文件中配置的 SpringMVC 配置文件路径,默认的文件路径是 /WEB-INF/[servletName]-servlet.xml

configureAndRefreshWebApplicationContext

configureAndRefreshWebApplicationContext 方法主要也是配置&刷新 WebApplicationContext,在这个方法里会调用 addApplicationListener 为 wac 添加一个监听器,监听的是 ContextRefreshedEvent 事件,当收到该事件后,会调用 FrameworkServlet 的 onApplicationEvent 方法,并在该方法中调用 onRefresh 方法完成刷新,刷新之后,会将 refreshEventReceived 变量标记为 true。

public void onApplicationEvent(ContextRefreshedEvent event) {

this.refreshEventReceived = true;

synchronized (this.onRefreshMonitor) {

onRefresh(event.getApplicationContext());

}

}

这就是 FrameworkServlet#initServletBean 方法的大致工作逻辑。这里涉及到了 onRefresh 方法,但是这是一个空方法,在子类 DispatcherServlet 中实现了,所以接下来我们就来看 DispatcherServlet。

3.DispatcherServlet


这里我们就不废话了,直接来看 onRefresh 方法,如下:

@Override

protected void onRefresh(ApplicationContext context) {

initStrategies(context);

}

protected void initStrategies(ApplicationContext context) {

initMultipartResolver(context);

initLocaleResolver(context);

initThemeResolver(context);

initHandlerMappings(context);

initHandlerAdapters(context);

initHandlerExceptionResolvers(context);

initRequestToViewNameTranslator(context);

initViewResolvers(context);

initFlashMapManager(context);

}

在 onRefresh 方法中调用了 initStrategies 进行初始化操作。initStrategies 的内容其实很简单,就是九个组件的初始化。九个的初始化流程比较类似,这里我们以常见的视图解析器的初始化方法 initViewResolvers 为例,来一起看看初始化流程:

private void initViewResolvers(ApplicationContext context) {

this.viewResolvers = null;

if (this.detectAllViewResolvers) {

// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.

Map<String, ViewResolver> matchingBeans =

BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);

if (!matchingBeans.isEmpty()) {

this.viewResolvers = new ArrayList<>(matchingBeans.values());

// We keep ViewResolvers in sorted order.

AnnotationAwareOrderComparator.sort(this.viewResolvers);

}

}

else {

try {

ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);

this.viewResolvers = Collections.singletonList(vr);

}

catch (NoSuchBeanDefinitionException ex) {

// Ignore, we’ll add a default ViewResolver later.

}

}

// Ensure we have at least one ViewResolver, by registering

// a default ViewResolver if no other resolvers are found.

if (this.viewResolvers == null) {

this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);

if (logger.isTraceEnabled()) {

logger.trace(“No ViewResolvers declared for servlet '” + getServletName() +

“': using default strategies from DispatcherServlet.properties”);

}

}

}

一开始的 viewResolvers 变量是一个集合,解析出来的视图解析器对象都将放入这个集合中。

首先判断 detectAllViewResolvers 变量是否为 true,如果为 true,则直接去查找 Spring 容器中的所有视图解析器,将查找结果赋值给 viewResolvers,然后进行排序。默认情况下 detectAllViewResolvers 变量的值为 true,如果有需要,可以在 web.xml 中进行配置,像下面这样:

springmvc

org.springframework.web.servlet.DispatcherServlet

contextConfigLocation

classpath:spring-servlet.xml

detectAllViewResolvers

false

1

springmvc

/

如果 detectAllViewResolvers 的值为 false,那么接下来就会去 Spring 容器中查找一个名为 viewResolver 的视图解析器,此时查找到的就是一个单独的视图解析器。

一般来说,我们并不需要在 web.xml 中去配置 detectAllViewResolvers 的值,视图解析器有多少个就加载多少个。

举个简单例子,我们在 SpringMVC 的配置文件中可能像下面这样配置视图解析器:

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

image

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

image

还有源码相关的阅读学习

image

了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

[外链图片转存中…(img-DtTAOtvL-1714470049677)]

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

[外链图片转存中…(img-zDKwTk5b-1714470049677)]

还有源码相关的阅读学习

[外链图片转存中…(img-0wxoonig-1714470049678)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值