Spring MVC框架算是当下比较流行的Java开源框架。但实话实说,做了几年WEB项目,完全没有SpringMVC实战经验,乃至在某些交流场合下被同行严重鄙视“奥特曼”了。“心塞”的同时,只好默默的打开IDE从HelloWorld开始。

初步认识

宏观视野决定微观实现的质量,首先对Spring MVC框架组件及其流程做一个简单的认识。以下是从互联网中某Spring MVC教材扣来一张介绍图(懒得重复造轮子了):

从上图可以看出,Spring MVC框架的核心组件有DispatcherServlet、HandlerMapping、HandlerAdapter、Handler、ModelAndView、Model、View以及ViewResolver。既然是核心组件,怎么也得结合组件源码来探索个究竟吧:

DispatcherServlet

 

从名字可以看出,这就是一个Servlet实例,既然是Servlet,那当然是Srping MVC框架入口了,也是web.xml的一个Spring MVC配置项:

<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

其中springmvc为servlet的自定义命名名称,其中Spring MVC配置文件也是默认名称为[servletName]-servlet.xml。

从DispatcherServlet源码看到到,DispatcherServlet的基础结构是:

DispatcherServlet extend FrameworkServlet

FrameworkServlet extend HttpServletBean

HttpServletBean extend HttpServlet

初略的看了一下DispatcherServlet的干系源码,主要做了两大部分,其一是初始化WEB容器的上下文信息和一些Spring MVC策略容器(如HandlerMapping、HandlerAdapter等),在启动WEB容器时可以通过控制台输出看到Spring MVC的一些初始化操作:

……
信息: Starting Servlet Engine: Apache Tomcat/6.0.13
2016-4-12 10:51:54 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring FrameworkServlet 'springmvc'
2016-4-12 10:51:54 org.springframework.web.servlet.FrameworkServlet initServletBean
信息: FrameworkServlet 'springmvc': initialization started
2016-4-12 10:51:54 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing WebApplicationContext for namespace 'springmvc-servlet': startup date [Tue Apr 12 10:51:54 CST 2016]; root of context hierarchy
2016-4-12 10:51:54 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [springmvc-servlet.xml]
2016-4-12 10:51:55 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@c7f06: defining beans [helloWorldAnnotation,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping#0,org.springframework.format.support.FormattingConversionServiceFactoryBean#0,org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter#0,org.springframework.web.servlet.handler.MappedInterceptor#0,org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping#0,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter#0,org.springframework.web.servlet.view.InternalResourceViewResolver#0,/helloWorldController]; root of factory hierarchy
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldAnnotation] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldAnnotation.*] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldAnnotation/] onto handler [com.maventest.springmvc.HelloWorldAnnotation@9c393d]
2016-4-12 10:51:55 org.springframework.web.servlet.handler.AbstractUrlHandlerMapping registerHandler
信息: Mapped URL path [/helloWorldController] onto handler [com.maventest.springmvc.HelloWorldController@85ce5a]
2016-4-12 10:51:55 org.springframework.web.servlet.FrameworkServlet initServletBean
信息: FrameworkServlet 'springmvc': initialization completed in 1222 ms
……

其二就是对MVC容器的流程控制,其主要流程控制方法是doDispatch,接下来结合源码针对此方法的一些重要操作进行分析和学习:

//检查请求是否是multipart(如文件上传),如果是则通过MultipartResolver解析
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request; //获取请求对应的mappedHandler
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
} //获取请求对应的HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); //由适配器执行处理器(调用处理器相应功能处理方法)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); //如果HandlerAdapter没有对应的ModelAndView响应,怎通过上下文获取默认对应的view,接着
applyDefaultViewName(request, mv); //看applyPostHandle得知,这是定义拦截器的处理方法
mappedHandler.applyPostHandle(processedRequest, response, mv); //解析视图并进行视图的渲染
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

从doDispatch方法流程分析可以看出,跟以上Spring MVC框架流程图的处理流程是一致的,整个DispatcherServlet组件就是Spring MVC的总流程控制器,再形象一点就如下图所示:

HandlerMapping

察人先察色,HandlerMapping中文意思就是“处理映射”,作为一个强大的开源框架,命名自然不会乱来,通过名称就大概知其所以。看看getHandle这个方法:

protected HandlerExecutionChain getHandler(HttpServletRequest request)…

先不看源码,就大概可以猜个一二,这是通过request参数,获取一个对应的的处理类,而这个HandlerExecutionChain就是这个返回的处理类。这个HandlerMapping已经在项目启动的时候跟随Servlet一同初始化了:

initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);

而getHandler方法可以通过request获取请求的所有信息,包括请求方法、URL路径等,就可以通过这个映射容器找出对应的处理类了。下面再看看这个HandlerExecutionChain响应类属性:

private final Object handler;
private HandlerInterceptor[] interceptors;
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex = -1;

它包括了请求处理的所有拦截实例和核心处理handler实例,这都会在DispatcherServlet往下几个步骤会使用到的,具体可以往上回看DispatcherServlet的处理流程。

