初识SpringMVC与其初始化
文章目录
1. 初探SpringMVC基本流程
先来看一张图(引用自《Spring实战》第四版 ):
这张图大致描述了SpringMVC从接收请求到响应请求的流程。
- 首先从浏览器发出一个url网络请求,然后WEB容器就接收到一个请求,每一个请求都会经过前端控制器Servlet,在SpringMVC中,此前端控制器即为
DispatcherServlet
,在下面我们会分析这个Servlet究竟干了什么。 - 前端控制器接收到请求之后,将会把请求委派给
Handler
,处理器Handler
此时会接收到这个请求,此时处理器会找到url对应的控制器。 - 执行控制器中url所对应的方法。
- 执行url对应的方法之后,将会返回一个模型和视图(
ModelAndView
)给前端控制器。 - 前端控制器将委派一个视图解析器去完成对刚刚返回的视图的解析(包括模型的传递)。
- 视图解析完成,视图解析器返回给前端控制器一个视图,前端控制器会将得到的视图响应出去。
- 将视图响应给浏览器,浏览器渲染视图呈现给用户。
到这里我们可以知道,一个请求要经过很多步骤,最后才能响应出去。其中我们可以看到,主要的工作都是由一个实现Servlet接口的DispatcherServlet
来实现的,它像一个中央处理器,负责分发所有的工作(Manager)。
我在阅读源码的过程中,这种架构思想几乎在每个框架都有出现,例如Shiro的
SecurityManager
,MyBatis的SqlSession
,Spring的ApplicationContext
等等,它们都充当了一个管理者的角色,负责所有工作,但具体做事的其实不是它们,作为管理者只需要调配分发工作给下级,而干活的实际上是下级来做,这样能清晰整个架构,使功能与功能之间产生解耦,“专人干专事”,并且符合面向对象设计的原则。如果功能耦合在一起,一个类包办所有功能,那么复杂性会极高,难维护难修改,并且容易让人看不懂,面向对象退化为面向过程。
看完了基本流程,我们来粗略的讲讲如何使用,首先你需要在web.xml配置如下:
<!-- 用来告诉ContextLoaderListener配置文件的位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:applicationContext.xml
</param-value>
</context-param>
<!-- WEB容器启动此类也将启动,载入上下文 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 前端控制器DispatcherServlet -->
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
2. ContextLoaderListener
在上面的web.xml配置中,我们发现需要配置这样一个Listener,那么它是做什么用的呢?我们知道,在ServletContext启动之后,会调用Listener的contextInitialized方法,那么我们就从它的这个方法开始分析:
@Override
public void contextInitialized(ServletContextEvent event) {
//委托给了父类ConetxtLoader去做
initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//如果已经存在Root上下文对象,抛出异常
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
//创建Context
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//对创建出来的Context进行初始化refresh
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将初始化好的上下文对象存放在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
}
//catch 略..
}
此方法委托给了父类ContextLoader
去做。在这个方法中,主要做了两件事:
-
存在性验证:首先会判断servletContext中是否已经有一个
RootContext
了,如果有将抛出异常,没有才继续初始化。 -
创建Context上下文对象:createWebApplicationContext方法创建一个
WebApplicationContext
。protected WebApplicationContext createWebApplicationContext(ServletContext sc) { //获取Context的Class类类型 Class<?> contextClass = determineContextClass(sc); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); } //根据得到的Class使用反射实例化上下文对象 return (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(