目录
DispatcherServlet类图
Web应用
启动的最后一个步骤就是创建和初始化相关Servlet
,我们配置了DispatcherServlet类
前端控制器,前端控制器作为中央控制器是整个Web应用
的核心,用于获取分发用户请求并返回响应。
通过类图可以看出DispatcherServlet类
的间接父类实现了Servlet接口
,因此其本质上依旧是一个Servlet
HttpServletBean#init
DispatcherServelt
类的本质是Servlet
,所以在Web应用
部署到容器后进行Servlet
初始化时会调用相关的init(ServletConfig)
方法,因此,DispatchServlet类
的初始化过程也由该方法开始:
(注意:DispatcherServelt
没有init方法,会走到父类HttpServletBean
的init方法)
/**
* DispatcherServlet 初始化入口
* Map config parameters onto bean properties of this servlet, and
* invoke subclass initialization.
* @throws ServletException if bean properties are invalid (or required
* properties are missing), or if subclass initialization fails.
*/
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
/*
* 1.加载初始化参数,如:
* <servlet>
* <servlet-name>DispatcherServlet</servlet-name>
* <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
* <init-param>
* <param-name>name</param-name>
* <param-value>zimu</param-value>
* </init-param>
* <load-on-startup>1</load-on-startup>
* </servlet>
* 这里会解析init-param列表。
*/
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
// 2.将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// 3.注册自定义属性编辑器,一旦遇到 Resource 类型的属性将会使用 ResourceEditor 进行解析
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 4.空实现,留给子类覆盖
initBeanWrapper(bw);
// 5.属性注入
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// Let subclasses do whatever initialization they like.
// 【重点】6.子类负责实现,主要来初始化ApplicationContext及一些Resolve
initServletBean();
}
new ServletConfigPropertyValues()
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
// 获取 web.xml 中 DispatcherServlet 配置的 init-params 属性内容
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
保存 init-params 属性
addPropertyValue(new PropertyValue(property, value));
if (missingProps != null) {
missingProps.remove(property);
}
}
// Fail if we are still missing properties.
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}
该方法最主要的作用就是初始化init-param
,如果我们没有配置任何init-param
,那么该方法不会执行任何操作。从这里我们没有拿到有用的信息,但是在该方法结尾有initServletBean()
,这是一个模板方法,可以由子类来实现,那么接下来我们就去看其子类FrameworkServlet中
的initServletBean
FrameworkServlet#initServletBean
继续查看 initServletBean()。父类 FrameworkServlet 覆盖了 HttpServletBean 中的 initServletBean 函数,如下:
protected void initServletBean() throws ServletException {
}
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();
try {
//【重点】,用于初始化子ApplicationContext对象,
// 主要是用来加载<servlet/>对应的servletName-servlet.xml文件如:springMVC.xml
this.webApplicationContext = initWebApplicationContext();
// 空的模板方法
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
//当我们看到这句日志,就能知道dispatcherServlet已经初始化完成,web子容器也就初始化完成了
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
其中我们可以只有一个核心方法:initWebApplicationContext()
,主要用来初始化springmvc子容器,其余都是一些日志相关的,关于 initWebApplicationContext
的实现是在 FrameworkServlet#initWebApplicationContext
中完成,下面我们就来看看其实现过程。
initWebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
/*
获取由ContextLoaderListener创建的根IoC容器
获取根IoC容器有两种方法,还可通过key直接获取
*/
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果当前webApplicationContext不为null(context 实例在构造函数中被注入),则为其设置父容器
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
/*
如果当前Servelt存在一个WebApplicationContext即子IoC容器
并且上文获取的根IoC容器存在,则将根IoC容器作为子IoC容器的父容器
*/
cwac.setParent(rootContext);
}
//配置并刷新当前的子IoC容器,用于构建相关Bean
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 未能通过构造函数注入,则尝试去ServletContext容器中查找有无WebApplicationContext
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
//如果当前Servlet不存在一个子IoC容器则去查找一下
wac = findWebApplicationContext();
}
// 以上均无WebApplicationContext,则创建一个新的WebApplicationContext
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.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
//我们是否需要吧我们的容器发布出去,作为ServletContext的一个属性值呢?默认值为true哦
if (this.publishContext