HandlerAdapter

还是从名称理解开始,HandlerAdapter中文意思就是处理对象适配器,按意思就是说Spring MVC有很多个Handler处理对象,这个处理器实际就是一个Handler代理。那么如果不自己定义Handler代理的话,那默认有多少个呢,那就可以看看DispatcherServlet.properties这个配置文件了:

Name:org.springframework.web.servlet.HandlerAdapter
Value:org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

哦,原来默认有HttpRequestHandlerAdapter、SimpleControllerHandlerAdapter和AnnotationMethodHandlerAdapter一共三个默认的Handler代理。那他们分别有什么用呢,看看我自己亲自动手做过Spring MVC HelloWorld实例就很明白了,我通过两种方式实现了两个HelloWorld Handler,一个在配置文件配置的bean:

<bean name="/helloWorldController" class="com.maventest.springmvc.HelloWorldController"/>
public class HelloWorldController implements Controller{

    public ModelAndView handleRequest(HttpServletRequest arg0,
HttpServletResponse arg1) throws Exception { ModelAndView mv = new ModelAndView();
mv.addObject("message", "Hello World!,i am HelloWorldController.");
mv.setViewName("hello"); return mv;
}
}

而另一个是通过注解实现的HelloWorld Handler:

@Controller
public class HelloWorldAnnotation{ @RequestMapping(value="/helloWorldAnnotation")
public String hello(ModelMap model){ model.addAttribute("message", "Hello, World!I am HelloWorldAnnotation."); return "hello";
}
}

这两种方式就是分别通过SimpleControllerHandlerAdapte和AnnotationMethodHandlerAdapter处理的,那这样一说就很明白了。另外这三个个Handler代理都实现了HandlerAdapter接口,就是Spring MVC规定了Handler代理的规则,分别有以下定义方法:

public interface HandlerAdapter {
//判断处理适配器是不是支持该Handler
boolean supports(Object handler);
//调用对应的Handler中适配到的方法,并返回一个ModelView
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
//这个暂时还没看懂具体想干什么(不是重点,暂时放下)
long getLastModified(HttpServletRequest request, Object handler);
}

其中判断是否找到合适的Handler代理就靠这个supports方法的具体实现,如果适配成功,这个代理会替这个Handler实现业务路基处理。再简单这三个代理的supports实现:

//HttpRequestHandlerAdapter
@Override
public boolean supports(Object handler) {
return (handler instanceof HttpRequestHandler);
} //SimpleControllerHandlerAdapte
@Override
public boolean supports(Object handler) {
return (handler instanceof Controller);
} //AnnotationMethodHandlerAdapter
@Override
public boolean supports(Object handler) {
return getMethodResolver(handler).hasHandlerMethods();
}

再来简单分别说说以上三个代理对handle方法的实现:

HttpRequestHandlerAdapter和SimpleControllerHandlerAdapte都是直接调用handler的handleRequest方法,而AnnotationMethodHandlerAdapter稍微复杂一点,它是通过注释和反射获取相关自定义信息,进行匹配和封装,具体可自行参考其源码。

Handler

这就是自己实现的具体业务处理类了,上文提到很多,不用多说了。

ModelAndView

通过handler代理完成业务流程后返回一个ModelAndView对象,从名称就大概可以知道这是一个装载的数据模型(Model)和数据视图的对象(View)。

Model

从源码可以看出,model集成了LinkedHashMap<String,Object>类,这个model对象装载了所有在Handler响应给页面的数据。例如在我自己例子中的message数据:

model.addAttribute("message", "Hello, World!I am HelloWorldAnnotation.");

这些数据将会在页面上通过JSTL获取。

View

View接口表示一个响应给用户的视图,例如jsp文件,pdf文件,html文件等,该接口定义如下:

public interface View {
//HttpServletRequest中的属性名,其值为响应状态码
String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus";
//HttpServletRequest中的属性名,前一篇文章用到了该变量,它的对应值是请求路径中的变量,及@PathVariable注解的变量
String PATH_VARIABLES = View.class.getName() + ".pathVariables";
//该视图的ContentType
String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType";
//获取该视图ContentType
String getContentType();
//渲染该视图
void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

该接口只有两个方法定义,分别表明该视图的ContentType和如何被渲染。Spring中提供了丰富的视图支持,并且可以自定义视图。

ViewResolver

ViewResolver接口定义了如何通过view 名称来解析对应View实例的行为。例如在我自己的一个注解Handler实现里面,我返回的是“hello”view name字符串,意思就是响应到对应的hello.jsp视图(在springmvc-servlet.xml配置文件定义了):

//controller
@RequestMapping(value="/helloWorldAnnotation")
public String hello(ModelMap model){

return "hello";
}

springmvc-servlet.xml:

<!-- ViewResolver -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>

在这里,我选择了默认Spring MVC JSP的实现类InternalResourceViewResolver。再来看看ViewResolver的接口定义:

public interface ViewResolver {
View resolveViewName(String viewName, Locale locale) throws Exception;
}

该接口只有一个方法,通过view name 解析出View。还是以我例子为准,通过“hello”view name字符串,通过ViewResolver. resolveViewName方法生成View实例。再通过View实例的render方法渲染该视图,剩下的具体细节可自行学习。

总结

 

两天学习下来,终于对Spring MVC有个大概的了解。毕竟是一个通用的框架,除了默认的实现,Spring MVC框架还定义了大量的标准可供用户自定义实现,整体也算是采用了Open-Closed原则,扩展性好,但有不失整体优雅。

【WEB】初探Spring MVC框架的更多相关文章

