文章目录
一、SpringMVC概述
Spring MVC一种开源的、轻量级的、基于MVC的Web层应用框架。偏前端而不是基于业务逻辑层。
SpringMVC是Spring中的模块,它实现了mvc设计模式的web框架,Spring web MVC框架提供了MVC(模型 - 视图 - 控制器)架构,用于开发灵活和松散耦合的Web应用程序的组件。 MVC模式使应用程序的不同组件(输入逻辑,业务逻辑和UI逻辑)合理有效的分离,同时又有效的将各组件组合一起完成功能。
· 模型(Model) 封装了应用程序数据,通常它们将由POJO
类组成。
· 视图(View) 负责渲染模型数据,一般来说它负责生成客户端浏览器可以解释HTML输出。
· 控制器(Controller) 负责处理用户请求并构建适当的模型,并将其传递给视图进行渲染。
常用组件:
DispatcherServlet:前端控制器
Controller:处理器/页面控制器,做的是MVC中的C的事情,但控制逻辑转移到前端控制器了,用于对请求进行处理
HandlerMapping :请求映射到处理器,如果映射成功返回一个HandlerExecutiongChain对象(包含一个Handler处理器(页面控制器)对象、多个HandlerInterceptor拦截器对象)
ViewResolver : 视图解析器,找谁来处理返回的页面。把逻辑视图解析为具体的View,进行这种策略模式,很容易更换其他视图技术;如InternalResourceViewResolver将逻辑视图名映射为JSP视图
LocalResolver:本地化、国际化
MultipartResolver:文件上传解析器
HandlerExceptionResolver:异常处理器
二、环境搭建
2.1 HelloWorld范例
①新建Web工程,加入 jar 包;
②配置web.xml:
<!-- 配置SpringMVC核心控制器:DispatcherServlet -->
<!--前端控制器 dispatcherServlet 其实就是servlet-->
<servlet>
<!--servlet的友好名称,建议跟类名保持一致-->
<servlet-name>dispatcherServlet</servlet-name>
<!--servlet的全类名-->
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--servlet实例创建并且读取springmvc配置文件-->
<init-param>
<!--DispatcherServlet类中的一个属性:contextConfigLocation,里面配置springmvc的路径-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- 表示前端控制器在服务器启动时就加载servlet实例 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!-- 配置前端控制器的访问地址 -->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<!--
/和/*的区别
/ 如果请求访问的是jsp,则不拦截
/* 全部拦截,包含jsp页面
-->
<url-pattern>/</url-pattern>
</servlet-mapping>
③配置Springmvc.xml:
<!-- 包扫描 -->
<context:component-scan base-package="Controller"></context:component-scan>
<!--
配置映射解析器:
拼接完整的页面路径信息
/WEB-INF/views/+逻辑名字+.jsp
/WEB-INF/views/welcome.jsp
-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- prefix:逻辑名字前的路径信息 -->
<property name="prefix" value="/WEB-INF/views/"></property>
<!-- suffix:逻辑名字页面的后缀 -->
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 如果将DispatcherServlet请求映射配置为"/",则Spring MVC将捕获Web容器所有的请求,包括静态资源的请求,Spring MVC会将它们当成一个普通请求处理,因此找不到对应处理器将导致错误。 -->
<!-- 在springMVC-servlet.xml中配置<mvc:default-servlet-handler />后,会在Spring MVC上下文中定义一个DefaultServletHttpRequestHandler,它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理。 -->
<mvc:default-servlet-handler></mvc:default-servlet-handler>
<!-- <mvc:annotation-driven /> 会自动注册:RequestMappingHandlerMapping 、RequestMappingHandlerAdapter 与 ExceptionHandlerExceptionResolver 三个bean。-->
<!--
支持使用 ConversionService 实例对表单参数进行类型转换
支持使用 @NumberFormat、@DateTimeFormat 注解完成数据类型的格式化
支持使用 @Valid 注解对 JavaBean 实例进行 JSR 303 验证
支持使用 @RequestBody 和 @ResponseBody 注解
-->
<mvc:annotation-driven></mvc:annotation-driven>
④Controlller控制器类:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
//@Controller 注解是把HelloController类配置到Spring容器中
@Controller
public class HelloController {
/**
* 使用 @RequestMapping 注解来映射请求的 URL
* / : 在web工程中表示路径为:http://ip:port/工程路径
* /hello : 表示地址为:http://ip:port/工程路径/hello
*/
@RequestMapping(value = "/hello")
public String hello(){
System.out.println(" springmvc hello world程序 ");
/**
* 返回值会通过视图解析器解析为实际的物理视图,
* 对于 InternalResourceViewResolver 视图解析器,会做如下的解析:
* 通过 prefix + returnVal + suffix 这样的方式得到实际的物理视图,
* 默认做转发操作.
* /WEB-INF/views/success.jsp
*/
return "success";
}
}
⑤视图配置:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h4>helloworld</h4>
</body>
</html>
2.2 HelloWorld流程图解
2.3 请求相应流程图
基本步骤:
- 客户端请求提交到DispatcherServlet;
- 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller;
- DispatcherServlet将请求提交到Controller(也称为Handler);
- Controller调用业务逻辑处理后,返回ModelAndView;
- DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图;
- 视图负责将结果显示到客户端;
三、 前端控制器
DispatcherServlet 是前端控制器设计模式的实现,提供 Spring Web MVC 的集中访问点,并把请求分发到不同的控制器去处理,根据控制器处理后的结果,生成相应的响应发送到客户端。前端控制器既可以使用Filter实现(Struts2采用这种方式),也可以使用Servlet来实现(spring MVC框架)。
3.1 Servlet回顾
SpringMVC是用Servlet来实现前端控制器,所以首先来回忆一下servlet的相关知识。
Servlet生命周期的三个阶段:
在Servlet容器启动后,客户首次向Servlet发送请求,Servlet容器创会建一个Servlet实例。
- 初始化:实例化之后紧接着执行初始化,只执行一次,调用init()方法。
- 服务:处理请求并响应浏览器,每次从浏览器发送请求访问此servlet,都会调用service()方法。
- 销毁:服务器关闭时执行销毁的方法,只执行一次,调用destroy()方法
提示:在web.xml里面的<servlet>
标签中,加入<load-on-startup>
,就可以将servlet的加载时间提前到服务器启动时,只能设置正整数,负整数或0没有任何效果,而且值越小越先加载。
3.2 DispatcherServlet前端控制器
前端控制器源码分析:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
processedRequest = checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 确定当前请求的处理程序。
mappedHandler = getHandler(processedRequest, false);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 确定当前请求的处理程序适配器。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Last-Modified value for [" + requestUri + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
try {
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
}
applyDefaultViewName(request, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Error err) {
triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
return;
}
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
第一步:用户发送请求至前端控制器DispatcherServlet;
.action类型的URL通过过滤器进入DispatcherServlet类,调用其doDiapatch()方法:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
......
}
第二步:前端控制器调用HandlerMapping处理器映射器,请求获取Handle;
在doDiapatch()方法中调用了DispatcherServlet类的getHandler方法:
HandlerExecutionChain mappedHandler = null;
......
//调用getHandler() 获取HandlerExecutionChain:当前请求地址对应的处理器(链),即请求处理器。
mappedHandler = getHandler(processedRequest, false);
其中getHandler方法:
@Deprecated
protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
return getHandler(request);
}
//在getHandler()方法中遍历了已有的handlerMapping,然后调用handlerMapping.getHandler(request)获得HandlerExecutionChain,而HandlerExecutionChain中包含了需要调度的指定handler。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// HandlerMapping: 存储了所有的请求地址和处理器(方法)的对应信息,在组件初始化的时候就存放了所有URL和处理器(方法)的对应信息。
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
//logger记录器,记录日志信息
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
//HandlerExecutionChain:当前请求地址对应的处理器(链)
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;//说明前端传过来的URL和注解上的路径匹配, 就返回对应的控制器
}
}
return null;
}
处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成),返回一个执行器的链(HandlerExecutionChain)给DispatcherServlet前端控制器;
第三步:回到doDiapatch()方法,调用处理器适配器HandlerAdapter执行Handler,确定方法运行时的参数,利用反射执行目标方法得到执行结果ModelAndView。
ModelAndView mv = null;
......
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
......
//确定方法运行时的参数,利用反射执行目标方法。
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
四、视图解析器
请求处理方法执行完成后,最终返回一个 ModelAndView 对象。对于那些返回 String,View 或 ModeMap 等类型的处理方法,Spring MVC 也会在内部将它们装配成一个 ModelAndView 对象,它包含了逻辑名和模型对象的视图。
Spring MVC 借助视图解析器(ViewResolver)得到最终的视图对象(View),对于最终究竟采取何种视图对象对模型数据进行渲染,处理器并不关心,处理器工作重点聚焦在生产模型数据的工作上,从而实现 MVC 的充分解耦。
4.1 视图
视图的作用是渲染模型数据,将模型里的数据以某种形式呈现给客户。为了实现视图模型和具体实现技术的解耦,Spring 在 org.springframework.web.servlet 包中定义了一个高度抽象的 View 接口:
- 视图对象由视图解析器负责实例化。由于视图是无状态的,所以他们不会有线程安全的问题。
常用视图实现类:
4.2 ViewResolve视图解析器
SpringMVC 为逻辑视图名的解析提供了不同的策略,可以在 Spring WEB 上下文中配置一种或多种解析策略,并指定他们之间的先后顺序。每一种映射策略对应一个具体的视图解析器实现类。视图解析器的作用比较单一:将逻辑视图解析为一个具体的视图对象。
所有的视图解析器都必须实现ViewResolver接口: