今天来说说深入一下SpringMVC,在学习SpringMVC源码之前,必须得了解javaEE原生的Servlet的工作流程,以tomcat为例子:在web.xml中配置servlet,指定servlet-class以及servlet-mapping。然后请求过来根据mapping映射到servlet的类,执行其中的doGet、doPost等方法。具体这里就不细说了。
先附上一张UML类图,可以先跳过:
一、从我们的web.xml入手
<!-- Spring mvc 配置的Servelt -->
<servlet>
<servlet-name>springmvc</servlet-name>
<!-- Servlet的类,DispatcherServlet -->
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<!-- 配置初始化参数 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<!-- 配置文件的地址-->
<param-value>classpath:application-context.xml</param-value>
</init-param>
<!-- 让web容器启动之后就加载此类,并且调用init方法 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<!-- 配置拦截的路径 -->
<url-pattern>/</url-pattern>
</servlet-mapping>
这是我们最常见的SpringMVC在web.xml中的配置了,从配置文件中我们可以看到核心类就是:DispatcherServlet。
我们在初始化DispatcherServlet的时候,传入了参数contextConfigLocation,这个属性的意思是:本地上下文配置位置
我们后面看他是如何使用的。然后看mapping,是 / 也就是所有的请求都会过这个DispatcherServlet。
二、web容器启动,调用init方法
进入DispatcherServlet类,发现并没有找到覆盖Servlet接口的init的方法。那么他肯定不是在DispatcherServlet进行初始化的。
然后我们继续往上面找:DispatcherServlet --> FrameworkServlet --> HttpServletBean最终在GenericServlet和HttpServletBean类中找到了覆盖Servlet接口的init方法(只有这个方法里面有内容,其他类下面的都是空方法),内容如下:
GenericServlet中的init方法:
public void init(ServletConfig config) throws ServletException {
this.config = config;
// 这是config,这个类里面包含有我们配置文件中配置的init-param值。
this.init();
}
HttpServletBean中的init方法:
@Override
public final void init() throws ServletException {
// 从init参数设置bean属性。
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;
}
}
// 让子类做他们喜欢的任何初始化。
initServletBean();
}
2.1 从init参数设置bean属性:ServletConfigPropertyValues方法
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
首先看getServletConfig()方法,这个方法是GenericServlet的方法,返回config属性:
private transient ServletConfig config;
public ServletConfig getServletConfig() {
return config;
}
而ServletConfig 接口中有我们的web.xml文件中配置的init-param的key和value。
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
public String getInitParameter(String name);
// 此方法可以获取我们配置文件中的init-param配置
public Enumeration<String> getInitParameterNames();
}
然后我们继续看ServletConfigPropertyValues的构造方法:参数一:ServletConfig ,参数二:requiredProperties设置必须传递的属性
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {
Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);
// 这里就从ServletConfig 里面获取我们配置的值
Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
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, ", "));
}
}
2.2 如果我们在web.xml文件中设置了init-param,那么执行如下代码:
// 从web.xml文件中获取配置
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
// 如果不为空,
if (!pvs.isEmpty()) {
try {
// BeanWrapper 获取BeanWrapper ,这是装饰者模式的一种体现
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 获取ServletConfig,将其赋值给ServletContextResourceLoader的属性:servletContext
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// 将bean注册自定义属性编辑器:ResourceEditor
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;
}
// 执行子类自定义初始化bean的方法,他的子类就包括:FrameworkServlet和DispatcherServlet
initServletBean();
}
2.3 initServletBean()方法:初始化bean,这个方法只有其子类FrameworkServlet进行了实现,DispatcherServlet没有实现
@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();
// 这个是一个空方法,
// 此方法将在设置任何bean属性之后调用
// DispatcherServlet并没有实现他,目前来看,这个方法到是用于扩展的,
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");
}
}
2.4 initWebApplicationContext()方法:初始化web应用上下文,委托createWebApplicationContext来创建
// 为此servlet初始化并发布WebApplicationContext
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.
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;
}
此方法上面的注释说委托createWebApplicationContext来创建,那么我们打个断点:
果然,走到了那一步,wac变量还是为null,所以会走createWebApplicationContext方法。
2.5 createWebApplicationContext()方法:创建web上下文。
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");
}
// 使用默认的构造函数实例化bean
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// 设置环境
wac.setEnvironment(getEnvironment());
// 将ApplicationContext设置为此应用程序上下文的父级
wac.setParent(parent);
// 获取本地上下文配置:这个配置就是我们当初web.xml中配置的属性:contextConfigLocation
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 配置并且刷新web应用程序上下文,
configureAndRefreshWebApplicationContext(wac);
return wac;
}
2.6 configureAndRefreshWebApplicationContext()方法:配置并且刷新web应用程序上下文,到这里,sringmvc将加载bean,刷新上下文交给了spring方法了,然后spring就会将其加入到spring容器中。
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);
// 交给spring
wac.refresh();
}