MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种用于设计创建 Web 应用程序表现层的模式。
SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring FrameWork的后续产品,已经融合在SpringWeb Flow里面。Spring框架提供了构建 Web 应用程序的全功能 MVC 模块。使用Spring可插入的MVC架构,从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架或集成其他MVC开发框架,如Struts1(现在一般不用),Struts2等。 SpringMVC已经成为目前最主流的 MVC 框架之一,并且随着Spring3.0的发布,全面超越 Struts2,成为最优秀的 MVC 框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无须实现任何接口。同时它还支持RESTful编程风格的请求。
共同点:
区别:
利用Maven搭建一个webapp项目并将目录补全,并在pom.xml中引入坐标:
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<spring.version>5.0.2.RELEASEspring.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>servlet-apiartifactId>
<version>2.5version>
<scope>providedscope>
dependency>
<dependency>
<groupId>javax.servlet.jspgroupId>
<artifactId>jsp-apiartifactId>
<version>2.0version>
<scope>providedscope>
dependency>
dependencies>
配置核心控制器,在web.xml中:
<servlet>
<servlet-name>dispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>dispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
在resources目录下创建SpringMVC的配置文件springmvc.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="top.tjtulong"/>
<bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"/>
<property name="suffix" value=".jsp"/>
bean>
<mvc:annotation-driven/>
beans>
创建处理请求的类Controller,用注解自动注入:
// 控制器类
@Controller
public class HelloController {
@RequestMapping(path = "/hello")
public String sayHello() {
System.out.println("Hello SpringMVC");
return "success";
}
}
主页面index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Demo
入门程序
入门程序
成功页面WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
Title
入门成功
于是在主页面点击超链接后自动跳转到success.jsp页面
在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。
使用
自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在SpringMVC.xml配置文件中使用
替代注解处理器和适配器的配置。
@RequestMapping注解用于建立请求URL和处理请求方法之间的对应关系。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {}
params = {"accountName"}
,表示请求参数必须有accountNameparams = {"moeny!100"}
,表示请求参数中money不能是100。注意: 以上四个属性只要出现2个或以上时,他们的关系是与的关系。
// 控制器类
@Controller
@RequestMapping(path = "/user")
public class HelloController {
/**
* RequestMapping注解
* @return
*/
@RequestMapping(path = "/testRequestMapping", method = {RequestMethod.GET},
params = {"username=xiao"}, headers = {"Accept"})
public String testRequestMapping() {
System.out.println("test RequestMapping...");
return "success";
}
}
表单中请求参数都是基于key=value的。 SpringMVC绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。
请求参数绑定
请求参数为username,可以直接在Controller中的方法上绑定:
@Controller
@RequestMapping("/param")
public class ParamController {
@RequestMapping("testParam")
public String teatParam(String username) {
System.out.println("执行了..." + username);
return "success";
}
}
创建一个pojo类型和pojo中的pojo:
public class Account implements Serializable {
private String username;
private String password;
private Double money;
private User user;
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "Account{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", money=" + money +
", user=" + user +
'}';
}
}
内部pojo:User
public class User implements Serializable {
private String uname;
private Integer age;
}
在前端传入参数:
把数据封装Account类中
控制器代码:
/**
* 请求参数绑定到JavaBean的类中
* @param account
* @return
*/
@RequestMapping("/saveAccount")
public String saveAccount(Account account) {
System.out.println(account);
return "success";
}
可以在控制台看到参数被绑定:
Account{username='xiao', password='111', money=234.0, user=User{uname='xiaoge', age=22}}
<filter>
<filter-name>characterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>characterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
public class Account implements Serializable{
private String username;
private String password;
private Double money;
private List<User> list;
private Map<String,User> map;
}
把数据封装到Account,类中存在list和map集合:
<form action="param/saveAccount" method="post">
姓名:<input type="text" name="username" /><br/>
密码:<input type="text" name="password" /><br/>
金额:<input type="text" name="money" /><br/>
用户姓名:<input type="text" name="list[0].uname" /><br/>
用户年龄:<input type="text" name="list[0].age" /><br/>
用户姓名:<input type="text" name="map['one'].uname" /><br/>
用户年龄:<input type="text" name="map['one'].age" /><br/>
<input type="submit" value="提交" />
</form>
SpringMVC可以自动将表单提交的字符串类型转换成各种类型。
但一些情况如果格式不正确会出现400错误。
根据日期删除账户
日期格式不正确,应该为2018/01/01才能自动类型转换为date。
配置一个自己的类型转换器StringToDateConverter:
/**
* 把字符串转换日期
*/
public class StringToDateConverter implements Converter<String, Date> {
/**
* String source 传入进来字符串
* @param source
* @return
*/
public Date convert(String source) {
// 判断
if (source == null) {
throw new RuntimeException("请您传入数据");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
// 把字符串转换日期
return df.parse(source);
} catch (Exception e) {
throw new RuntimeException("数据类型转换出现错误");
}
}
}
在springmvc.xml中配置该转换器:
<mvc:annotation-driven conversion-service="conversionService"/>
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="top.tjtulong.utils.StringToDateConverter">bean>
set>
property>
bean>
把原生API直接写在控制的方法参数中使用即可。
/**
* 原生的API
* @return
*/
@RequestMapping("/testServlet")
public String testServlet(HttpServletRequest request, HttpServletResponse response){
System.out.println("执行了...");
System.out.println(request);
HttpSession session = request.getSession();
System.out.println(session);
ServletContext servletContext = session.getServletContext();
System.out.println(servletContext);
System.out.println(response);
return "success";
}
当传入的参数名称与控制器方法不同时,必须使用@RequestParam注解。
RequestParam
控制器:
/**
* 常用注解
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(name="name") String username){
System.out.println("执行了...");
return "success";
}
}
作用: 用于获取请求体内容。直接使用得到是key=value&key=value…结构的数据。 get请求方式不适用。
属性: required:是否必须有请求体,默认值是:true。当取值为true时,get请求方式会报错。如果取值为false,get请求得到是null。
/**
* 获取到请求体的内容
* @return
*/
@RequestMapping("/testRequestBody")
public String testRequestBody(@RequestBody String body){
System.out.println("执行了...");
System.out.println(body);
return "success";
}
Restful(表述性状态转移)风格的URL:
请求路径一样,可以根据不同的请求方式去执行后台的不同方法。简单的说就是:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。
核心思想就是每个资源都有它的状态,不同状态下,可对它进行的操作不一样。
restful风格的URL优点:
作用:拥有绑定url中的占位符的。例如:url中有/delete/{id},{id}就是占位符。
入门案例
控制器:
/**
* PathVariable注解
* @return
*/
@RequestMapping(value="/testPathVariable/{sid}")
public String testPathVariable(@PathVariable(name="sid") String id){
System.out.println("执行了...");
System.out.println(id);
return "success";
}
作用:获取指定请求头的值
属性:value:请求头的名称
/**
* 获取请求头的值
* @param header
* @return
*/
@RequestMapping(value = "/testRequestHeader")
public String testRequestHeader(@RequestHeader(value = "Accept") String header, HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("执行了...");
System.out.println(header);
return "success";
}
作用:用于获取指定cookie的名称的值
属性:value:cookie的名称
/**
* 获取Cookie的值
* @return
*/
@RequestMapping(value="/testCookieValue")
public String testCookieValue(@CookieValue(value="JSESSIONID") String cookieValue){
System.out.println("执行了...");
System.out.println(cookieValue);
return "success";
}
出现在方法上:表示当前方法会在控制器方法执行前线执行。
出现在参数上:获取指定的数据给参数赋值。
应用场景:当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
/**
* ModelAttribute注解
* @return
*/
@RequestMapping(value="/testModelAttribute")
public String testModelAttribute(@ModelAttribute("abc") User user){
System.out.println("testModelAttribute执行了...");
System.out.println(user);
return "success";
}
@ModelAttribute
public void showUser(String uname, Map<String,User> map){
System.out.println("showUser执行了...");
// 通过用户查询数据库(模拟)
User user = new User();
user.setUname(uname);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
showUser会先于testModelAttribute执行。
用于多次执行控制器方法间的参数共享。
@Controller
@RequestMapping("/anno")
@SessionAttributes(value={"msg"})//把msg=xxx存到session域
public class AnnoController {
/**
* SessionAttributes的注解
* @return
*/
@RequestMapping(value="/testSessionAttributes")
public String testSessionAttributes(Model model){
System.out.println("testSessionAttributes...");
// 底层会存储到request域对象中
model.addAttribute("msg","美美");
return "success";
}
}
controller方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testString")
public String testString(Model model){
System.out.println("testString方法执行了...");
User user = new User();
user.setUsername("xiao");
user.setPassword("916521");
user.setAge(22);
model.addAttribute("user",user);
return "success";
}
}
可以通过EL表达式在jsp中输出Model中的pojo:
${user.username}
${user.password}
${user.age}
@RequestMapping("/testVoid")
public void testString(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println("testVoid方法执行了...");
//请求转发
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request, response);
return;
}
response.sendRedirect("testRetrunString")
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json 串");
ModelAndView是SpringMVC为我们提供的一个对象,该对象也可以用作控制器方法的返回值。
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
// 创建ModelAndView对象
ModelAndView mv = new ModelAndView();
System.out.println("testModelAndView方法执行了...");
// 模拟从数据库中查询出User对象
User user = new User();
user.setUsername("小凤");
user.setPassword("456");
user.setAge(30);
// 把user对象存储到mv对象中,底层也会把user对象存入到request对象
mv.addObject("user", user);
// 跳转到哪个页面
mv.setViewName("success");
return mv;
}
返回ModelAndView类型时,浏览器跳转只能是请求转发。
/**
* 使用关键字的方式进行转发或者重定向
* @return
*/
@RequestMapping("/testForwardOrRedirect")
public String testForwardOrRedirect(){
System.out.println("testForwardOrRedirect方法执行了...");
// 请求的转发
// return "forward:/WEB-INF/pages/success.jsp";
// 重定向
return "redirect:/index.jsp";
}
用jQuery模拟ajax请求,首先需要放行jQuery的静态资源:
<mvc:resources mapping="/js/**" location="/js/"/>
发送Ajax请求:
<script src="js/jquery.min.js"></script>
<script>
//页面加载,绑定单击事件
$(function () {
$("#btn").click(function () {
//alert("hello btn");
// 发送ajax请求
$.ajax({
// 编写json格式,设置属性和值
url:"user/testAjax",
contentType:"application/json;charset=UTF-8",
data:'{"username":"hehe","password":"123","age":30}',
dataType:"json",
type:"post",
success:function(data){
// data服务器端响应的json的数据,进行解析
alert(data);
alert(data.username);
alert(data.password);
alert(data.age);
}
});
});
});
</script>
控制器收到json并自动解析成实体类,再将返回对象转换为json格式:
/**
* 模拟异步请求响应
*/
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){
System.out.println("testAjax方法执行了...");
// 客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
System.out.println(user);
// 做响应,模拟查询数据库
user.setUsername("haha");
user.setAge(40);
// 做响应
return user;
}
引入依赖:
<dependency>
<groupId>commons-fileuploadgroupId>
<artifactId>commons-fileuploadartifactId>
<version>1.3.1version>
dependency>
<dependency>
<groupId>commons-iogroupId>
<artifactId>commons-ioartifactId>
<version>2.4version>
dependency>
前端:
控制器:
/**
* SpringMVC文件上传
* @return
*/
@RequestMapping("/fileupload2")
public String fileuoload2(HttpServletRequest request, MultipartFile upload) throws Exception {
System.out.println("springmvc文件上传...");
// 使用fileupload组件完成文件上传
// 上传的位置
String path = request.getSession().getServletContext().getRealPath("/uploads/");
// 判断,该路径是否存在
File file = new File(path);
if(!file.exists()){
// 创建该文件夹
file.mkdirs();
}
// 说明上传文件项
// 获取上传文件的名称
String filename = upload.getOriginalFilename();
// 把文件的名称设置唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid+"_"+filename;
// 完成文件上传
upload.transferTo(new File(path,filename));
return "success";
}
应用服务器-------->图片服务器
/**
* 跨服务器文件上传
* @return
*/
@RequestMapping("/fileupload3")
public String fileuoload3(MultipartFile upload) throws Exception {
System.out.println("跨服务器文件上传...");
// 定义上传文件服务器路径
String path = "http://localhost:9090/uploads/";
// 说明上传文件项
// 获取上传文件的名称
String filename = upload.getOriginalFilename();
// 把文件的名称设置唯一值,uuid
String uuid = UUID.randomUUID().toString().replace("-", "");
filename = uuid+"_"+filename;
// 创建客户端的对象
Client client = Client.create();
// 和图片服务器进行连接
WebResource webResource = client.resource(path + filename);
// 上传文件
webResource.put(upload.getBytes());
return "success";
}
异常处理思路:Controller调用service,service调用dao,异常都是向上抛出的,最终由DispatcherServlet找异常处理器进行异常的处理。
首先自定义异常类:
/**
* 自定义异常类
*/
public class SysException extends Exception{
//存储提示信息
private String message;
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public SysException(String message) {
this.message = message;
}
}
自定义异常处理器:
/**
* 异常处理器
*/
public class SysExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
//获取到异常对象
SysException exception = null;
if (e instanceof SysException) {
exception = (SysException) e;
} else {
e = new SysException("系统正在维护");
}
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage());
mv.setViewName("error");
return mv;
}
}
对异常处理器组件进行注册:
<bean id="sysExceptionResolver" class="top.tjtulong.exception.SysExceptionResolver">bean>
这样配置后,当服务器再次出现异常时,可直接显示异常信息:
${errorMsg}
SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。
可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。
拦截器和过滤器的功能比较类似,也有区别
创建类,实现HandlerInterceptor接口,重写需要的方法:
/**
* 自定义拦截器1
* @author rt
*/
public class MyInterceptor1 implements HandlerInterceptor{
/**
* controller方法执行前,进行拦截的方法
* return true放行
* return false拦截
* 可以使用转发或者重定向直接跳转到指定的页面。
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception {
System.out.println("拦截器执行了...");
return true;
}
}
配置拦截器:
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/*"/>
<mvc:exclude-mapping path=""/>
<bean class="top.tjtulong.demo1.MyInterceptor1"/>
mvc:interceptor>
mvc:interceptors>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/user/*"/>
<bean class="top.tjtulong.controller.cn.itcast.interceptor.MyInterceptor1" />
mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="top.tjtulong.controller.cn.itcast.interceptor.MyInterceptor2" />
mvc:interceptor>
mvc:interceptors>
补充:一个web应用部署在Tomcat下的目录结构
webapps
|- hello
|-index.html
|-WEB-INF
|-web.xml
|-classes
|-lib
参考:田小波的博客
首先,用户的浏览器发出了一个请求,这个请求经过互联网到达了我们的服务器。Servlet容器首先接待了这个请求,并将该请求委托给前端控制器DispatcherServlet进行处理。接着DispatcherServlet将该请求传给了处理器映射组件HandlerMapping,并获取到适合该请求的拦截器和处理器。在获取到处理器后,DispatcherServlet还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet通过处理器适配器HandlerAdapter调用处理器的逻辑,并获取返回值ModelAndView。之后,DispatcherServlet需要根据ModelAndView解析视图。解析视图的工作由ViewResolver完成,若能解析成功,ViewResolver会返回相应的视图对象View。在获取到具体的View对象后,最后一步要做的事情就是由View渲染视图,并将渲染结果返回给用户。
在请求过程中要经过许多组件:
从上面的流程中可以看出,Spring MVC对各个组件的职责划分的比较清晰。DispatcherServlet负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。
HttpServlet从名字上就可看出,这个类是和HTTP协议相关。该类的关注点在于怎么处理HTTP请求,比如其定义了doGet方法处理GET类型的请求,定义了doPost方法处理POST类型的请求等。我们若需要基于Servlet写Web应用,应继承该类,并覆盖指定的方法。doGet和doPost等方法并不是处理的入口方法,所以这些方法需要由service方法调用才行,这个方法的实现如下:
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest &&
res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
// 调用重载方法,该重载方法接受 HttpServletRequest 和 HttpServletResponse 类型的参数
service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
// 处理 GET 请求
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// 调用 doGet 方法
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
// 处理 HEAD 请求
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
// 处理 POST 请求
} else if (method.equals(METHOD_POST)) {
// 调用 doPost 方法
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
第二个 service 方法中,该方法根据请求类型分发请求。我们可以根据需要覆盖指定的处理方法。
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);
// 获取可处理当前请求的处理器 Handler,对应流程图中的步骤②
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取可执行处理器逻辑的适配器 HandlerAdapter,对应步骤③
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 处理 last-modified 消息头
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 执行拦截器 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 调用处理器逻辑,对应步骤④
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 如果 controller 未返回 view 名称,这里生成默认的 view 名称
applyDefaultViewName(processedRequest, mv);
// 执行拦截器 postHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 解析并渲染视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
if (mv != null && !mv.wasCleared()) {
// 渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isDebugEnabled()) {...
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
return;
}
if (mappedHandler != null) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale);
View view;
/*
* 若 mv 中的 view 是 String 类型,即处理器返回的是模板名称,
* 这里将其解析为具体的 View 对象
*/
if (mv.isReference()) {
// 解析视图,对应步骤⑤
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}
if (logger.isDebugEnabled()) {...}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
// 渲染视图,并将结果返回给用户。对应步骤⑥和⑦
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {...}
throw ex;
}
}
handlerAdapter对应的是Controller,有三种最常用的HandlerAdapter,分别是:
RequestMappingHandlerAdapter主要是适配注解类处理器,注解类处理器就是我们经常使用的@Controller的这类处理器。
HttpRequestHandlerAdapter主要是适配静态资源处理器,静态资源处理器就是实现了HttpRequestHandler接口的处理器,这类处理器的作用是处理通过SpringMVC来访问的静态资源的请求。
SimpleControllerHandlerAdapter是Controller处理适配器,适配实现了Controller接口或Controller接口子类的处理器。
对于实现Controller接口的控制类,执行接口方法即可,对于注解方法的处理器,需要通过反射得到参数名,再映射参数,再执行(invoke)方法。