  1. Spring MVC篇一、搭建Spring MVC框架

    本项目旨在搭建一个简单的Spring MVC框架,了解Spring MVC的基础配置等内容. 一.项目结构 本项目使用idea intellij创建,配合maven管理.整体的目录结构如图: 其中ja ...

  2. Spring MVC 框架的架包分析,功能作用,优点

    由于刚搭建完一个MVC框架,决定分享一下我搭建过程中学习到的一些东西.我觉得不管你是个初级程序员还是高级程序员抑或是软件架构师,在学习和了解一个框架的时候,首先都应该知道的是这个框架的原理和与其有关j ...

  3. 从零开始学 Java - 搭建 Spring MVC 框架

    没有什么比一个时代的没落更令人伤感的了 整个社会和人都在追求创新.进步.成长,没有人愿意停步不前,一个个老事物慢慢从我们生活中消失掉真的令人那么伤感么?或者说被取代?我想有些是的,但有些东西其实并不是 ...

  4. spring mvc 框架搭建及详解

    现 在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不 ...

  5. Spring MVC框架搭建

    Spring MVC篇一.搭建Spring MVC框架 本项目旨在搭建一个简单的Spring MVC框架,了解Spring MVC的基础配置等内容. 一.项目结构 本项目使用idea intellij ...

  6. Spring MVC框架下的第一个Hello World程序

    本程序是一个maven程序,使用maven方便管理jar包和程序,简化了操作步骤.本程序的目的是通过一个简单的程序,了解Spring MVC框架的基本工作流程,由简入繁的学习Spring MVC框架, ...

  7. spring MVC框架入门(外加SSM整合)

    spring MVC框架 一.什么是sping MVC Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面.Spring 框架提供了构建 W ...

  8. 关于我使用spring mvc框架做文件上传时遇到的问题

    非常感谢作者 原文:https://blog.csdn.net/lingirl/article/details/1714806 昨天尝试着用spring mvc框架做文件上传,犯了挺多不该犯的毛病问题 ...

  9. 戏说 Spring MVC 框架

    Spring MVC 是 Spring 框架的一部分,和 Struts 一样都是属于 web 层框架,根据项目分层的思想,一个项目基本可以分为持久层,业务层和 web 层.而 Spring MVC 主 ...

随机推荐

  1. MySQL安装之后没有MySQL数据库的原因

    mysql安装完之后,登陆后发现只有两个数据库:mysql> show databases;+--------------------+| Database           |+------ ...

  2. Android框架之AndroidAnnotations基础

    一:开源网址 https://github.com/excilys/androidannotations/wiki 二:AndroidAnnotation特点 (1)依赖注入 可以注入 views, ...

  3. spring 事务:注解方式

    (1) .<context:component-scan base-package="*.*" /> 该配置隐式注册了多个对注解进行解析的处理器,如: Autowire ...

  4. 使用C#代码部署SharePoint 2013开发包简单总结(一)

    这篇文章将总结下如何将自己开发的列表.Web部件.事件接收器等元素部署到SharePoint的服务器.因水平有限,我的做法未必是最佳实践,会有些错误理解和疏漏,欢迎各位高手批评指正——但一定要能给出更 ...

  5. javaweb中实现在线人数统计

    session并不是浏览器关闭时销毁的,而是在session失效的时候销毁下列代码就是监测session创建.销毁 package com.my.count; import javax.servlet ...

  6. node.js之path

    说到node.js,可能实际中用到node进行后台开发的公司不多,大部分人都没有开发后台的经验.但是也要了解node相关模块的用法,因为现在前端自动化脚本的构建,模块的打包越来越离不开node.特别是 ...

  7. Unity 4.x Asset Bundle 重名

    在 Unity 4.5.1f3中测试发现如下问题 两个不同文件下相同名字的资源打包成AssetBundle以后加载失败,提示错误  xxxxx can't be loaded because anot ...

  8. C# 计算字符串在控制台中的显示长度

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  9. ruby 学习笔记 1

    写ruby blog  系统的记录下.也是对我学ruby的点滴记录. 先介绍下我的学习环境.系统:ubuntu12.04文档:techotopia ,ruby文档,the hard way learn ...

  10. 修改Chrome临时文件位置

    通过目录链接实现. mklink /D "C:\Users\ljq\AppData\Local\Google\Chrome\User Data" z:\temp http://we ...