SpringMVC对web层的支持笔记整理
1. 什么是MVC
MVC是web层的一种设计模式 , 全称为 Model View Controller
- Model : 数据模型 (接收和响应的数据 , 可以理解为对应的实体类)
- View : 视图 , jsp和html也属于一种试图 , 用于数据的展示
- Controller : 控制器 , 用于数据模型和试图的中转位置
2. SpringMVC概述
2.1. SpringMVC简单介绍
Spring Web MVC是基于Servlet API构建的原始Web框架 , 并从一开始就包含在Spring Framework中 , 正式名称"Sprnig Web MVC"来自其源模块spring-webmvc的名称 , 但它通常被成为"Spring MVC"
简而言之 , SpringMVC对Servlet进行封装 , 避免了繁琐的获取表单参数 , 多余的servlet服务类等代码
2.2. Spring对web层的支持
- spring对web层的支持需要用到2个包 :
- spring-web : spring对web层的支持包
- spring-webmvc : springwebmvc的框架包
- spring对web层的支持提供的技术 :
- spring webmvc : springmvc框架
- spring webFlux : spring webFluex框架
- 这两个框架的作用一样 , 只是写法不同
3. SpringMVC的基本使用
3.1. 创建web工程
不做演示
3.2. 导入jar包
4个核心包 + 1个日志包 + spring-web + spring-webmvc + spring-aop 共计8个包
3.3. 创建并定义springmvc配置文件
<!-- 配置处理器映射器-->
<bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
<!-- 处理器适配器-->
<bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
<!-- 视图解析器-->
<bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置前置-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 配置后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!-- 配置自定义类 , 将ControllerTest交给Spring管理 , 并加入容器-->
<bean name="/test" class="com.springMVCTest.controller.ControllerTest"/>
</beans>
3.4. 创建Controller
public class ControllerTest implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
}
3.5. 编写一个页面
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>成功</h1>
</body>
</html>
3.6. 配置web.xml
<!-- 配置核心控制器 加载springmvc的配置文件-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--初始化参数加载配置文件
key的写法是固定的必须是contextConfigLocation , 不能写其他-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
4. SpringMVC的执行流程
流程一共分为2大阶段
- Tomcat启动阶段
- Tomcat启动并加载web.xml
- web.xml配置了一个servlet
- 如果servlet中的
<load-on-startup>1</load-on-startup>指定为1 , 则在Tomcat启动时Servlet直接实例化并且初始化方法将会执行
- 如果servlet中的
- servlet只要初始化就会加载springmvc的配置文件
- springmvc配置文件加载 , 会将配置的4个bean标签对应的对象实例化好并放到容器中
- 浏览器访问阶段
- 浏览器访问
http://localhost:8080/test通过域名端口找到了本机域名端口 - 然后通过
/test访问 , 因为<url-pattern>/</url-pattern>参数配置为/, 此时请求会到达DispatcherServlet, 说明请求到达了springmvc - 通过
/test, 就可以从容器中获取对应的Controller , 直接执行controller方法 - 返回内容给浏览器显示
- 浏览器访问
图解 :

