# 1.SpringMVC简介
Spring MVC是一个基于Java的Web应用程序开发框架,它属于Spring Framework的一部分。Spring MVC通过将应用程序分解为模型(Model)、视图(View)和控制器(Controller)三个核心组件,提供了一种基于MVC(模型-视图-控制器)设计模式的开发方式。
在Spring MVC中,模型(Model)代表应用程序的数据模型,视图(View)负责展示模型数据给用户,而控制器(Controller)处理用户请求,并根据请求的类型选择合适的视图呈现给用户。这种分层架构使得代码的组织更加清晰,并且易于扩展和维护。
Spring MVC提供了许多特性和组件,使得开发Web应用程序更加便捷。其中包括:
注解驱动开发:Spring MVC支持使用注解来配置控制器和请求映射,简化了配置和编码的过程。
强大的数据绑定和验证:Spring MVC提供了强大的数据绑定和验证功能,能够自动将请求参数绑定到模型对象上,并进行验证和转换。
灵活的视图解析:Spring MVC支持多种视图解析技术,如JSP、Thymeleaf、Freemarker等,可以根据需要选择合适的视图技术。
拦截器:Spring MVC的拦截器可以在请求处理的各个阶段进行拦截和处理,实现例如身份验证、日志记录等功能。
RESTful支持:Spring MVC对于构建RESTful风格的Web服务提供了良好的支持,可以方便地处理RESTful风格的URL和资源映射。
总结来说,Spring MVC是一个成熟、灵活且功能强大的Java Web开发框架,它可以帮助开发者快速构建可扩展的Web应用程序,并且具有良好的模块化和可测试性。
# 2.SpringMVC快速入门
需求: 客户端发起请求,服务器接收请求,执行逻辑并进行视图跳转
开发步骤:
导入SpringMVC相关坐标
<!--Spring-MVC--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.0.5.RELEASE</version> </dependency>
当我们导入了坐标后我们就有了SpringMVC核心控制器DispatchcerServlet
在web.xml中配置SpringMVC核心控制器DispathcerServlet
<!-- 因为这个Servlet不是我们编写的,我们无法使用注解配置,所以手动配置这个Servlet 配置dispatcherServlet (dispatcher是调度员的意思,说明这个Servlet负责所有Servlet共有的操作,而我们只需要编写特殊的操作) --> <servlet> <!-- Servlet的名字--> <servlet-name>DispatcherServlet</servlet-name> <!-- 指定DispatcherServlet的字节码文件所在位置--> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 告诉DispatcherServlet,需要加载的Spring-MVC的配置文件的位置 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring-mvc.xml</param-value> </init-param> <!-- 当值为0或者大于0时,表示容器在应用启动时就加载这个servlet; 当是一个负数时或者没有指定时,则指示容器在该servlet被选择时才加载。 正数的值越小,启动该servlet的优先级越高。 --> <load-on-startup>1</load-on-startup> </servlet> <!-- 配置Servlet的映射地址,/ 是缺省的标识, 因此找不到对应的请求就走这个, 而/* 表示的是所有的请求 --> <servlet-mapping> <servlet-name>DispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
创建Controller类和视图页面
package com.snake.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; /** * 在web层中,给这个Controller写明对应的注解, */ @Controller // 添加为Bean public class UserController { /** * * 使用@RequestMapping注解 来表明映射的方法 * @return */ @RequestMapping("/quick") public String save() { System.out.println("嗯哼~"); //返回的网页,类似于重定向和请求转发? return "success.jsp"; } }
使用注解配置Controller类中业务方法的映射地址
@RequestMapping("/quick") public String save() { System.out.println("嗯哼~"); //返回的网页,类似于重定向和请求转发? return "success.jsp"; }
配置SpringMVC核心文件Spring-mvc.xml 名字任意,注意别忘了,配置Servlet时,配置这个初始化文件的位置
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
配置包扫描,也可以在applicationContext.xml配置文件中配置,但是为了拆分,我们应该新建一个文件来写
-->
<!--
可以扩大扫描范围,并且自定义扫描的注解和不扫描的注解,一般都是指定不扫描什么..
exclude-filter: 不扫描的
include-filter: 扫描的
com.xxx.xxxx为不想被扫描的包
<context:component-scan base-package="com.snake">
<context:exclude-filter type="annotation" expression="com.xxx.xxxx"/>
</context:component-scan>
-->
<context:component-scan base-package="com.snake.controller"></context:component-scan>
</beans>
- 客户端发起请求测试
注意,如果浏览器返回500,可能是jsp依赖,没有把范围指定为provide
# 3.SpringMVC流程图
# 4.SpringMVC框架内部执行流程图
- 用户发送请求至前端控制器DispatcherServlet
- DispatcherServlet收到请求调用HandlerMapping处理器映射器
- 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果 有则生成)一并返回给DispatcherServlet
- DispatcherServlet调用HandlerAdapter处理器适配器
- HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
- Controller执行完成返回ModelAndView
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器
- ViewReslover解析后返回具体View
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)
- DispatcherServlet响应用户
# 5.Spring注解解析
@RequestMapping
(1)作用:用于建立请求URL和处理请求方法之间的对应关系
(2)位置:
类上,请求URL的第一级访问路径,此处不写的话,就相当于应用的根路径
//当我们没有在返回的网页前加'/',它就默认这个网站的资源在我们这个方法的访问资源的位置下,加了就是webapp的根目录下 return "success.jsp";
没加上'/',它会去@questMapping映射的位置找
如果我们加上了'/',资源就能正确的去找webapp下的success.jsp'
方法上,请求Url的第二级访问路径,与类上的使用@RequestMapping标注的一级目录组成访问虚拟路径
(3)属性:
value:用于指定请求的url.它和path属性的作用是一样
method:用于指定请求的方式, 例如get,post等,多个请求方式,用一个数组接收
@RequestMapping(value="/quick",method = {RequestMethod.GET,RequestMethod.POST},params = {"username"})
params:用于指定限制请求参数的条件.他支持简单的表达式,要求请求参数key和value必须和配置的一模一样
例如
params = {"accountName"}, 表示请求的参数必须有accountName
params = {"money!100"},表示请求参数中money不能是100
# 6.手动配置SpringMVC内部组件的部分属性
视图解析器
SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址 org/springframework/web/servlet/DispatcherServlet.properties,
该文件中配置了默认的视图解析器,如下: org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.I nternalResourceViewResolver
翻看该解析器源码,可以看到该解析器的默认设置,如下:
REDIRECT_URL_PREFIX = "redirect:" --重定向前缀
FORWARD_URL_PREFIX = "forward:" --转发前缀(默认值)
我们可以在return的字符串,里面添加转发或者重定向的前缀,(转发的前缀是默认的,我们不写这个前缀就是转发)
这样就会给浏览器发送对应的响应
prefix = ""; --视图名称前缀
suffix = ""; --视图名称后缀
我们可以设置Spring容器中的视图解析器 InternalResourceViewResolver,他有两个属性 prefix = ""; --视图名称前缀 和 suffix = ""; --视图名称后缀,
这两个属性有set方法,所以我们可以通过Spring容器进行设置
通过设置这两个前后缀的内容,我们可以,省去一些路径的书写,
<bean id="ViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
经过上面的配置,
return "success"; 等同于 return "/jsp/success.jsp";
注意:重定向时,并不会拼接前缀和后缀
# 7.SpringMVC的数据响应
# 1.页面跳转
return "xxx.html" 默认是请求转发
@RequestMapping(value="/quick") public String save() { System.out.println("嗯哼~"); //当我们没有在返回的网页前加'/',它就默认这个网站的资源在我们这个方法的访问资源的位置下,加了就是webapp的根目录下 return "redirect:/jsp/success.jsp"; }
return ModelAndView 对象,
这个对象可以封装数据和视图 ModelAndView方式封装的数据 在request域中保存
/** * 通过返回 ModelAndView对象来跳转页面 Model 模型用来封装数据 View 视图用来展示数据 * @return */ @RequestMapping(value="/quick2") public ModelAndView save2() { ModelAndView modelAndView = new ModelAndView(); modelAndView.setViewName("success"); //封装一个视图 /jsp/success.jsp modelAndView.addObject("username","阿尔托莉雅"); //保存的数据在request域中,我们在jsp页面中可以通过el表达式来获取 //返回ModelAndView return modelAndView; }
# 2.返回数据
# (1).返回普通格式的数据
/**
* 返回数据
* 方式1: 通过Spring注入获取HttpServletResponse来获取输出流,输出一个字符串.返回值设置成void
* 中文乱码问题:
* 方式1.将tomcat升级至8之后的版本
* 方式2.设置编码:
* 告诉浏览器用UTF-8编码来解码
* response.setContentType("text/html;charset=utf-8");
* 设置输出流的编码为UTF-8
* response.setCharacterEncoding("UTF-8");
*/
@RequestMapping(value="/quick6")
public void save6(HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");
response.setCharacterEncoding("UTF-8");
response.getWriter().write("你就是我的星球");
}
/**
* 返回数据
* 方式2: 通过注解 @ResponseBody
* 告诉Spring容器我们返回的是输出到网页的字符串,而不是一个跳转的网页
* 中文乱码问题:
* 方式1.将tomcat升级至8之后的版本,未测试不知道是否有效...
* 方式2. 因为是通过注解来返回的字符串,而不是流,所以我们直接获取流来设置编码不起作用??
* 我们在注解中设置 produces属性
* @RequestMapping (value="/quick7",produces = {"text/html;charset=utf-8"})
*/
@RequestMapping(value="/quick7",produces = {"text/html;charset=utf-8"})
@ResponseBody
public String save7() throws IOException {
return "啊哈哈~~诶嘿嘿~~";
}
# (2.)返回JSON类型数据
1. 使用第三方的jar包的json工具类,手动的转换数据,再返回
/**
* 返回JSON类型数据,
* 可以手动拼接,但是太麻烦,
* 我们通过导入fastjson坐标,使用里面的工具类来转换对象为JSon格式的数据
* 通过注解 @ResponseBody
* 告诉Spring容器我们返回的是输出到网页的字符串,而不是一个跳转的网页
*/
@RequestMapping(value="/quick8",produces = {"text/html;charset=utf-8"})
@ResponseBody
public String save8() throws IOException {
//获取User对象
User user = new User("阿尔托莉雅",18);
//将User对象转换成json格式的数据
String jsonString = JSON.toJSONString(user);
return jsonString;
}
2. 通过配置HandlerAdapter处理器适配器,来帮我们自动转换成JSON数据
- Spring-MVC配置文件
<!--
配置处理适配器,帮助我们自动转换JSON数据
-->
<bean id="HandlerAdapter" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
</list>
</property>
</bean>
- java代码
/**
* 通过配置处理适配器,让它帮我们自动帮我们处理JSON数据
* 注意! 我没有使用老师使用的json转换工具Jackson,我用的是fastjson
* 所以配置时,传入的Bean对象用的是fastJSON的FastJsonHttpHttpMessageConverter
* fastJson的
* Bean: com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter
*
* 如果没有导入老师使用的json转换工具Jackson的坐标,直接用老师的那个转换Bean对象会抛出异常!!
* 老师的Bean:
* org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
*/
@RequestMapping(value="/quick9",produces = {"text/json;charset=utf-8"})
@ResponseBody //不能省略,因为传的还是字符串数据
public User save9() throws IOException {
//获取User对象
User user = new User("阿尔托莉雅",18);
//因为我们配置了处理适配器,直接返回对象,就可以转换成JSON数据
return user;
}
# 使用MVC命名空间的一个功能,帮我们自动配置处理器适配器,(它也会配置处理映射器),而且它的里面就帮我们配置好了JSON数据的自动转换
<!--
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"
使用命名空间的mvc,自动帮我们配置处理适配器和处理映射器
其中处理适配器,帮我们配置好了自动转换JSON数据,注意,它需要Jackson的工具jar包,fastjson的不行
-->
<mvc:annotation-driven/>
以后写SpringMVC的项目,直接就把这个命名空间写上
- 如果浏览器 的状态码为 406, 错误的意思是:说是指定的资源已经找到,但它的MIME类型和客户在Accpet头中所指定的不兼容,我们应该设置返回的类型为 application/json
# (3).数据在域中的保存
3.1Request对象中保存数据(不推荐)
3.2、Session域中保存数据
3.3、ServletContext域中保存数据
3.4、Map或Model或ModelMap形式保存数据在request域中
3.5、ModelAndView方式保存数据到request域中
3.6、@SessionAttributes保存数据到Session域中(不推荐使用)
3.7、@ModelAttribute注解
# 8.SpringMVC的接收请求参数
客户端请求参数的格式是: name=value&name=value...
服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:
- 基本类型参数
- POJO类型参数
- 数组类型参数
- 集合类型参数
# 8.1 获取基本类型参数
Controller中的业务方法的参数名要与请求参数的name一致,这样SpringMVC就会帮你自动映射匹配
/**
* 接收浏览器通过get方式发送的参数
* Controller中的业务方法的参数名要与请求参数的name一致,
* 这样SpringMVC就会帮你自动映射匹配
*/
@RequestMapping(value="/quick12",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save12(String username,int age) throws UnsupportedEncodingException {
//写明了@ResponseBody注解,并且返回值为void,说明我们不返回数据
/*
只要方法形参的名字与get方式,发过来的参数名字匹配就等自动映射匹配
如果,形参的类型为数字的类型,从url中获取的参数是String的,SpringMVC会自动将数字类型转换成对应的类型
但是如果是字母字符串,无法转换成数字的话就会抛出异常
*/
/*
解除中文乱码
*/
String encode = URLEncoder.encode(username, "ISO-8859-1");
String decode = URLDecoder.decode(encode, "UTF-8");
System.out.println(decode);
System.out.println(age);
}
# 8.2请求数据POJO类型参数,或者说参数要封装成对象
/**
* 接收的数据通过SpringMVC直接给我们封装成对象
* 如果请求参数的名称符合POJO类对象的set方法中的首字母大写就匹配的要求
* 就会将参数传递到set方法进行封装,所以要求封装的类写了set方法
*/
@RequestMapping(value="/quick13",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save13(User user) throws UnsupportedEncodingException {
//通过Spring帮我们封装参数的数据,变成一个对象
//我们应该在Set方法里面对数据进行处理,例如数据的验证,以及编码解码等等
System.out.println(user);
}
# 8.3请求携带的参数为数组类型数据
/**
* 获得数组类型数据
*/
@RequestMapping(value="/quick14",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save14(String[] strs) throws UnsupportedEncodingException {
/**
* 由于数组打印是地址,我们借助集合工具类帮我们打印
*/
System.out.println(Arrays.asList(strs));
//System.out.println(Arrays.toString(strs));
}
# 8.4 请求携带的参数为集合类型数据
如果前端发送数据的方式是GET,数据放在 请求url里面 表单放在from-data里面
使用时要做后端加上@RequestParam注解代表接收get方式的数据,它会从这两个地方拿数据
如果前端发送的数据方式POST,并且是JSON字符串,这样的话数据存在请求体中,我们注意Ajax的请求必须设置 contextType为 application/json,不过我用axios不设置好像也可以,axios默认发送json格式数据
我们必须使用@RequestBody注解,表明到请求体里面拿数据!!!!
request.getParameter() 能接受这两种方式的数据,以及post方式的数据,
但是无法接受json类型的数据!!!
具体参考https://www.cnblogs.com/blogtech/p/11172168.html
前端:,本例使用的axios,也可以使用原生的AJAx
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
//准备集合数据
var userList = new Array();
userList.push({
username:"saber",
age:18
});
userList.push({
username:"white",
age:18
});
//使用axios发送请求
axios({
method:"post",
url:"http://localhost:8080/Spring-demo-web/user/quick15",
data:userList
})
</script>
后端代码:
/**
* 获取集合类型数据
* 创建一个VO对象(Value Object),里面有个属性是一个集合,之后重写set方法
* 我们通过让Spring通过这个set方法,帮我们向集合中添加元素
*
* @RequestParam 该注解的代表接收get方式的数据
* @RequestBody 该注解代表接收放在请求体里面的数据,所以必须是post请求才行!!
* 前端发送集合数据的方式:
* 1.在表单中,手动写一个集合中的多个对象属性的输入框, 麻烦...
* <form action="${pageContext.request.contextPath}/user/quick15" method="post">
* <input type="text" name="userList[0].username"><br>
* <input type="text" name="userList[0].age"><br>
* <input type="text" name="userList[1].username"><br>
* <input type="text" name="userList[1].age"><br>
* <input type="submit" value="提交">
* </form>
* 2. 使用Ajax发送JSON类型数据, 接收的为集合类型对象,而不是封装后的VO对象
*/
@RequestMapping(value="/quick15",produces = {"application/json;charset=utf-8"})
@ResponseBody
public void save15(@RequestBody(required = false) List<User> userList) throws IOException {
//使用方式1:
// System.out.println(Arrays.asList(vo.getUserList()));
//使用方式2:
System.out.println(userList);
}
# 8.5.接收数据时的注解
@RequestParam注解
# 8.6 获得Restful风格的参数
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务 器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
GET:用于获取资源
POST:用于新建资源
PUT:用于更新资源
DELETE:用于删除资源
例如:
- /user/1 GET : 得到 id = 1 的 user
- /user/1 DELETE: 删除 id = 1 的 user
- /user/1 PUT: 更新 id = 1 的 user
- /user POST: 新增 user
前端也可以通过ajax的url发送占位符中的数据
后端获取数据时设置@RequestMapping注解的method = RequestMethod.POST等
演示以Get动词请求的方式
/**
* 演示Restful风格参数,浏览器发送url+数据
* @RequestMapping (value="/quick15/{name}") 注解中的{name}是占位符,将url中这个位置的数据传输到@PathVariable注解中 value="name"的方法形参
* @PathVariable (value="name",required = true) 这个注解后面跟着哪个参数,就会把这个占位符中的数据传给哪个形参, required = true代表这个占位符的数据,浏览器必须传过来
* 注解自动
* @throws IOException
*/
@RequestMapping(value="/quick16/{name}")
@ResponseBody
public void save16(@PathVariable(value="name",required = true) String name) throws IOException {
System.out.println(name);
}
# 9.开放静态资源访问
使用SpringMVC时为什么要开启静态资源??
原因:
默认的servlet的匹配路径是/,当用户访问资源时,没有资源的路径与其匹配就会访问到这个默认路径/ 也就是说会访问这个Servlet,同时这个Servlet会放行静态资源,当我们用SpringMVC的dispatchServlet配置了匹配路径/时就会,替代这个默认的Servlet,同时所有的静态资源的访问也都会被拒绝,需要我们手动去配置静态资源访问权限,或者说将静态的访问资源返回给默认的Servlet
具体情况:(60条消息) url-pattern配置为"/"和"/*"的区别_木子易子的博客-CSDN博客_ (opens new window)
如果我们的前端页面中用到了服务器中的一些js,css,图片等等资源,我们需要给这些文件开启访问权限, 如果发现没有开放资源但是依然返回正常的状态码,说明浏览器没有清空缓存,还保留着这个文件的数据
方式1: 在SpringMVC 中开放资源的访问权限
<!--
开放服务器中js文件夹下的访问权限
mapping="/js/**"代表 用户访问时输入的url
location="/js/" 代表文件存在的实际位置
-->
<mvc:resources mapping="/js/**" location="/js/"/>
方式2: 将SpringMVC的静态访问权限交给TomCat去处理
<!--转交给默认的Servlet既下面这个代码-->
<mvc:default-servlet-handler/>
TomCat好像是把所有的资源文件都看成 Servlet,我们交给它处理就能找到 资源
方式3: 使用Servlet配置映射 在web.xml中配置
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
参考:web项目如何通过servlet访问静态资源 (opens new window)
# 10.请求参数乱码问题,配置全局过滤器
当post请求时,数据会出现乱码(奇怪的是我并没有出现乱码,而老师的课程里却出现了乱码),我们可以设置一个过滤器来进行编码的过滤。
在Web.xml中配置一个全局的过滤器
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<!--使用SpringMVC提供的过滤器-->
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!--设置里面的encoding属性的值为对应的编码-->
<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>
# 11.自定义请求参数的类型转换器
这个转化器是将 前端发送过来的数据映射到参数上时进行的转化, 不会将返回的数据进行转化
SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自 定义转换器。
自定义类型转换器的开发步骤:
定义转换器类实现Converter接口
package com.snake.converter; import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * 自定义的日期转换器, * 让Spring接收字符串类型的日期时, * 自动帮我们转换我们想要的日期格式,而不是它转换的格式 * * 步骤: * 1.定义转换类,实现Converter接口, 不要导错包!!!!! org.springframework.core.convert.converter.Converter; * Converter<String, Date> 代表,将字符串转换成日期类型, 前端发过来参数都是字符串类型的 * 2.在SpringMVC配置文件中,将转换器加入到,转换器的集合中去 */ public class DateConverter implements Converter<String,Date> { public Date convert(String source) { //获得格式转换类 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { Date date = simpleDateFormat.parse(source); return date; } catch (ParseException e) { e.printStackTrace(); } return null; } }
在SpringMVC配置文件中声明转换器
<!-- 将自定义的日期转换器加入到ConversionServiceFactoryBean中的list集合去 --> <bean id="converterService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.snake.converter.DateConverter"/> </list> </property> </bean>
在SpringMVC配置文件中引用转换器
<!-- 使用命名空间的mvc,自动帮我们配置处理适配器和处理映射器 其中处理适配器,帮我们配置好了自动转换JSON数据,注意,它需要Jackson的工具jar包,fastjson的不行 conversion-service="converterService" 是告诉它,我们配置了我们自己的转换器 --> <mvc:annotation-driven conversion-service="converterService"/>
# 12.获得Servlet相关API
SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:
- HttpServletRequest
- HttpServletResponse
- HttpSession
@RequestMapping("/quick16")
@ResponseBody
public void quickMethod16(HttpServletRequest request,HttpServletResponse response,HttpSession session){
System.out.println(request);
System.out.println(response);
System.out.println(session);
}
# 13.获得请求头的数据
@RequestHeader
使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name) @RequestHeader注解的属性如下:
value:请求头的名称
required:是否必须携带此请求头
/**
* 获取浏览器请求头的数据
* 使用@RequestHeader注解
* @RequestHeader ("User-Agent") 括号里面的字符串对象请求头里面的键值对
*/
@RequestMapping(value="/quick18")
@ResponseBody
public void save18(@RequestHeader("User-Agent") String user_agent) throws IOException {
System.out.println(user_agent);
}
2.精确的获取请求头中Cookie里面的某个键值对
使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:
value:指定cookie的名称
required:是否必须携带此cookie
/** * 同样是获取请求头里面的数据 * 但是里面有一个是Cookie有一些特殊,因为Cookie里面也可以有键值对, * 如果通过直接使用@RequestHeader的方式获取Cookie的数据,就会获得一行 包含着许多键值对的数据 * 我们可以通过@CookieValue注解精确的获得 Cookie中的字符串的某个键对应的数据 * * 我们以获取Cookie中的JSESSIONID(Session与浏览器会话时用到的唯一标识id)为例 */ @RequestMapping(value="/quick19") @ResponseBody public void save19(@CookieValue("JSESSIONID") String jsessionid) throws IOException { System.out.println(jsessionid); }
# 14.文件上传
前端:
文件上传客户端需要的三要素
表单项的input标签的type="file"
表单的提交方式是POST
表单的enctype属性是多部分表单形式,及enctype="multipart/form-data",
默认的表单是enctype="application/x-www-form-urlencoded",意思就是键值对的形式
# 1.单文件上传
步骤:
导入fileupload和io的坐标, commons-fileupload包里面 好像已经依赖了 commons-io包 ,所以不需要引入commons-io了
<!-- 文件上传需要的依赖 fileupload和io --> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.3</version> </dependency>
在SpringMVC中配置文件上传解析器
<!-- 配置文件上传解析器 id:multipartResolver id不能变,应该是要靠这个id来加载文件上传解析器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- 上传文件总大小--> <property name="maxUploadSize" value="5242800"/> <!-- 上传单个文件的大小--> <property name="maxUploadSizePerFile" value="5242800"/> <!-- 上传文件的编码类型--> <property name="defaultEncoding" value="UTF-8"/> </bean>
编写文件上传代码
前端代码:
<form action="${pageContext.request.contextPath}/user/quick20" method="post" enctype="multipart/form-data"> 姓名:<input type="text" name="name"><br> 文件:<input type="file" name="file"><br> <input type="submit" value="提交"> </form>
后端服务器:
/**
* 练习文件上传,
* 注意前端的表单上传的参数名字,
* 需要与方法的形参的名字对应,这样SpringMVC才能帮我们正确的赋值,或者使用@RequestParm注解,进行映射
* @throws IOException
*/
@RequestMapping(value="/quick20")
@ResponseBody
public void save20(@RequestParam("name") String username,@RequestParam("file") MultipartFile uploadFile) throws IOException {
System.out.println("上传者:"+username);
//获取文件上传时的名字
String originalFilename = uploadFile.getOriginalFilename();
//通过里面的transferTo方法,帮我们调用io流将文件保存到磁盘
uploadFile.transferTo(new File("src/main/webapp/file/"+originalFilename));
System.out.println("上传成功!");
}
# 2.多文件上传
和单文件上传同理
导入fileupload和io坐标,然后配置上传文件解析器,之后开始编写代码
在前端上传多个文件时,如果以file1,file2,file3..命名. 我们也需要再形参里定义MultipartFile file1,file2,file3
这样有些麻烦,但是如果前端是以同名的多个文件上传,我们在后端直接以MultipartFile 数据或者List集合接收就行
前端代码:
<form action="${pageContext.request.contextPath}/user/quick20" method="post" enctype="multipart/form-data">
姓名:<input type="text" name="name"><br>
文件1:<input type="file" name="file"><br>
文件2:<input type="file" name="file"><br>
文件3:<input type="file" name="file"><br>
<input type="submit" value="提交">
</form>
后端代码:
/**
* 练习多文件上传,第二个参数我们以集合或者数组的方式进行接收
* @param username
* @param uploadFile
* @throws IOException
*/
@RequestMapping(value="/quick21")
@ResponseBody
public void save21(@RequestParam("name") String username,@RequestParam("file") MultipartFile[] uploadFile) throws IOException {
System.out.println("上传者:"+username);
for(MultipartFile file:uploadFile)
{
//获取文件上传时的名字
String originalFilename = file.getOriginalFilename();
//通过里面的transferTo方法,帮我们调用io流将文件保存到磁盘
file.transferTo(new File("src/main/webapp/file/"+originalFilename));
System.out.println("上传成功!");
}
}
# 15.SpringMVC拦截器
# 15.1 拦截器(interceptor)的作用
SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于处理器进行预处理和后处理
将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain)。在访问被拦截的方 法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。
# 15.2拦截器和过滤器区别
# 15.3 拦截器快速入门
自定义拦截器很简单,只有如下三步
接口所在包:spring-webmvc-5.0.5.RELEASE.jar
创建拦截器类,并实现HandlerInterceptor接口
package com.snake.Interceptor; import org.springframework.lang.Nullable; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 自定义一个拦截器,并实现HandlerInterceptor接口 * 重写三个方法,接口的三个方法都是default的,里面都是空的方法体 * 即使我们不重写方法也不会报错,就是没有任何操作 */ public class myInterceptor implements HandlerInterceptor { /** * 这个方法在内部资源(方法)被访问之前调用, 进行拦截 * 当返回true时,运行资源访问,既放行 * 当返回false时,阻止资源的访问 */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } //这个方法在内部资源(方法)被访问后,要返回视图或者数据时之前,调用,执行一些操作 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } //在返回视图或数据之后,调用,执行一些操作 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
配置拦截器
<!-- 配置我们自定义的拦截器 --> <mvc:interceptors> <mvc:interceptor> <!--拦截路径, 拦截全局必须设置成'/**', 而不能设置成'/'!!--> <mvc:mapping path="/**"/> <!--自己配置的拦截器的字节码文件所在位置--> <bean class="com.snake.Interceptor.myInterceptor"/> </mvc:interceptor> </mvc:interceptors>
测试拦截器的拦截效果
控制台输出
# 15.4 多个拦截器的执行
当同时配置同一个路径的多个拦截器时,按照在配置文件的声明的先后顺序,来指向,
如果拦截器1的声明在拦截器2的之前,则执行顺序为
<!--
配置我们自定义的拦截器
多个拦截器时,通过声明顺序决定谁先拦截,并且按照拦截器链的顺序执行对应的方法
-->
<mvc:interceptors>
<!-- 拦截器1-->
<mvc:interceptor>
<!--拦截路径, 拦截全局必须设置成'/**', 而不能设置成'/'!!-->
<mvc:mapping path="/**"/>
<!--自己配置的拦截器的字节码文件所在位置-->
<bean class="com.snake.Interceptor.MyInterceptor1"/>
</mvc:interceptor>
<!-- 拦截器2-->
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.snake.Interceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
各个方法的执行顺序 控制台输出 :
preHandle1... preHandle2... 内部资源被访问... postHandle2... postHandle1... afterCompletion2... afterCompletion1...
# 15.5 拦截方法说明
# 16.SpringMVC异常处理
# 1.1异常处理的思路
# 1.2异常处理两种方式
使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver
<!-- 配置简单映射异常处理器 --> <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <!-- 当抛出异常,但是异常处理器没有匹配的异常解决视图对应时,就会执行这个默认错误的处理 value对应的是发生这个异常后,请求转发的视图, 前缀'/'和后缀'.jsp'被视图解析器给补上了 --> <property name="defaultErrorView" value="defaultError"/> <!-- 我们手动设置具体匹配的异常如果抛出,返回的视图 --> <property name="exceptionMappings"> <map> <!--如果出现类型转换异常,返回到classCastError.jsp页面--> <entry key="java.lang.ClassCastException" value="classCastError"/> <!--我自定义的异常,如果抛出,返回到myError.sjp页面--> <entry key="com.itheima.exception.MyException" value="myError"/> </map> </property> </bean>
实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器
创建异常处理器类实现HandlerExceptionResolver
/** * 自定义的异常处理器实现类 */ public class MyExceptionResolver implements HandlerExceptionResolver { public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { //我们使用instanceof来判断异常对象 e 是否是某个异常的子类, 既异常e是否属于我们判断的异常 ModelAndView modelAndView = new ModelAndView(); if(e instanceof ClassCastException) { //我们在request域中存在信息,到错误页面去显示 modelAndView.addObject("errorInfo","类型转换异常"); }else if(e instanceof MyException) { modelAndView.addObject("errorInfo","自定义异常"); }else{ modelAndView.addObject("errorInfo","默认异常"); } //设置视图 modelAndView.setViewName("error"); return modelAndView; } }
配置异常处理器
<!-- 使用自定义的异常处理器, 只有将处理器配置成Bean加入到Spring容器就行 --> <bean id="exceptionResolver" class="com.itheima.exceptionResolver.MyExceptionResolver"/>
编写异常页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <h1>自定义异常处理器: ${errorInfo}</h1> </body> </html>
测试异常调整
# 17. @RequestParam
, @RequestBody
,@PathVariable
注解
我在controller层传的参数是List时报No primary or single unique constructor found for interface java.util.List, 这是因为 List是一个接口, 不能实例化, 需要 使用@RequestParam注解, 进行修饰, 告诉他,帮我封装值就可以,
注解 | 支持的类型 | 支持的请求类型 | 支持的Content-Type | 请求示例 |
---|---|---|---|---|
@PathVariable | url | GET | 所有 | /test/{id} |
@RequestParam | url | GET | 所有 | /test?id=1 |
@RequestBody | Body | POST/PUT/DELETE/PATCH | json | {“id”:1} |
# 18. 前端传入字符串格式时间, 后端报错(字符串无法转换为Date)
需要在 接收时间的属性, 上添加 @DateTimeFormat注解, 并且 Date的类型 必须是 java.util.date下的, 注意这个注解是帮我们自动转换 GET方式, 也就是url上的时间参数, 如果是 请求体 中 JSON格式中的时间参数, 我们需要时 @JsonFormat
注解帮我们进行转换
# 19. Controller 使用Lombok的 @AllArgsConstructor 的注解 与 @Value 注解不能共用的问题
我们知道, 在一个Bean中 注入其他的Bean, 可以通过set方法注入, 构造的方法注入, 字段注入
, 字段注入已经不被推荐使用原因是, 字段注入使用的是反射, 反射比构造器或者set慢一些.
我们通过 Lombok的 @AllArgsConstructor 注解, 可以帮我们快速生成这些字段的全参数构造器(猜测会帮我们在上面使用@Autowired 注解), 就变成了 构造的方法注入, 我们就没有必要给每一个字段使用@Autowired或者@Resource注解, 但是如果我们还需要注入 String类型时, 例如从Springboot的yaml文件中配置的值, 我们需要使用@value注解
但是@value 注解 与 @AllArgsConstructor 注解共同使用时报错, 说是找不到一个 String类型的Bean, 注入失败.
解决方法:
改用 Lombok 的 @RequiredArgsConstructor 注解, 这个注解只会为final修饰的字段属性,进行注入, 所以我们只要在String类型上使用@Value注解, 其他的类型则使用final进行修饰.
# 20. 在拦截器中返回消息乱码的问题
当我们使用拦截器的 HttpServletResponse
对象 getWriter().write() 方法返回消息时, 如果数据是包含中文的就会乱码
解决方式: 使用setCharacterEncoding() 方法,或者setContentType()方法. 注意, 编码格式不要写 成 utf-8, 会报错!