1. SpringMVC的执行流程
(1) 首先从客户端接收请求,由DispatchServlet(前端控制器/servlet派遣器)接收
(2) 然后传递给HandlerMapping(映射处理器),HandlerMapping开始通过url寻找对应的处理器(Handler类)
(3) Handler类实例化生成一个具体的handler处理器对象和拦截器(如果有则生成),并封装为一个handlerExecuteChain对象回给DispatchServlet(前端控制器)
(4) DispatchServlet调用HandlerAdapter(Handler适配器),HandlerAdapter调用具体的handler处理器对象(中的方法),handler执行完封装为一个modleAndView返回给DispatchServlet
(5) DispatchServlet再将view(逻辑视图)交给ViewResolver(视图解析器)进行解析,返回具体的view(物理视图)
(6) DispatchServlet将view进行model(数据)填充,最终响应用户
2. SpringMVC配置
使用springMVC必须配置的三大件
处理器映射器(HandlerMapping)、处理器适配器(HandlerAdapter)、视图解析器(ViewResolver)
只需要手动配置视图解析器,而处理器映射器和处理器适配器只需要开启注解驱动即可
在resource目录下添加springmvc.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 自动扫描包,让指定包下的注解生效,由IOC容器统一管理 -->
<context:component-scan base-package="nuc.ss.controller"/>
<!-- 让Spring MVC不处理静态资源 -->
<mvc:default-servlet-handler />
<!--
支持mvc注解驱动
在spring中一般采用@RequestMapping注解来完成映射关系
要想使@RequestMapping注解生效
必须向上下文中注册DefaultAnnotationHandlerMapping
和一个AnnotationMethodHandlerAdapter实例
这两个实例分别在类级别和方法级别处理。
而annotation-driven配置帮助我们自动完成上述两个实例的注入。
-->
<mvc:annotation-driven />
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="internalResourceViewResolver">
<!-- 前缀 -->
<property name="prefix" value="/WEB-INF/jsp/" />
<!-- 后缀 -->
<property name="suffix" value=".jsp" />
</bean>
</beans>
3. SpringMVC常用注解
(1) @Controller
@Controller 在类定义处添加,使其成为⼀个控制器(也可以称为Handler),用于接收客户端请求
(2) @RequestMapping
@RequestMapping可以在类,也可以在方法前添加,用于将 URL 请求与业务方法进行映射,也就是让HandlerMapping(映射处理器)能找到处理的业务方法
@RequestMapping注解的参数说明
① value
指定 URL 请求的实际地址
② method
指定请求类型,GET、POST、PUT、DELET,不写则代表都可以接收
③ params
指定请求中必须包含某些参数,否则无法调用该方法
例如以下代码
@RequestMapping(value = "/index",params = {"name","id=123"})
public String toIndex(){
return "index"; }
上述代码表示请求中必须包含 name 和 id 两个参数,同时 id 的值必须是123
参数绑定:
在形参列中通过添加 @RequestParam 注解完成 HTTP 请求参数与业务方法形参的映射
如果不写@RequestParam,那么形参名需要和HTTP请求参数一一对应,如果不对应,则形参会赋予null值
required = true 表示必须要传递参数 defaultValue可以写默认值
@RequestMapping(value = "/index",params = {"name","id=10"})
public String toIndex(@RequestParam("name") String userName,@RequestParam("id",required = false,defaultValue = "123") int userId){
System.out.println(userName);
System.out.println(userId);
return "index";
}
上述代码中,业务方法将请求的参数 name 和 id 分别赋给了形参 userName 和 userId ,同时⾃动完成了数据类型转换,这些工作是由HandlerAdapter(handler适配器)完成的
如果形参是类,HandlerAdapter还能将请求的参数封装成该类对象并赋值
注意:基本类型不能赋予null值,如果不对应且参数类型是基本数据类型则会报错,解决方法:使用包装类
restful 风格的URL参数接收写法:
@RequestMapping("/rest/{name}/{id}")
public String rest(@PathVariable("name") String userName,@PathVariable("id") userId){
System.out.println(userName);
System.out.println(userId);
return "index";
}
(3) @ResponseBody
ResponseBody作用于方法上,代表返回客户端一个json字符串,否则默认是返回给DispatchServlet (servlet派遣器)然后再传递给ViewResolver(视图解析器),也可以看作ResponseBody返回的是Model,不加该注释默认是返回ModelAndView
(4) @RestController
@RestController作用于类上,相当于该类所有方法都标记上了@ResponseBody
补充:什么时候使用@Controller,什么时候使用@RestController?
如果Handler只是纯粹给外提供数据返回服务,则可以选用@RestController,但如果是作为一个完整的后端响应服务,则选用@Controller
4. SpringMVC数据绑定的方式
我们可以将要接收的数据分为几种类型来说明:基本数据类型、实体类、数组、List集合、Map集合、Json
业务方法中的形参会由HandlerAdapter自动去获取
① 基本数据类型
基本数据类型,直接在方法形参中写出即可
② 实体类
实体类需要形参配对(例如input标签的name属性需要和实体类的属性名对应),HandlerAdapter会自动进行封装,对在上一标题内容已经说明
@Data
public class User {
String name;
Integer id;
}
<form action="/save" method="post">
⽤户id:<input type="text" name="id"/><br/>
⽤户名:<input type="text" name="name"/><br/>
<input type="submit" value="注册"/>
</form>
@RequestMapping(value = "/hello/save",method = RequestMethod.POST)
public String login(User user){
return "index";
}
② Array
对于数组,直接在形参中用数组的写法接收
@RequestMapping("/array")
public String array(String[] name){
String str = Arrays.toString(name);
return "index";
}
③ List
Spring MVC 不⽀持 List 类型的直接转换,需要对 List 集合进⾏包装(因为前端参数只能给对象的属性赋值,不能直接给对象赋值)
@Data
public class UserList {
private List<User> users;
}
页面表单提交写法
<form action="/data/list" method="post">
⽤户1编号:<input type="text" name="users[0].id"/><br/>
⽤户1名称:<input type="text" name="users[0].name"/><br/>
⽤户2编号:<input type="text" name="users[1].id"/><br/>
⽤户2名称:<input type="text" name="users[1].name"/><br/>
⽤户3编号:<input type="text" name="users[2].id"/><br/>
⽤户3名称:<input type="text" name="users[2].name"/><br/>
<input type="submit" value="提交"/>
</form>
对应业务方法
@RequestMapping("/list")
public String list(UserList userList){
StringBuffer str = new StringBuffer();
for(User user:userList.getUsers()){
str.append(user.toString());
}
return "index";
}
方法说明:将前端传来的List集合参数作为某个对象的属性来完成赋值
这里即是通过包装类UserList,将List<User>作为UserList的属性以赋值(使用StringBuffer是为了提高效率)
④ Map
Map也需要包装类的方法来完成数据绑定,原理和接收List集合类似
包装类
@Data
public class UserMap {
private Map<String,User> users; }
<body>
<form action="/data/map" method="post">
⽤户1编号:<input type="text" name="users['a'].id"/><br/>
⽤户1名称:<input type="text" name="users['a'].name"/><br/>
⽤户2编号:<input type="text" name="users['b'].id"/><br/>
⽤户2名称:<input type="text" name="users['b'].name"/><br/>
⽤户3编号:<input type="text" name="users['c'].id"/><br/>
⽤户3名称:<input type="text" name="users['c'].name"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
这里的key键可以随便写,但是不要重名
业务方法
@RequestMapping("/map")
public String map(UserMap userMap){
StringBuffer str = new StringBuffer();
for(String key:userMap.getUsers().keySet()){
User user = userMap.getUsers().get(key);
str.append(user);
}
return str.toString();
}
方法说明:通过keySet()方法获得key的集合,然后遍历key,用实体类的实例使用get(Key)方法取出对象以完成赋值
⑤ Json
Spring MVC 中的 JSON 和 JavaBean 的转换需要借助于 fastjson,在pom.xml 引⼊相关依赖
<!-- 配置fastjson -->
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4"></bean>
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script type="text/javascript">
$(function(){
var user = {
"id":123,
"name":"小明"
};
$.ajax({
url:"/json",
data:JSON.stringify(user),
type:"POST",
contentType:"application/json;charset=UTF-8",
dataType:"JSON",
success:function(data){
alter(data.id+" "+data.name);
}
})
});
</script>
@RequestMapping("/json")
public User json(@RequestBody User user){
System.out.println(user);
return "index";
}
5. SpringMVC 数据模型解析(添加模型数据)
几种常见的添加模型数据方式:
① Map
② Model
③ ModelAndView
④ @ModelAttribute
⑤ @SessionAttribute
前四种都是将数据添加到Request ,⑤是添加到session
(1) Map
通过Map存入一组键值对,同时会将其也存入Request中,在前端页面直接从Request中去获取数据,也就是等同于直接使用request.addAttribute(“user”,user)
业务方法
@RequestMapping("/map")
public String map(Map<String,User> map){
User user = new User();
user.setId(123);
user.setName("小明");
map.put("user",user);
return "view";
}
前面取得数据的方式(el表达式)
<body>
${requestScope.user}
</body>
(2) Model
@RequestMapping("/model")
public String model(Model model){
User user = new User();
user.setId(123);
user.setName("小明");
model.addAttribute("user",user);
return "view";
}
(3) ModelAndView
使用ModelAndView有多钟添加数据的方式,原理都是通过构造函数传参的不同去添加,这里列出3个例子
@RequestMapping("/modelAndView")
public ModelAndView modelAndView(){
User user = new User();
user.setId(123);
user.setName("小明");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
modelAndView.setViewName("view");
return modelAndView;
}
@RequestMapping("/modelAndView2")
public ModelAndView modelAndView2(){
User user = new User();
user.setId(123);
user.setName("小明");
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user",user);
View view = new InternalResourceView("/view.jsp");
modelAndView.setView(view);
return modelAndView;
}
@RequestMapping("/modelAndView3")
public ModelAndView modelAndView3(){
User user = new User();
user.setId(123);
user.setName("小明");
ModelAndView modelAndView = new ModelAndView("view");
modelAndView.addObject("user",user);
return modelAndView;
}
(4) HttpServletRequest
原生的Servlet的Request添加方式
@RequestMapping("/request")
public String request(HttpServletRequest request){
User user = new User();
user.setId(123);
user.setName("小明");
request.setAttribute("user",user);
return "view";
}
(5) @ModelAttribute注解
@ModelAttribute 注解用于添加在方法上,说明该方法专门用来返回要填充到模型数据中的对象,添加了该注解的方法总是会先于业务方法先执行(执行任意一个业务方法前都会先执行该方法再执行)
不使用return也可以使用Map或者Model来添加数据
@ModelAttribute
public User getUser(){
User user = new User();
user.setId(123);
user.setName("小明");
return user;
}
使用了该注解后,业务方法不需要再去添加该方法已添加的数据
@RequestMapping("/modelAttribute")
public String modelAttribute(){
return "view";
}
(6) 将数据添加到session中
① 在业务方法中添加到session
session是从request中得到的
@RequestMapping("/session")
public String session(HttpServletRequest request){
HttpSession session = request.getSession();
User user = new User();
user.setId(123);
user.setName("小明");
session.setAttribute("user",user);
return "view";
}
获取session这一步可以直接交给HandlerAdapter去做,即可以直接在形参中写出
@RequestMapping("/session2")
public String session(HttpSession session){
User user = new User();
user.setId(123);
user.setName("小明");
session.setAttribute("user",user);
return "view";
}
② 使用@SessionAttribute注解(一般不常用)
@SessionAttribute注解用于添加在类上,表示该类所业务方法执行时,都会对应去查找@SessionAttribute中的value值,如果request.setAttribute中的key值和value值中的key对应,那么会直接添加到对应session中
例如
@SessionAttributes(value = {"user","address"})
public class ViewHandler {
}
那么该类的业务方法中只要向 request 中添加了 key = “user”、key = “address” 的对象时,SpringMVC 会⾃动将该数据添加到 session 中
(7) 将数据添加到Application中
@RequestMapping("/application")
public String application(HttpServletRequest request){
ServletContext application = request.getServletContext();
User user = new User();
user.setId(123);
user.setName("小明");
application.setAttribute("user",user);
return "view";
}
备注:使用HttpServletRequest或session都是使用了servlet原生api的方式
6. 自定义数据转换器
数据转换是指将客户端 HTTP 请求中的参数转换为业务方法中定义的形参
HandlerApdter 已经提供了部分转换,String 转 int,String 转 double,表单数据的封装(成实体类)等
但是在特殊的业务场景下,HandlerAdapter ⽆法进进转换,就需要自定义转换器
以客户端传入String类型,但是需要转换成Date类型为例
① 创建DateConverter类,继承Converter接口,实现convert方法,即代表它是一个转换器
public class DateConverter implements Converter<String, Date> {
private String data;
public DateConverter(String data){
this.data = data;
}
@Override
public Date convert(String str) {
SimpleDateFormat simpleDateFormat = new
SimpleDateFormat(this.data);
Date date = null;
try {
date = simpleDateFormat.parse(str);
} catch (ParseException e) {
e.printStackTrace();
}
return date;
}
}
② 在xml中配置自定义转换器
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<list>
<bean class="com.ruoxi.converter.DateConverter">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"></constructor-arg>
</bean>
</list>
</property>
</bean>
在mvc驱动中注册转换器
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
③ 表单提交
<body>
<form action="/converter/date" method="post">
请输⼊⽇期:<input type="text" name="date"/>(yyyy-MM-dd)<br/>
<input type="submit" value="提交"/>
</form>
</body>
④ 业务方法
@ResponBody
@RequestMapping("/date")
public String date(Date date){
return date.toString();
}
7. 文件上传与下载
Spring MVC 对 Apache fileupload 组件这种上传方式进⾏了封装
(1) 配置
① 在pom.xml中添加依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
② web.xml 中防止过滤静态文件 即以默认的方式去处理不交给servletMapping(这里以.png为例)
(静态资源都需要这么做 在标题2中的xml已经完成,单独设置的话需要以下这么做)
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.png</url-pattern>
</servlet-mapping>
③ springmvc.xml 配置上传组件 (resouces中的xml)
<!-- 配置上传组件 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
(2) 单文件上传
1、input 的 type 设置为 file
2、form 的 method 设置为 post(get 请求只能将文件名传给服务器)
3、form 的 enctype 设置为 multipart-form-data(如果不设置只能将⽂件名传给服务器)
<body>
<form action="/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="img"/>
<input type="submit" value="上传"/>
</form>
<img src="${path}">
</body>
文件上传原理是调用输入输出流,SpringMVC对其进行了封装,可以直接调用transferTo(File)函数
request.setAttribute(“path”,"/file/"+name)可以保存文件的路径,通过这种方式,前端可以使用${}el表达式取得文件在页面上展示
@PostMapping("/upload")
public String upload(MultipartFile img, HttpServletRequest request){
if(img.getSize()>0){
//获取保存上传⽂件的file路径 (这里默认是在tomcat文件夹里)
String path = request.getServletContext().getRealPath("file");
//获取上传的⽂件名
String name = img.getOriginalFilename();
//根据file路径创建一个空的名字为name的文件
File file = new File(path,name);
try {
//将MultipartFile img文件赋予给file
img.transferTo(file);
//保存上传之后的⽂件路径
request.setAttribute("path","/file/"+name);
} catch (IOException e) {
e.printStackTrace();
}
}
return "upload";
}
SpringMVC中如何实现转发和重定向,有什么区别?
转发到页面return"forward:+绝对地址"。转发到控制器其他方法:return的是"forward:+类上requestmapping的地址+方法上requestmapping的地址"
重定向到页面:return的是"redirect:+绝对地址",注意不能重定向访问WEB-INF下的资源。重定向到控制器其他方法:return的是"redirect:+类上requestmapping的地址+方法上requestmapping的地址"。重定向到外部链接:return的是"redirect:+链接地址
转发和重定向的区别是转发只是一次请求,重定向是两次请求;转发地址栏不变,重定向地址栏将改变;转发只能到内部资源,重定向可以到内部或外部资源;转发可以到WEB-INF下资源,重定向不可以