5. SpringMVC的内部流程
流程文字解释 :
- 请求
/test到达了DispatcherServlet DispatcherServlet会携带请求找到处理器映射器BeanNameUrlHandlerMapping, 通过URL当做Bean的name属性值从容器中找到ControllerTest- 处理器映射器
BeanNameUrlHandlerMapping找到之后 , 将找到的结果返回给DispatcherServlet` DispatcherServlet把执行的内容给处理器适配器SimpleControllerHandlerAdapter, 处理器适配器去调用对应的controller方法 , 获得ModelAndView- 处理器适配器
SimpleControllerHandlerAdapter拿到ModelAndView之后 , 把结果给了DispatcherServlet - 处理器适配器
DispatcherServlet拿到返回的结果之后 , 将结果给视图解析器InternalResourceViewResolver拼接视图的前后缀 , 然后就拿到了真实的视图地址 - 把地址返回给
DispatcherServlet,DispatcherServlet拿到真正视图地址 , 返回的Model给模版引擎 - 使用模版引擎技术渲染页面 , 并返回给前端
图解 :

了解不同的写法使用不同的处理器映射器和不同的处理器适配器
- 总结 :
- dispatcherServlet : 是一个中转站 , 所有的功能都需要dispatcherServlet 做中转 , 这个servlet不能少 , dispatcherServlet 是springmvc的核心核心控制器
- 处理器映射器 : 通过请求才能找到对应的bean
- 处理器适配器 : 主要执行对应bean的方法
- 试图解析器 : 拼接前后缀 , 找到视图的真实地址
- jsp : 是一个模版引擎 , 是渲染数据使用的 使用EL表达 jstl标签
6. 实现一个类中不同的请求做不同的事
问题 : 定义的Controller因为只有一个实现 , 因此一个controller只能做一件事 , 并且每次都需要配置Bean
解决 : 通过包扫描和注解的方式完成一个类中 , 不同的请求做不同的事情
6.1. 编写Controller
- 使用包扫描的方式将Controller添加入容器 , 需要在类上添加注解
@Controller这样aop扫描会将当前类作为controller加入容器 - 注意 :
@Controller是@Component的一个延伸 , 当类作为Controller时不能使用@Component, 否则会报错
//@Component 使用Component注解将本类加入容器中 ,
//在web层中又一个Component注解的延伸 : @Controller
//@Controller可以通过aop扫描将当做一个controller
//注意 : 在web层只能使用@Controller不能使用@Component否则会报错
//使用@Controller需要配置包扫描
@Controller
public class ControllerTest2 {
//GetMapping注解 : 此方法只接受get请求
//注解参数 : 表示请求路径
//登陆操作
@GetMapping("login")
private ModelAndView login() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "登录成功");
modelAndView.setViewName("success");
return modelAndView;
}
//注册操作
@GetMapping("register")
private ModelAndView register() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "注册成功");
modelAndView.setViewName("success");
return modelAndView;
}
}
6.2. 编写配置文件
<!-- 配置包扫描-->
<context:component-scan base-package="com.springMVCTest.controller"/>
<!--处理器映射器 根据请求路径去找到对应的映射-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<!--处理器适配器 根据请求路径-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
<!-- 视图解析器-->
<bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置前置-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 配置后缀-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
6.3. 配置优化
开启mvc注解驱动
<!-- 配置包扫描-->
<context:component-scan base-package="com.springMVCTest.controller"/>
<!-- 使用这个标签替代处理器映射器和处理器适配器-->
<mvc:annotation-driven/>
<!-- 视图解析器-->
<bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置前置-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 配置后缀-->
<property name="suffix" value=".jsp"/>
</bean>
</beans>
7. springMVC请求接收
常用的http请求
- get : get请求 springMVC中对应的是
@GetMapping - post : post请求 springMVC中对应的是
@PostMapping - delete : delet请求 springMVC中对应的是
@DeleteMapping - put : put请求 springMVC中对应的是
@PutMapping @RequestMapping: 默认情况下可以用于任何请求方式
8. 参数的接收与封装
SpringMVC接受参数的方式 :
- key=value方式
- json方式
- 文件接收
8.1. key=value形式接收参数
只要参数的接受方式是以key=value的形式接收 , 那么所有的请求方法都可以按如下方法使用
注意 : 表单的name属性就是请求参数的可以 , 需要和方法方法的参数名一致 , 此时会自动将数据封装上
8.1.1. 基本数据类型和字符串
collector :
@GetMapping("basicParam")
public ModelAndView basicParam(int age, String name) {
System.out.println(age);
System.out.println(name);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
jsp :
<h2>key = value 基本类型和字符串的接收</h2>
<form action="/basicParam">
年龄 <input type="text" name="age"><br>
姓名 <input type="text" name="name">
<input type="submit" value="提交">
</form>
8.1.2. 数组
collector :
@GetMapping("arrayParam")
public ModelAndView arrayParam(String[] check) {
System.out.println(Arrays.toString(check));
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
jsp :
<h2>key = value 基本类型和字符串的接收</h2>
<form action="/arrayParam">
选择 :
<input type="checkbox" name="check" value="A">A
<input type="checkbox" name="check" value="B">B
<input type="checkbox" name="check" value="C">C<br>
<input type="submit" value="提交">
</form>
8.1.3. 对象类型
注意 : 使用对象类型接收数据 , 表单的name属性要和实体类中的属性名一致 , 和controller方法的接受对象名没有关系
创建对象 :
public class User {
private String name;
private int age;
private String sex;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
controller :
@GetMapping("objectParam")
public ModelAndView objectParam(User user) {
System.out.println(user);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
jsp :
<h2>key = value 对象接收</h2>
<form action="/objectParam">
<!--name属性的值要与实体类的属性值相同-->
姓名<input type="text" name="name"/><br>
性别<input type="text" name="sex"/><br>
年龄<input type="text" name="age"/><br>
地址<input type="text" name="address"/><br>
<input type="submit" value="提交">
</form>
8.1.4. 包装类类型 ( 不重要 )
包装类 :
public class UserExt {
private User user;
private List<User> list;
private Map<String, User> map;
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, User> map) {
this.map = map;
}
public List<User> getList() {
return list;
}
public void setList(List<User> list) {
this.list = list;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@Override
public String toString() {
return "UserExt{" +
"user=" + user +
", list=" + list +
", map=" + map +
'}';
}
}
controller :
@GetMapping("extParam")
public ModelAndView extParam(UserExt userExt) {
System.out.println(userExt);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
jsp :
<h2>key = value 包装类接收</h2>
<form action="/objectParam">
姓名<input type="text" name="user.name"/><br>
性别<input type="text" name="user.sex"/><br>
年龄<input type="text" name="user.age"/><br>
地址<input type="text" name="user.address"/><br>
<input type="submit" value="提交">
</form>
8.1.5. List集合类型 ( 不重要 )
controller :
@GetMapping("listParam")
public ModelAndView listParam(UserExt userExt) {
userExt.getList().forEach(user -> System.out.println(user));
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
jsp :
<h2>key = value list接收</h2>
<form action="/objectParam">
<input type="text" name="list[0].name"/><br>
<input type="text" name="list[0].age"/><br>
<input type="text" name="list[1].name"/><br>
<input type="text" name="list[1].age"/><br>
<input type="text" name="list[2].name"/><br>
<input type="text" name="list[2].age"/><br>
<input type="text" name="list[3].name"/><br>
<input type="text" name="list[3].age"/><br>
<input type="submit" value="提交">
</form>
8.1.6. Map集合类型 ( 不重要 )
controller :
@GetMapping("mapParam")
public ModelAndView mapParam(UserExt userExt) {
System.out.println(userExt.getMap());
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
jsp :
<h2>key = value map接收</h2>
<form action="/objectParam">
<input type="text" name="map['a']"/><br>
<input type="text" name="map['b']"/><br>
<input type="text" name="map['c']"/><br>
<input type="text" name="map['d']"/><br>
<input type="submit" value="提交">
</form>
8.2. JSON格式字符串
- JSON格式接收请求一般用于Post请求和Put请求 , 因为传递的josn格式是一个字符串 , 需要放到请求体中 , 供java代码直接使用对象接收
- 后端获取到json字符串需要通过
@RequestBody从请求体中获取json格式字符串封装到对象内 - 将json字符串转换为对象 , 需要依赖于第三方的json和对象转换包
- 第三方转换包有 :
json-lib(已淘汰)fastJsonjacksongsonjson-b
- 第三方转换包有 :
- springMVC默认支持
jsckson, 如果是jackson不需要任何配置 , 导入包即可 , 如果不是jackson需要配置 - JSON格式传递参数不会乱码
@Controller
public class JSONStringReceiveController {
@PostMapping("receiveJson")//@RequestBody 表示从请求体中获取json格式字符串封装到对象内
public ModelAndView receiveJson(@RequestBody String strJson) throws IOException {
//获取的字符串无法直接转换为对象 , 需要依赖于第三方的json和对象转换包
//springmvc默认支持jackson , 如果使用jackson转换对象 , 不需要任何配置
ObjectMapper objectMapper = new ObjectMapper();
User user = objectMapper.readValue(strJson, User.class);
System.out.println(user);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
}
//==============使用jackson自动将json封装为对象===========
@Controller
public class JSONStringReceiveController2 {
@PostMapping("receiveJson")//@RequestBody 表示从请求体中获取json格式字符串封装到对象内
public ModelAndView receiveJson(@RequestBody User user) throws IOException {
//获取的字符串无法直接转换为对象 , 需要依赖于第三方的json和对象转换包
//springmvc默认支持jackson , 如果使用jackson转换对象 , 不需要任何配置
//ObjectMapper objectMapper = new ObjectMapper();
//User user = objectMapper.readValue(strJson, User.class);
System.out.println(user);
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
}
8.3. Rest方式接收参数
9. 乱码问题
9.1. get请求接收汉字参数乱码
-
get请求在Tomcat8以上没有乱码问题 , 如果使用Tomcat7会有乱码
-
根据String的构造方法
String s = new String(username.getBytes("ISO-8859-1"),"utf-8") -
修改配置文件
/** 修改 Tomcat中的config目录下 server.xml 添加 URIEncoding="utf-8" 注意点:如果有 useBodyEncodingForURI="true" 就删除 例如: <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" disableUploadTimeout="true" executor="tomcatThreadPool" URIEncoding="utf-8"/> */
9.2.post请求接收汉字参数乱码
- 解决方式
- 和get方法一样 , 使用String的构造函数解决 ( 了解即可 )
- 使用Spring提供的过滤器一次性解决
<!-- 使用spring提供的编码过滤器解决post请求的乱码问题-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>characterEncodingFilter</param-name>
<param-value>utf-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
提问 : post一定会出现乱码么?
不一定会 , 需要根据post请求的位置判断 ,
**如果post请求的位置在请求体中 ( 参数不拼接到url后面 ) 会出现乱码 , **
如果在请求头中 ( 参数拼接到url后面 ) 不会出现乱码
10. 获取ServletAPI
想要得到最原始的
HttpservletRequest和HttpServletResponse直接在方法里写形参即可 ,然后就可在代码中使用
@GetMapping("returnView")
public String returnView(HttpServletRequest request) {
//可以拿对象随意操作
request.setAttribute("msg", "request内数据");
return "success";
}
11. Controller的返回值
11.1. ModelAndView作为返回值
/*
* 返回值ModelAndView
* 携带数据返回视图(JSP页面)
* 在JSP页面可以用EL表达式输出
* 默认请求是转发
* */
@GetMapping("ReturnModelAndView")
public ModelAndView ReturnModelAndView() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", "ModelAndView携带的数据");
modelAndView.setViewName("success");
return modelAndView;
}
11.2. 字符串作为返回值
返回值是字符串共有3中用法 :
- 返回视图
- 返回的字符串携带数据的方式
- 放到域中 , 所有域都可以
- 放到Model中 , 本质也是放到域中
- 放到Map中 , 本质也是放到域中
- 返回的字符串携带数据的方式
- 返回字符串本身
- 返回字符串本身有可能产生乱码 ,
@GetMapping等注解的produces属性可以设置返回值的样式和编码格式produces = "text/html;charset=utf-8"可以识别html标签produces = "application/json;charset=utf-8"可以识别jsonproduces = "charset=utf-8"设置编码格式
@ResponseBody: 通过HttpServletResponse输出数据 , 数据的返回值以响应体返回给页面 , 而不是页面
- 返回字符串本身有可能产生乱码 ,
- 转发或重定向
- 返回字符串做转发 :
forward: + 转发的地址 , 注意加"/" - 返回字符串做重定向 :
redirect + 转发的地址 , 注意加"/"
- 返回字符串做转发 :
11.2.1. 返回字符串作为 : 视图
返回视图同时携带数据 :
//返回视图-HttpServletRequest
@GetMapping("returnView1")
public String returnView1(HttpServletRequest request) {
request.setAttribute("msg", "request内数据");
return "success";
}
//返回视图-Model
@GetMapping("returnView2")
public String returnView2(Model model) {
model.addAttribute("msg", "model内数据");
return "success";
}
//返回视图-Map
@GetMapping("returnView3")
public String returnView3(Map<String, String> map) {
map.put("msg", "map内数据");
return "success";
}
11.2.2. 返回字符串作为 : 字符串
/*
* 返回字符串本身
* GetMapping注解的produces属性可以设置返回值的样式 ,
* text/html可以识别html标签 ,
* application/json可以识别json,
* charset=utf-8设置编码格式
* @ResponseBody : 通过HttpServletResponse输出数据 , 数据的返回值以相应体返回给页面 , 而不是页面
*/
@GetMapping(value = "returnStringSlf", produces = "text/html;charset=utf-8")
@ResponseBody //数据的返回值以相应体返回给页面 , 而不是页面
public String returnStringSlf() {
//可以返回中文 , 但是会乱码 , 需要在produce属性中添加charset=utf-8
//String json="{\"name\":\"aa\",\"age\":\"10\"}";
//以json格式返回字符串 , 需要在produce属性中添加application/json
//return json;
//直接返回字符串success
return "success";
}
统一设置避免乱码 :
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<span style="white-space:pre"></span>
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
<value>*/*;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
11.2.3. 返回字符串作为 : 转发或重定向
//返回字符串做转发 : forward: + 转发的地址 , 注意加"/"
@GetMapping(value = "returnStrForwardOrRedirect1")
public String returnStrForwardOrRedirect1() {
//请求转发到returnStringSlf , 转发 : 不改变地址栏 , 服务器内部行为
return "forward:/returnStringSlf";
}
//返回字符串做重定向 : redirect: + 转发的地址 , 注意加"/"
@GetMapping(value = "returnStrForwardOrRedirect2")
public String returnStrForwardOrRedirect2() {
//页面重定向至returnStringSlf , 重定向 : 改变地址栏 , 浏览器行为
return "redirect:/returnStringSlf";
}
11.3. 返回void ( 无返回值 )
/*
* 返回void
* 默认找的是以请求路径为视图名称的jsp页面
* */
@GetMapping("returnVoidController")
public void returnVoidController(){
System.out.println("xxxxx");
}
11.4. 返回对象
- springMVC能够将对象转换为json但是需要依赖Jackson
@ResponseBody: 把处理器对象转换为json后 , 返回给浏览器 , 位置位于方法的定义上面
/*
* 处理器方法返回一个user , 通过框架转为json , 响应请求
* @ResponseBody
* 作用 : 把处理器方法返回对象转为json后 , 通过HttpServletResponse输出给浏览器
* 位置在方法的定义上面 , 和其他注解没有先后顺序
* */
@GetMapping("returnUser")
@ResponseBody
public User returnUser() {
User user = new User();
user.setName("aaa");
user.setAge(10);
return user;//会被框架转换为json
}
返回对象框架的处理流程 ( 了解 ) :
- 框架会把返回User类型 , 调用框架中的
ArrayList<HttpMessageConverter>中每个类的canWrite()方法 , 检查哪个HttpMessageConverter接口的实现类能够处理User类型的数据 , 最终找到MappingJacksong2HttpMessageConverter - 框架会调用实现类的
write()方法 ,MappingJacksong2HttpMessageConverter的write()方法 , 将User对象转为Json , 调用Jackson的ObjcetMapper实现转为json - 框架会调用
@ResponseBody把2的结果数据输出到浏览器 , ajax请求处理完成
11.5. 返回ResponseEntity
ResponseEntity使用泛型 , 说明任何类型都可以包含在ResponseEntity中返回
ResponseEntity默认直接就是json , 但是需要Jackson支持
注意 : 如果直接返回对象 , 想要返回json格式字符串 , 有2种选择
- 方法直接返回对象 , 此时需要注解
@ResponseBody, 需要添加jackson支持 - 方法返回ResponseEntity , 把需要返回的对象放到ResponseEntity中 , 此时不需要添加
@ResponseBody, 需要添加jackson支持
/*
* 返回ResponseEntity
* ResponseEntity使用泛型 , 说明任何类型都可以包含在ResponseEntity中返回
* 默认直接就是json格式 , 但是需要jackson支持
*
* 注意 : 如果直接返回对象 , 想要返回json格式字符串的话 , 有2种选择
* 1. 直接返回对象 , 需要@ResponseBody并添加jackson支持
* 2. 返回ResponseEntity , 把要返回的对象放到ResponseEntity中 , 不需要添加@ResponseBody , 需要添加jackson
* */
@GetMapping("returnEntity")
public ResponseEntity returnEntity() {
User user = new User();
user.setAge(11);
user.setName("aa");
return ResponseEntity.ok(user);
}
12. Rest风格
Rest风格规范 :
- 对于请求方式规范 , 不同的操作使用不同的请求方式
- get : 一般是获取数据
- post : 登陆和添加数据
- put : 一般是修改
- delete : 删除操作
- 对于不同的状态码的规范
- 状态码
- 相应信息
- 结果集
- 参数接收/请求路径
- 将参数拼接在了URL中 , 参数成为了请求路径的一部分
12.1. Rest风格参数获取
获取路径中的参数值使用@PathVariable , 如果名字一样直接获取即可 , 没有先后顺序 , 如果参数名和路径名不一致 , 需要通过@PathVariable的参数指定获取路径上的值 , 也不分先后顺序
@GetMapping("/restPath/{name}/{age}")
public void restPath(@PathVariable String name, @PathVariable int age) {
System.out.println(name);
System.out.println(age);
}
@GetMapping("/restPath/{name}/{age}")
public void restPath(@PathVariable("name") String username, @PathVariable("age") int userage) {
System.out.println(name);
System.out.println(age);
}
13. 文件上传
目前市场流行的文件上传分为2中 :
- 提交表单(包含异步)
- Base64格式
13.1.java代码编写方式
- 提交表单上传
- Servlet3.0 之前原始的写法
- Servlet3.0 之后part的写法
- fileupload.jar + commons.io.jar的写法
- fileupload.jar + commons.io.jar继承SpringMVC的写法
- Base64上传
- 写法很单一
13.2. 提交变单上传的三要素
- post请求
- 有一个
input type=file标签 enctype=multipart/form-data
13.3. part方式上传文件
这种方式 不需要导入任何包 , 也不要配置任何视图解析器
-
编写Controller
@PostMapping("uploadController") @ResponseBody public String upload(HttpServletRequest request) throws IOException, ServletException { //从前端读取文件 Part pic = request.getPart("pic"); //找到写的位置 String realPath = request.getServletContext().getRealPath("/uploads/"); File file = new File(realPath); //如果位置不存在则创建 if (!file.exists()) { file.mkdirs(); } //文件写入 pic.write(realPath + pic.getSubmittedFileName()); return "http://localhonst:8080/uploads/" + pic.getSubmittedFileName(); } } -
编写前端页页面
<form action="/upLoadServlet" enctype="multipart/form-data" method="post"> <input type="file" name="pic"> <input type="submit" value="上传"> </form> -
添加配置信息
<!-- 开启springmvc part方式文件上传--> <multipart-config/> <!-- 也可以写的更详细些 : --> <!--可以直接复制 单位byte--> <multipart-config> <max-file-size>20848820</max-file-size> <max-request-size>418018841</max-request-size> <file-size-threshold>1048576</file-size-threshold> </multipart-config>
13.4. 上传文件重名处理
- 为了防止图片重名 , 可以对上传的文件进行重命名
- 重命名的方式 :
- UUID
- 时间戳
- 毫秒值
- 纳秒值
- 建立多个文件夹
13.4.1. 使用UUID重命名文件
- 先获取文件后缀名
- 生成一个uuid
- 拼接uuid和后缀名 , 获取新文件名
- 文件写入
代码 :
@Controller
public class SpringUpLoadController {
@PostMapping("uploadController")
@ResponseBody
public String upload(HttpServletRequest request) throws IOException, ServletException {
//从前端读取文件
Part pic = request.getPart("pic");
//找到写的位置
String realPath = request.getServletContext().getRealPath("/uploads/");
File file = new File(realPath);
//如果位置不存在则创建
if (!file.exists()) {
file.mkdirs();
}
//声明一个uuid
String uuid = UUID.randomUUID().toString();
//获得文件后缀名
String ext = StringUtils.getFilenameExtension(pic.getSubmittedFileName());
//拼接uuid和文件后缀名 , 生成新的文件名
String fileName = uuid + "." + ext;
//文件写入
pic.write(realPath + fileName);
return "http://localhonst:8080/uploads/" + pic.getSubmittedFileName();
}
13.4.2. 使用毫秒值 , 纳秒值重命名文件
@Controller
public class SpringUpLoadController {
@PostMapping("uploadController")
@ResponseBody
public String upload(HttpServletRequest request) throws IOException, ServletException {
//从前端读取文件
Part pic = request.getPart("pic");
//找到写的位置
String realPath = request.getServletContext().getRealPath("/uploads/");
File file = new File(realPath);
//如果位置不存在则创建
if (!file.exists()) {
file.mkdirs();
}
//获取毫秒值
long id = System.currentTimeMillis();
//获取纳秒值
//long id = System.nanoTime();
//获得文件后缀名
String ext = StringUtils.getFilenameExtension(pic.getSubmittedFileName());
//文件写入
pic.write(realPath );
return "http://localhonst:8080/uploads/" + pic.getSubmittedFileName();
}
13.5. 目录打散
目录打散 : 创建多个目录分别存储文件
@PostMapping("uploadController2")
@ResponseBody
public String upload2(HttpServletRequest request) throws IOException, ServletException {
//从前端读取文件
Part pic = request.getPart("pic");
//找到写的位置
String realPath = request.getServletContext().getRealPath("/");
//声明一个uuid
String uuid = UUID.randomUUID().toString();
//获得文件后缀名
String ext = StringUtils.getFilenameExtension(pic.getSubmittedFileName());
//获取uuid的哈希值
int hashCode = uuid.hashCode();
//根据哈希值获取第一个文件夹的名字
int dir1 = hashCode & 0xf; //0--15
//获取第二个文件夹的名字
int dir2 = dir1 >> 4; //0-15
//设置文件最终保存地址
String finalFilePath = realPath + "/" + dir1 + "/" + dir2 + "/";
//获取文件夹 , 文件夹不存在则创建
File file = new File(finalFilePath);
if (!file.exists())
file.mkdirs();
//拼接uuid和文件后缀名 , 生成新的文件名
String fileName = uuid + "." + ext;
//文件写入
pic.write(finalFilePath + fileName);
return "http://localhonst:8080/uploads/" + dir1 + "/" + dir2 + "/" + fileName;
}
13.6. 多文件上传
controller :
@PostMapping("uploadController3")
@ResponseBody
public String upload3(HttpServletRequest request) throws IOException, ServletException {
//从前端读取文件
Collection<Part> parts = request.getParts();
parts.forEach(part -> {
//找到写的位置
String realPath = request.getServletContext().getRealPath("/");
//声明一个uuid
String uuid = UUID.randomUUID().toString();
//获得文件后缀名
String ext = StringUtils.getFilenameExtension(part.getSubmittedFileName());
//获取uuid的哈希值
int hashCode = uuid.hashCode();
//根据哈希值获取第一个文件夹的名字
int dir1 = hashCode & 0xf; //0--15
//获取第二个文件夹的名字
int dir2 = dir1 >> 4; //0-15
try {
part.write(realPath + uuid + "." + ext);
} catch (IOException e) {
e.printStackTrace();
}
});
return null;
}
jsp :
<form action="/upLoadServlet" enctype="multipart/form-data" method="post">
<input type="file" name="pic" multiple>
<input type="submit" value="上传">
</form>
13.7. springMVC方式上传文件
这种方式相较之前比较麻烦 , 需要导包和配置多媒体视图解析器
-
导入jar包
导入
common-fileupload.jarcommons-io包 -
编写Controller
在方法的形式参数位置 , 定义一个MultipartFile 参数名称和前端传递的input标签的name属性值一致
@Controller public class SpringUpLoadController { @PostMapping("uploadByIO") public String uploadByIO(HttpServletRequest request, MultipartFile pic) throws IOException { String realPath = request.getServletContext().getRealPath("/file/"); File file = new File(realPath); if (!file.exists()) file.mkdirs(); String uuid = UUID.randomUUID().toString(); String filenameExtension = StringUtils.getFilenameExtension(pic.getOriginalFilename()); String filePath = realPath + uuid + "." + filenameExtension; pic.transferTo(new File(filePath)); return "success"; } -
编写前端
<form action="/uploadByIO" enctype="multipart/form-data" method="post"> <input type="file" name="pic" > <input type="submit" value="上传"> </form> -
配置多媒体视图解析器
<!-- 配置多媒体试图解析器--> <bean id="commonsMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
14. 文件下载
14.1. 文件下载方式一 ( 不建议使用 )
直接将连接传给前端 ( 不建议使用 ) , 如果浏览器可以解析则直接浏览 , 不能解析则直接下载
弊端 : 不能计算下载次数 , 对于那些不懂电脑的人 , 不直到使用Ctrl+s或右键另存为
演示 :
添加需要下载的文件 :
在web下创建文件夹 , 在文件夹下存放需要下载的文件
直接使用资源路径去下载 :
<h2>下载</h2>
<a href="/download/a.txt">下载a</a>
<a href="/download/b.txt">下载b</a>
<a href="/download/c.txt">下载c</a>
修改springmvc配置文件 :
<!--开启静态资源-->
<mvc:default-servlet-handler/>
14.2.文件下载方式二
直接下载文件 , 不需要浏览器解析
好处 : 任何文件下载都可以通过弹窗保存 , 可以统计下载次数
文件下载过程中 , java程序的功能 :
- 从文件存储位置读取文件
- 把读取的文件写到浏览器
controller :
@GetMapping("download")
public ResponseEntity<byte[]> download(HttpServletRequest request) throws IOException {
//从文件夹读取文件
String filePath = request.getServletContext().getRealPath("/download/a.txt");
FileInputStream fileInputStream = new FileInputStream(filePath);
byte[] bytes = new byte[fileInputStream.available()];
fileInputStream.read(bytes);
//写文件
//调起浏览器下载功能 (通过响应头告知浏览器这个请求是一个下载)
//1. 设置请求头
HttpHeaders httpHeaders = new HttpHeaders();
//httpHeaders第一个可以随意设置 , 不管设置什么最后对会变为"attachment"
//第二个参数设置文件名 , 有时候会出现乱码 , 就需要设置URLEncoder.encode("文件名","编码格式") 解决乱码
httpHeaders.setContentDispositionFormData("attachment", URLEncoder.encode("a.txt","utf-8"));
//通过ResponseEntity设置响应头并返回数据 第一参数为返回数据 , 第二个参数为响应头 , 第三个参数为响应状态码
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes, httpHeaders, HttpStatus.OK);
return responseEntity;
}
jsp :
<h2>下载</h2>
<a href="/download">下载a</a>
15. SpringMVC类型转换问题
15.1. Date类型
15.1.1. 型以key=value形式传参
-
第一种方式 :
@DateTimeFormat(pattern = "yyyy-MM-dd"): 可用于方法形参的位置 , 目的是解决key=value参数形式Date日期格式转换参数是
Date类型则在参数前添加注解@DateTimeFormat(pattern = "yyyy-MM-dd")@GetMapping("dataFormat") public String dataFormat(String name, @DateTimeFormat(pattern = "yyyy-MM-dd") Date date) { System.out.println(date); return "success"; }参数是对象 :
在对象对应的参数上写注解
@DateTimeFormat(pattern = "yyyy-MM-dd")public class User { private String name; @DateTimeFormat(pattern = "yyyy-MM-dd") private Date date; //get set tosring } -
第二种方式 :
自定义类型转换器convert :
- 自定义一个类实现Convert接口 , 重写方法
- 配置自定义类
自定义Convert :
import org.springframework.core.convert.converter.Converter; //converter的2个泛型表示从a泛型到b泛型 public class TimeConvert implements Converter<String, Date> { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); @Override //s是前端传来日期格式的字符串 public Date convert(String s) { Date date = null; try { date = simpleDateFormat.parse(s); } catch (ParseException e) { e.printStackTrace(); } return date; } }配置自定义类 :
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <set> <bean class="自定义的convert地址"/> </set> </property> </bean> <!-- 注解驱动 conversion-service="conversionService"设置全局--> <mvc:annotation-driven conversion-service="conversionService"/>
15.1.2. 以json形式传参
如果传递的是一个json ,
@DateTimeFormat处理方式失效处理json默认使用jackson , 只需要导入jar包即可 , 默认的接收格式为yyyy-MM-dd , 用此格式不需要添加注解 , 使用其他格式需要添加注解
自定义格式一 :
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date date;
自定义格式二 :
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper" ref="objectMapper"/>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
p:indentOutput="true"
p:simpleDateFormat="自定义格式"
/>
15.2. LocalDateTime类型
15.2.1. 以key=vlaue形式传参
-
第一种方式 :
在对应的参数上添加注释
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")可以自动转换格式public class User { private String name; //必须加上HH:mm:ss否则会报错 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime localDateTime;//get set tostrign
- 第二种方式 :
自定义类型转换器convert
convert :
```java
public class LocalDateTimeConvert implements Converter<String, LocalDateTime> {
@Override
public LocalDateTime convert(String s) {
LocalDateTime parse = LocalDateTime.parse(s, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
return parse;
}
}
配置文件 :
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="com.test.convert.LocalDateTimeConvert"/>
</set>
</property>
</bean>
<mvc:annotation-driven conversion-service="conversionService"/>
15.2.2. 以json形式传参
默认情况下 , 使用JsonFormate注解 , 封装报错 , 返回的内容指定的格式也失效
注意 : 如果使用java8的日期格式 , 需要添加一个jackson的支持包 jsr310
导入jsr310之后 , 不需要做任何配置 , 只写使用@JsonFormate注解 , 接收和响应都会按照指定的格式生效
public class User {
private String name;
//必须加上HH:mm:ss否则会报错
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime localDateTime;
//get set tostrign
16. 静态资源拦截
SpringMVC默认对静态资源有拦截 ( html css js 图片 字体 多媒体 图标…)
在
<url-pattern></url-pattern>标签中设置值来实现拦截
<url-pattern> </url-pattern>取值 :
/: 拦截所有静态资源 , jsp除外/*: 拦截所有静态资源 , 包括jsp
16.1. 静态资源拦截的解决方式
16.1.1. 使请求不进入SpringMVC ( 不用 )
SpingMVC默认对静态资源有拦截 , 那么只要不让静态资源进入SpringMVC即可越过拦截
由于这种情况jsp页面没有经过SpringMVC , 所以jsp页面不被渲染
因为这种方式不满足Rest风格API所以不用
实现方式 : 让所有有规律的请求进入SpingMVC
举例 :
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
16.1.2.配置静态资源映射
配置静态资源映射 , 即告诉SpringMVC哪些请求需要到什么地方找
实现方式 : 在SpringMVC配置文件中配置静态资源映射
注意 : 如果使用4.2.5版本的springmvc则必须在请求匹配前加
/, 资源位置后加/
举例 :
<!-- mapping : 请求匹配 , 支持通配符-->
<!-- location : 资源的位置-->
<!-- 通配符* : 一级路径-->
<!-- 通配符** : 多级路径-->
<mvc:resources mapping="/test1.html" location="/"/>
<mvc:resources mapping="/css/**" location="/css/"/>
16.1.3. 放行所有静态资源
一个标签解决所有问题 , 建议使用
开发时如果使用就用这种方式 , 或者根本不使用
举例 :
<mvc:default-servlet-handler/>
17. 异常处理
编写程序的过程中 :
dao操作数据库时可能出现异常 , 此时异常抛出 ,
service处理dao层的异常 , service层本身也可能出现异常 , service把所有的异常抛出
contorller需要处理dao层异常 , service层异常 , 本身也可能有异常contorller是用SpringMVC编写的 , 所以异常处理是SpringMVC的异常处理
SpringMVC异常处理有2中方式 :
- 抛出 : 交由JVM处理 , 也就是控制台报错 , 出现报错页面
- 处理异常 :
- 返回一个固定页面 ( 服务器繁忙 )
- 返回页面 , 携带数据 ( 需要使用JSP或HTML+其他模版引擎)
- 定制json格式字符串 ( 需要掌握 )
- 提示 : 只要页面不是jsp或者html+模版引擎 , 则第一二中方式无法使用
- 如果是前后端分离项目或前后端不分离使用ajax异步请求的 , 此时需要使用自定义异常返回json状态码 , 并且为了满足Rest封装 , 错误信息应该包含 : status message
17.1. 处理异常 : 返回固定页面
当出现异常时 , 直接返回一个固定的页面 , 如果需要展示错误信息比较复杂
web.xml配置文件 :
<error-page>
<location>/error.html</location>
</error-page>
17.2. 处理异常 : 返回页面并携带数据
携带数据到达视图 , 渲染展示 :
- 好处 : 可以显示不同的错误信息
- 坏处 : 页面目前来讲只是能使用jsp页面 , 并且需要配置视图解析器
springMVC :
<!-- 视图解析器-->
<bean id="resourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 配置前置-->
<property name="prefix" value="/WEB-INF/pages/"/>
<!-- 配置后缀-->
<property name="suffix" value=".jsp"/>
</bean>
handler :
//记得配置扫描器
@Component
public class ExceptionHandler implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
String message = e.getMessage();
// httpServletRequest.setAttribute("msg", message);
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", message);
modelAndView.setViewName("error");
return modelAndView;
}
}
17.3. 处理异常 : 自定义异常
处理方法 :
- 添加
controllerAdvice给所有controller添加功能- 只要controller抛出MyException异常 , 则进入
ControllerAdvice自定义应用场景 : 明白controller要抛出的异常 , 但是又无法处理 ( 编译器异常 )
问题 : 运行过程中出现的未被提前处理的异常( 空指针 索引操作 类型转换错误等 )怎么处理?
自定义枚举类型保存返回异常状态和信息 :
public enum MyStatus {
USERNAME_ERROR(4000, "用户名错误"),
PASSWORD_ERROR(4001,"密码错误")
;
private int status;
private String message;
MyStatus(int status, String message) {
this.status = status;
this.message = message;
}
//get set tostring
}
自定义异常抛出信息为枚举类 :
public class MyException extends RuntimeException {
private MyStatus myStatus;
public MyException(MyStatus myStatus) {
this.myStatus = myStatus;
}
public MyStatus getMyStatus() {
return myStatus;
}
public void setMyStatus(MyStatus myStatus) {
this.myStatus = myStatus;
}
}
编写ControllerAdvice , 给所有controller添加功能
@ControllerAdvice//给controller层添加功能 , 所有controller出现的问题都会进入这里
public class TestHandler {
//@ResponseBody
//使用responseEntity返回json数据 , 或者使用@ResponseBody注解 , 记得导如jackson包 , 否则无法使用
//controller层如果出现异常 并且异常是MyException异常 , 此时会执行这个方法 , 并用形参接收异常
@ExceptionHandler(MyException.class)
public ResponseEntity<Map<String, Object>> handlerMyException(MyException e) {
MyStatus myStatus = e.getMyStatus();
Map<String, Object> map = new HashMap<>();
map.put("status", myStatus.getStatus());
map.put("message", myStatus.getMessage());
return ResponseEntity.ok(map);
}
}
测试 :
@ResponseBody
@GetMapping("exception1")
public String exception1() {
System.out.println("test");
throw new MyException(MyStatus.USERNAME_ERROR);
}
17.4. 异常处理 : 其他异常
程序运行过程中 , 根本不知道那里出现了异常 , 也不知道出现了什么异常 , 必须要找到这个异常的父类 : Throwable
当与自定义异常同时存在时 , 如果满足自定义异常则使用自定义异常的
ControllerAdvice, 否则使用这个ControllerAdvice
自定义枚举类保存返回异常信息 :
public enum MyStatus {
USERNAME_ERROR(4000, "用户名错误"),
PASSWORD_ERROR(4001,"密码错误"),
UNKNOW_ERROR(4004,"未知错误")
;
private int status;
private String message;
MyStatus(int status, String message) {
this.status = status;
this.message = message;
}
//get set tostring
}
编写ControllerAdvice , 给所有controller添加功能 :
@ControllerAdvice//给controller层添加功能 , 所有controller出现的问题都会进入这里
public class TestHandler {
//@ResponseBody
//使用responseEntity返回json数据 , 或者使用@ResponseBody注解 , 记得导如jackson包 , 否则无法使用
//controller层如果出现异常 并且异常是Throwable的子类 , 此时会执行这个方法 , 并用形参接收异常
@ExceptionHandler(Throwable.class)
public ResponseEntity<Map<String, Object>> handlerMyException(Throwable t) {
MyStatus myStatus = Mystatus.UNKNOW_ERROR();
Map<String, Object> map = new HashMap<>();
if(!StringUtils.isEmpty(t.getMessage)){
map.put("message", t.getMessage());
}else{
map.put("message",myStatus.getStatus());
}
map.put("status", myStatus.getMessage());
return ResponseEntity.ok(map);
}
}
测试 :
@ResponseBody
@GetMapping("exception2")
public String exception2() {
int i = 1 / 0;
return "success";
}
@ResponseBody
@GetMapping("exception3")
public String exception3() {
List list = null;
list.add("aa");
return "success";
}
@ResponseBody
@GetMapping("exception4")
public String exception4() {
List list = null;
list.get(1);
return "success";
}
@ResponseBody
@GetMapping("exception5")
public String exception5() {
String str = "a";
Integer integer = Integer.valueOf(str);
return "success";
}
18. SpringMVC的拦截器
拦截器Filter是Servlet的三大技术之一
SpringMVC也提供了拦截器功能interceptor
二者功能都是拦截请求 , 过滤请求的
SpringMVC拦截器的创建流程 :
- 定义一个类实现拦截器接口
- 重写方法 , 是否继续执行要看返回值是true还是false
- 在SpringMVC配置文件中配置拦截器
拦截器代码 :
public class TestInterceptor implements HandlerInterceptor {
//执行目标方法之前执行
//返回true表示请求会向下执行 , 返回false表示请求不会向下执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//登陆判断
//从session中获取user对象 , 如果对象为空则页面重定向 , 否则
Object user = request.getSession().getAttribute("user");
if (user == null) {
//重定向
return false;
} else
return true;
}
//方法执行后 , 返回视图前执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
//请求完成后执行 , 视图返回后执行 ( 仅限于试图解析器 )
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置文件 :
<!-- 配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--需要拦截取得地址 /**表示所有地址 /*表示所有的一级请求-->
<mvc:mapping path="/**"/>
<!--设置不拦截的请求-->
<mvc:exclude-mapping path="/login"/>
<bean class="com.test.interceptor.TestInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

浙公网安备 33010602011771号