一、继承或实现关系
public class DispatcherServlet extends FrameworkServlet
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
public abstract class HttpServlet extends GenericServlet
public abstract class GenericServlet implements Servlet, ServletConfig, Serializable
public interface Servlet
图解
二、SpringMVC的初始化过程源码分析
调用Servlet接口提供的init()方法进行初始化。由于Servlet接口中的方法都是抽象方法,真正调用的是它的实现类中的实现了init()的方法,从上图中关系得知,GenericServlet类实现了Servlet接口,并且实现了Servlet接口提供的init()方法。
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
在实现了Servlet接口中的init()方法后,该方法还有它的重载方法,并且调用了它的重载方法。
重载的init()方法如下:
public void init() throws ServletException {
}
可以看到init()方法中没有具体的方法体,所以需要关注它的子类。然后它的直接子类HttpServlet并没有重写init()方法,所以我们需要关注HttpServlet的子类,看看它的子类有没有重写init()方法。
从上图中可以看出,HttpServlet的子类HttpServletBean实现了init()方法。
HttpServletBean中init()的源码如下:
@Override
public final void init() throws ServletException {
// 从web.xml中获取servlet的初始化参数,把它添加到List<PropertyValue>
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
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.
initServletBean();
}
我们在web.xml中配置的init-param就是在这个阶段读取到内存中。
1、ProtertyValues
对
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
的研究。
ServletConfigPropertyValues
源码
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
// 从servletConfig对象中获取web.xml中配置的Servlet的初始化参数的名字,即param-name标签里的内容
// 这里 paramNames = contextConfigLocation
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
// 添加到 list 集合中
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, ", "));
}
}
对
addPropertyValue(new PropertyValue(property, value));
的源码分析。
private final List<PropertyValue> propertyValueList;
public MutablePropertyValues addPropertyValue(PropertyValue pv) {
for(int i = 0; i < this.propertyValueList.size(); ++i) {
PropertyValue currentPv = (PropertyValue)this.propertyValueList.get(i);
if (currentPv.getName().equals(pv.getName())) {
pv = this.mergeIfRequired(pv, currentPv);
this.setPropertyValueAt(pv, i);
return this;
}
}
this.propertyValueList.add(pv);
return this;
}
由于刚开始propertyValueList
为空,所以不会进入for
循环。直接执行this.propertyValueList.add(pv);
把形参中的protertyValue
对象放到propertyValueList
集合中。
实际上是把servlet
初始化中的参数封装到list
集合中了。
2、BeanWrapper
BeanWrapper是接口,实际上创建的是它的实现类
BeanWrapperImpl
。作用:对
DispatcherServlet
进行包装。
3、ResourceLoader
资源加载器
作用:加载各种资源。
private final ServletContext servletContext;
public ServletContextResourceLoader(ServletContext servletContext) {
this.servletContext = servletContext;
}
4、注册到自定义编辑器中
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
将ResourceLoader和Environment封装进ResourceEditor。
再将ResourceEditor封装进BeanWrapper。
ResourceEditor
属性编辑器接口,它的作用更像一个转换器,将字符串转换为类对象的属性。
5、initBeanWrapper(bw)
它是一个没有具体方法体的方法。
作用:如果我们想要自定义包装类,我们就可以重写该方法。
protected void initBeanWrapper(BeanWrapper bw) throws BeansException {}
6、bw.setPropertyValues(pvs, true)
把
ProtertyValues
封装到BeanWrapper
中。
通过BeanWrapper
可以访问到Servlet
的所有参数、资源加载器加载的资源以及DispatcherServlet
中的所有属性,并且可以像访问Javabean
一样简单。
7、initServletBean()
最后还调用
initServletBean();
方法。
protected void initServletBean() throws ServletException {}
由于该方法没有方法体,所以需要看该类的子类是否重写了该方法。
HttpServletBean
的子类FrameworkServlet
。
8、分析FrameworkServlet
initServletBean()
源码
@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 {
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);
}
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
这里调用了initWebApplicationContext()
方法。
initWebApplicationContext()
源码
protected WebApplicationContext initWebApplicationContext() {
// 内部判断是否有异常,如果没有,则返回 WebApplicationContext
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.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
wac
刚开始为null
,调用createWebApplicationContext(rootContext)
方法来创建SpringMVC
容器。
createWebApplicationContext(rootContext)
源码
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); // 设置 Application 为父容器
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
configureAndRefreshWebApplicationContext(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();
}
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
设置容器的id
返回到initWebApplicationContext()
方法。
最后得到了wac
对象。
最后返回到调用处initServletBean()
。
9、刷新容器
在
initWebApplicationContext()
中会调用onRefresh(wac)
刷新容器。由于
onRefresh()
方法的方法体为空,所以需要看它的子类重写之后的方法。
FrameworkServlet
的子类DispatcherServlet
中的重写了onRefresh()
方法。
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
DispatcherServlet
中的onRefresh()
方法调用了本类中的initStrategies()
方法。
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
到此,DispatcherServlet
就初始化完成了。
【SpringMVC源码分析】DispatcherServlet初始化源码分析