声明:此博文为个人学习总结,如有错误疏漏之处,欢迎各位批评指正!
采用“器用分析法”对springmvc进行源码解析:
- 器:顾名思义就是要分析的目标,即springmvc本身的创建过程
- 用:即如何使用,即springmvc是如何工作的
使用“器用分析法”可以在一个复杂的类的很多看似杂乱无章的方法中快速梳理出头绪,不仅仅可以用来分析springmvc的源码,用来分析其他的源码也是可以的。
本博文主要是从“器”的角度对springmvc进行解析
首先从springmvc的前置控制器即DispatcherServlet 入手:
DispatcherServlet的继承结构:
从springmvc的整体结构分析:
在上图可以看出继承结构中有五个类:
- GenericServlet
- HttpServlet
- HttpServletBean
- FrameworkServlet
- DispatcherServlet
在springmvc中主要对后三个类做出论述:HttpServletBean、FrameworkServlet、DispatcherServlet
其中HttpServletBean和FrameworkServlet直接实现了三个接口:
- EnvironmentCapable
- EnvironmentAware
- ApplicationContextAware
在英文中Aware和Capable的意思如下:
Aware : 感知,意识到的,察觉到的
Capable : 有能力的胜任的
对后缀 XXXAware 的通俗解析如下:
如果你想使用spring中的某些东西,比如XXX,就可以通过实现 XXXAware 接口,告诉spring,spring会通过实现接口的方法 setXXX 将XXX送过来,我们只需要接收就可以了,XXXAware 这里很容易理解,就是对XXX感知。
在这里实现了ApplicationContextAware接口,所以spring会通过调用setApplicationContext将ApplicationContext传给我们。实现EnvironmentAware接口也是同样的道理。
EnvironmentCapable也就是有Environment的能力,EnvironmentCapable会提供唯一的方法Environment getEnvironment(),这里也很容易看出来,他就是提供了一个Environment,ApplicationContext相信大家都已经很熟悉了
下来我们来分析分析Environment是一个什么东西。
我们提供一个普通的controllelr实现EnvironmentAware接口,通过断点分析:
package net.csdn.handler;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class HelloController implements EnvironmentAware{
private final Log logger = LogFactory.getLog(HelloController.class);
private Environment environment = null;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@RequestMapping("hello")
public String hello(Model model){
logger.info(" before.");
model.addAttribute("message","hello springmvc !!!");//在此处设置断点
logger.info(" after.");
return "hello";
}
}
注:简单环境搭建可以参考我的上一篇博文
这里断点调试查看environment 中的值,可以看到如下内容:
可以看到propertySources中包含了五个属性,下来对这五个属性分别来做说明:
-
ServletConfigPropertySource
继续看ServletConfigPropertySource的内部结构
可以看到ServletConfigPropertySource的source类型是StandardWrapperFacade,也就是Tomcat中定义的一个ServletConfig类型,所以这里就直接认为ServletConfigPropertySource封装的就是ServletConfig
继续查看config中的内容,可以看到之前在web.xml中配置的contextConfigLocation
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
<display-name>springmvcSourceAnalysis</display-name>
<servlet>
<servlet-name>SourceAnalysis</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 配置springmvc配置文件的位置 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 设置启动加载 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>SourceAnalysis</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
至此,认为ServletConfigPropertySource封装的就是ServletConfig也是毋庸置疑的了。
-
ServletContextPropertySource
这里很容易就能看出ServletContextPropertySource封装的是ServletContext -
JndiPropertySource 这里没有使用到Jndi 就不做分析了
-
MapPropertySource
MapPropertySource存放的是虚拟机的属性:包括java版本,所用操作系统的名称、版本号,用户主目录,临时目录等信息 -
SystemEnvironmentPropertySource
通过上图可以很清楚的看出SystemEnvironmentPropertySource中存放的是环境变量
分析完了整体的结构,下来我们对HttpServletBean、FrameworkServlet、DispatcherServlet这三个类来做具体源码分析:
HttpServletBean
首先来看HttpServletBean的init方法:
//public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware
@Override
public final void init() throws ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Initializing servlet '" + getServletName() + "'");
}
// 将Servlet中配置的参数设置到相应的属性
try {
//将Servlet中配置的参数封装到pvs变量中,其中第二个参数为必填项,否则会报错
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
//初始化一个BeanWrapper 对象 bw
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
//initBeanWrapper()为模板方法,由子类调用,来做一些初始化的工作,这里的入参bw中封装了DispatcherServlet对象,代表DispatcherServlet
initBeanWrapper(bw);
//将配置的初始化值 设置到DispatcherServlet
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
throw ex;
}
// Let subclasses do whatever initialization they like.
//initServletBean() 也是一个模板方法,由子类调用,是子类初始化的入口方法
initServletBean();
if (logger.isDebugEnabled()) {
logger.debug("Servlet '" + getServletName() + "' configured successfully");
}
}
可以看出HttpServletBean继承自HttpServlet ,只做了一件事情,就是将Servlet中配置的参数设置到相应的属性,其余交由子类去完成。
FrameworkServlet
由上文可知,FrameworkServlet入口方法是,initServletBean(),从入口方法看起
//public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
@Override
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
if (this.logger.isInfoEnabled()) {
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
}
long startTime = System.currentTimeMillis();
try {
//***核心代码***
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}
catch (ServletException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
catch (RuntimeException ex) {
this.logger.error("Context initialization failed", ex);
throw ex;
}
if (this.logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
elapsedTime + " ms");
}
}
可以看出这里的核心代码仅仅只有两句:
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
都是做了初始化的工作,分别初始化WebApplicationContext和FrameworkServlet
但是这里的initFrameworkServlet()是一个模板方法,本应由子类调用做一些初始化的工作,但是子类并没有使用它,所以这里着重讨论initWebApplicationContext(),可知FrameworkServlet 在构建的过程中的主要作用就是初始化了WebApplicationContext
下来对initWebApplicationContext()进行说明
//public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware
protected WebApplicationContext initWebApplicationContext() {
//第一件事:获取了spring的根容器,rootContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
//第二件事:设置webApplicationContext ,并根据情况调用onRefresh()方法
if (this.webApplicationContext != null) {
//如果已经通过构造方法设置了webApplicationContext 那么直接去使用就可以了
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
//context 还没有被刷新,例如:提供设置parent context、设置application context id 的服务等等
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.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
//第三件事:将webApplicationContext 设置到ServletContext
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;
}
//next todo
分析springmvc中的三个类 (引入源码 ,图片说明 进行分析)
HttpServletBean
FrameworkServlet
DispatcherServlet
小结…未完待续!