文章目录
SpringMVC的基本概念
MVC模型
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种用于设计创建 Web 应用程序表现层的模式。
SpringMVC介绍
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编程风格的请求。
- 前端控制器(DispatcherServlet)
- 请求到处理器映射(HandlerMapping)
- 处理器适配器(HandlerAdapter)
- 视图解析器(ViewResolver)
- 处理器或页面控制器(Controller)
- 验证器( Validator)
- 命令对象(Command 请求参数绑定到的对象就叫命令对象)
- 表单对象(Form Object 提供给表单展示和提交到的对象就叫表单对象)
SpringMVC与Struts2比较
共同点:
- 它们都是表现层框架,都是基于MVC模型编写的;
- 它们的底层都离不开原始ServletAPI;
- 它们处理请求的机制都是一个核心控制器;
区别:
- Spring MVC的入口是Servlet,而Struts2是Filter;
- Spring MVC是基于方法设计的(单例),而Struts2是基于类,Struts2每次执行都会创建一个动作类(多例)。所以Spring MVC会稍微比Struts2快些。
- Spring MVC使用更加简洁,同时还支持 JSR303,处理ajax的请求更方便 (JSR303 是一套JavaBean参数校验的标准,它定义了很多常用的校验注解,我们可以直接将这些注解加在我们JavaBean的属性上面,就可以在需要校验的时候进行校验了。)
- Struts2的OGNL表达式使页面的开发效率相比Spring MVC更高些,但执行效率并没有比JSTL提升,尤其是struts2的表单标签,远没有html执行效率高。
入门Demo
准备
利用Maven搭建一个webapp项目并将目录补全,并在pom.xml中引入坐标:
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
配置
配置核心控制器,在web.xml中:
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在resources目录下创建SpringMVC的配置文件springmvc.xml
<?xml version="1.0" encoding="UTF-8"?>
<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>
<!-- 开启SpringMVC框架注解的支持 -->
<mvc:annotation-driven/>
</beans>
Controller
创建处理请求的类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" %>
<html>
<head>
<title>Demo</title>
</head>
<body>
<h3>入门程序</h3>
<a href="hello">入门程序</a>
</body>
</html>
成功页面WEB-INF/pages/success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门成功</h3>
</body>
</html>
于是在主页面点击超链接后自动跳转到success.jsp页面
总结
基于组件方式:
在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件。
使用<mvc:annotation-driven>
自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在SpringMVC.xml配置文件中使用<mvc:annotation-driven>
替代注解处理器和适配器的配置。
RequestMapping注解
@RequestMapping注解用于建立请求URL和处理请求方法之间的对应关系。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {}
出现位置
- 类上: 请求URL的第一级访问目录,此处不写的话,就相当于应用的根目录,写的话需要以/开头。 它出现的目的是为了使我们的URL可以按照模块化管理:
例如: 账户模块:
- /account/add
- /account/update
- /account/delete …
把account的RequsetMapping写在类上,使URL更加精细。
- 方法上: 请求URL的第二级访问目录。
属性
- value:用于指定请求的URL,它和path属性的作用是一样的;
- method:用于指定请求的方式;
- params:用于指定限制请求参数的条件,它支持简单的表达式。要求请求参数的key和value必须和配置的一模一样。 例如:
params = {"accountName"}
,表示请求参数必须有accountName
params = {"moeny!100"}
,表示请求参数中money不能是100。 - headers:用于指定限制请求消息头的条件。
注意: 以上四个属性只要出现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绑定请求参数的过程是通过把表单提交请求参数,作为控制器中方法参数进行绑定的。
<a href="param/testParam?username=hehe&password=123">请求参数绑定</a>
请求参数为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类中
<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="user.uname" /><br/>
用户年龄:<input type="text" name="user.age" /><br/>
<input type="submit" value="提交" />
</form>
控制器代码:
/**
* 请求参数绑定到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>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-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错误。
<a href="account/deleteAccount?date=2018-01-01">根据日期删除账户</a>
日期格式不正确,应该为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中配置该转换器:
<!-- 开启SpringMVC框架注解的支持 -->
<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>
获取Servlet原生API
把原生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注解。
<a href="anno/testRequestParam?name=潇哥">RequestParam</a>
控制器:
/**
* 常用注解
*/
@Controller
@RequestMapping("/anno")
public class AnnoController {
@RequestMapping("/testRequestParam")
public String testRequestParam(@RequestParam(name="name") String username){
System.out.println("执行了...");
return "success";
}
}
RequestBody
作用: 用于获取请求体内容。直接使用得到是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";
}
PathVariable
restful风格
Restful(表述性状态转移)风格的URL:
请求路径一样,可以根据不同的请求方式去执行后台的不同方法。简单的说就是:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描述操作。
核心思想就是每个资源都有它的状态,不同状态下,可对它进行的操作不一样。
restful风格的URL优点:
- 结构清晰
- 符合标准
- 易于理解
- 扩展方便
PathVariable注解
作用:拥有绑定url中的占位符的。例如:url中有/delete/{id},{id}就是占位符。
<a href="user/hello/1">入门案例</a>
控制器:
/**
* PathVariable注解
* @return
*/
@RequestMapping(value="/testPathVariable/{sid}")
public String testPathVariable(@PathVariable(name="sid") String id){
System.out.println("执行了...");
System.out.println(id);
return "success";
}
RequestHeader
作用:获取指定请求头的值
属性: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";
}
CookieValue
作用:用于获取指定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
出现在方法上:表示当前方法会在控制器方法执行前线执行。
出现在参数上:获取指定的数据给参数赋值。
应用场景:当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。
/**
* 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执行。
SeesionAttributes
用于多次执行控制器方法间的参数共享。
@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}
void
- 转发
@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 指定响应结果,例如响应json 数据:
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("json 串");
ModelAndView
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";
}
ResponseBody响应json数据
用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;
}
文件上传
文件上传的前提
- form表单的enctype取值必须是:multipart/form-data (默认值是:application/x-www-form-urlencoded) enctype:是表单请求正文的类型
- method属性取值必须是Post
- 提供一个文件选择域
<input type=”file” />
引入依赖:
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
原理
控制器代码
前端:
<form action="/file/fileUpLoad" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/><br/>
<input type="submit" value="上传"/>
</form>
控制器:
/**
* 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";
}
SpringMVC的异常处理
异常处理思路: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框架中的拦截器用于对处理器进行预处理和后处理的技术。
可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。
拦截器和过滤器的功能比较类似,也有区别
- 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
- 拦截器是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>
三个拦截器方法
- preHandle方法是controller方法执行前拦截的方法
- 可以使用request或者response跳转到指定的页面
- return true放行,执行下一个拦截器,如果没有拦截器,执行controller中的方法。
- return false不放行,不会执行controller中的方法。
- postHandle是controller方法执行后执行的方法,在JSP视图执行前。
- 可以使用request或者response跳转到指定的页面
- 如果指定了跳转的页面,那么controller方法跳转的页面将不会显示。
- postHandle方法是在JSP执行后执行
- request或者response不能再跳转页面了
拦截器链
<!--配置拦截器-->
<mvc:interceptors>
<!--配置拦截器-->
<mvc:interceptor>
<!--要拦截的具体的方法-->
<mvc:mapping path="/user/*"/>
<!--不要拦截的方法
<mvc:exclude-mapping path=""/>
-->
<!--配置拦截器对象-->
<bean class="top.tjtulong.controller.cn.itcast.interceptor.MyInterceptor1" />
</mvc:interceptor>
<!--配置第二个拦截器-->
<mvc:interceptor>
<!--要拦截的具体的方法-->
<mvc:mapping path="/**"/>
<!--不要拦截的方法
<mvc:exclude-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
- hello:应用目录,hello就是应用的名称(在IDEA中创建项目时是web目录,执行时自动改名为项目名);
- index.html:应用资源。应用下可以有多个资源,例如css、js、html、jsp等,也可以把资源放到文件夹中,例如:hello\html\index.html,这时访问URL为:http://localhost:8080/hello/html/index.html;
- WEB-INF:这个目录名称必须是大写,这个目录下的东西是无法通过浏览器直接访问的,也就是说放到这里的东西是安全的;
- web.xml:应用程序的部署描述符文件,可以在该文件中对应用进行配置;
- classes:存放class文件的目录;
- lib:存放jar包的目录。
SpringMVC源码分析
参考:田小波的博客
一个请求的流程
首先,用户的浏览器发出了一个请求,这个请求经过互联网到达了我们的服务器。Servlet容器首先接待了这个请求,并将该请求委托给前端控制器DispatcherServlet进行处理。接着DispatcherServlet将该请求传给了处理器映射组件HandlerMapping,并获取到适合该请求的拦截器和处理器。在获取到处理器后,DispatcherServlet还不能直接调用处理器的逻辑,需要进行对处理器进行适配。处理器适配成功后,DispatcherServlet通过处理器适配器HandlerAdapter调用处理器的逻辑,并获取返回值ModelAndView。之后,DispatcherServlet需要根据ModelAndView解析视图。解析视图的工作由ViewResolver完成,若能解析成功,ViewResolver会返回相应的视图对象View。在获取到具体的View对象后,最后一步要做的事情就是由View渲染视图,并将渲染结果返回给用户。
在请求过程中要经过许多组件:
从上面的流程中可以看出,Spring MVC对各个组件的职责划分的比较清晰。DispatcherServlet负责协调,其他组件则各自做分内之事,互不干扰。经过这样的职责划分,代码会便于维护。
DispatcherServlet
HttpServlet
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 方法中,该方法根据请求类型分发请求。我们可以根据需要覆盖指定的处理方法。
DispatcherServlet族谱
继承关系图:
- Aware
在Spring中,Aware类型的接口用于向Spring“索要”一些框架中的信息。比如当某个bean实现了ApplicationContextAware接口时,Spring在运行时会将当前的ApplicationContext实例通过接口方法setApplicationContext传给该bean。 - EnvironmentCapable
EnvironmentCapable仅包含一个方法定义getEnvironment,通过该方法可以获取到环境变量对象。 - HttpServletBean
HttpServletBean 是 HttpServlet 抽象类的简单拓展。HttpServletBean 覆写了父类中的无参 init 方法,并在该方法中将 ServletConfig 里的配置信息设置到子类对象中,比如 DispatcherServlet。 - FrameworkServlet
FrameworkServlet是Spring Web框架中的一个基础类,该类会在初始化时创建一个容器。同时该类覆写了doGet、doPost等方法,并将所有类型的请求委托给doService方法去处理。doService是一个抽象方法,需要子类实现。 - DispatcherServlet
DispatcherServlet协调各个组件工作。除此之外,DispatcherServlet还有一个重要的事情要做,即初始化各种组件,比如HandlerMapping、HandlerAdapter等。
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);
// 获取可处理当前请求的处理器 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
handlerAdapter对应的是Controller,有三种最常用的HandlerAdapter,分别是:
- RequestMappingHandlerAdapter
- HttpRequestHandlerAdapter
- SimpleControllerHandlerAdapter
RequestMappingHandlerAdapter主要是适配注解类处理器,注解类处理器就是我们经常使用的@Controller的这类处理器。
HttpRequestHandlerAdapter主要是适配静态资源处理器,静态资源处理器就是实现了HttpRequestHandler接口的处理器,这类处理器的作用是处理通过SpringMVC来访问的静态资源的请求。
SimpleControllerHandlerAdapter是Controller处理适配器,适配实现了Controller接口或Controller接口子类的处理器。
对于实现Controller接口的控制类,执行接口方法即可,对于注解方法的处理器,需要通过反射得到参数名,再映射参数,再执行(invoke)方法。