文章目录
- 一、SpringMVC简介
- 二、搭建SpringMVC框架
- 三、@RequestMapping注解
- 四、SpringMVC获取请求参数
- 五、域对象添加数据
- 六、SpringMVC的视图
- 七、RESTFul(重要)
- 八、RESTFul案例
- 九、HttpMessageConverter
- 十、文件下载和上传
- 十一、拦截器
- 十二、异常处理器
- 十三、注解配置SpringMVC
- 1.创建一个类继承AbstractAnnotationConfigDispatcherServletInitializer类,实现他的三个方法
- 2.创建SpringConfig类,使用@Configuration注解
- 3.修改初始化类的getRootConfigClasses方法,将Spring配置类放进数组
- 4.创建拦截器类,实现HandlerInterceptor接口,实现三个方法
- 5.创建SpringMVC的配置类
- 6.修改初始化类的getServletConfigClasses方法,将SpringMVC的配置类放进去(和3一样)
- 7.将DispatcherServlet的拦截路径放进去
- 8.设置过滤器(重写方法)
- 十四、SpringMVC执行流程
- 1、SpringMVC常用组件
- 2、DispatcherServlet初始化过程
- 3、DispatcherServlet调用组件处理请求
- 4、SpringMVC的执行流程
- 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
- DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
- 不存在
- 再判断是否配置了mvc:default-servlet-handler
- 如果没配置,则控制台报映射查找不到,客户端展示404错误
- 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
- 存在
- 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
- DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
- 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
- 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
- Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
- 此时将开始执行拦截器的postHandle(...)方法【逆向】。
- 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
- 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
- 将渲染结果返回给客户端。
- 十五、SpringMVC快速使用总结
一、SpringMVC简介
1、什么是MVC
MVC是一种软件架构的思想,将软件按照按照模型、视图、控制器来划分
JavaBean分为两类:
- 实体类Bean:专门存储业务数据的,如Study、User等
- 业务处理Bean:指Service或者Dao对象,专门用于处理业务逻辑和数据访问
M:Model,模型层,指工程中的javaBean(业务处理Bean类),作用是处理数据
V:View,视图层,指工程中的html或jsp页面,作用是与用户进行交互,展示数据
C:Controller,控制层,指工程中的servlet,作用是接受请求和相应浏览器
MVC的工作流程:
用户通过视图层发送请求到服务器,在服务器中请求被Controller接受,Controller调用相应的Model层处理请求,处理完毕将结果返回到Controller,Controller在根据请求处理的结果找到相应的View视图,渲染数据最后响应给服务器器
2、什么是SpringMVC
SpringMVC是Spring的一个后续产品,是Spring的一个子项目
SpringMVC是Spring为表示层开发提供的一套完备的解决方案。
本质就是一个Servlet
3、SpringMVC的特点
- 与IOC容器等基础设施无缝对接
- Spring的配置文件和SpringMVC的配置文件一摸一样
- 是基于原生的Servlet,对Servlet进行了封装,生成了功能强大的**DispatcherServlet前端控制器**,对请求和相应进行统一处理
- 全方位覆盖了表示层的各个领域,提供全面的解决方案
- 代码简洁,大幅提升开发效率
- 内部组件化程度高,可插拔式组件即插即用(需要实现什么功能,可以直接配置进去,不需要自己写)
- 性能好,适合大型项目
二、搭建SpringMVC框架
1、开发环境
IDE、Maven、tomcat、Spring
2、创建Maven工程
1.使用maven创建一个web工程
2.pom.xml中添加依赖
<dependencies>
<!--springMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- thymeleaf和spring5 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
3、配置web.xml
注册SpringMVC的DispatcherServlet控制器(统一管理请求)
a>默认配置方式(不太好)
使用这种配置方式,SpringMVC的配置文件默认位于WEB-INF下,默认名称为==<Servlet-name的值>-servlet.xml==
例如:以下配置对应的SpringMVC配置文件位于WEB-INF下,名字为dispatcherServlet-servlet.xml
<!--过滤器-->
<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>
<!--设置响应编码-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern><!--表示对所有的路径都过滤-->
</filter-mapping>
<!--配置SpringMVC的前端控制器,统一处理除jsp以外的的所有请求-->
<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>
<!-- / 可以匹配除jsp页面以外所有的请求,因为jsp有他自己的servlet程序-->
</servlet-mapping>
b>扩展配置方式(这种好)
通过init-param标签设置SpringMVC配置文件的名称和位置(在resources下创建)
通过load-on-startup标签设置SpringMVC前端控制器DispatcherServlet的初始化时间
<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>
<!--设置响应编码-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern><!--表示对所有的路径都过滤-->
</filter-mapping>
<!--配置SpringMVC的前端控制器,统一处理除jsp以外的的所有请求-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--设置springMVC配置文件的名称和路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--设置dispatcherServlet的初始化时间,1表示在服务器启动时(可以减少用户等待时间)-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
<!-- / 可以匹配除jsp页面以外所有的请求,因为jsp有他自己的servlet程序-->
</servlet-mapping>
注:
标签中使用 / 和 /* 的区别:
- / 所匹配的请求是/login或.html或.js或.css的请求路径,但是/不能匹配.jsp请求路径的请求。从而可以避免访问jsp页面时,该请求被DispatcherServlet处理,从而找不到对应的页面(因为jsp页面有单独的servlet程序)
- /* 能够匹配所有请求,例如在使用过滤器时,若需要对所有请求进行过滤,就需要使用/*的写法
4、创建请求控制器
由于前端控制器对浏览器发送的请求进行了统一的处理,但是具体的请求有不同的处理方式,所以需要创建具体的请求控制器(一个类),然后由前端控制器去匹配请求控制器中对应的方法
请求控制器中每一个请求处理的方法称为控制器方法
因为SpringMVC的控制器由一个普通类担任,因此需要通过==@Controller注解==将其标识为一个控制层组件,交给IOC容器管理,此时SpringMVC才能够识别控制器的存在
-
创建一个controller包(存放请求控制器)
-
创建一个请求控制器的类
-
给类添加@Controller注解
@Controller //spring中AOP的一个注解,说明他是一个控制器 public class FirstController { }
5、配置SpringMVC的配置文件
-
开启扫描组件(因为使用到了Controller注解)
<!--需要扫描的包--> <context:component-scan base-package="love.junqing.controller"></context:component-scan>
-
配置Thymeleaf视图解析器(可以直接复制过来)(需要添加context、mvc名称空间)
<!-- 配置Thymeleaf视图解析器 --> <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver"> <!-- 配置视图解析器优先级 --> <property name="order" value="1"/> <!-- 编码格式 --> <property name="characterEncoding" value="UTF-8"/> <!--模板--> <property name="templateEngine"> <!-- 内部bean:templateEngine的值 --> <bean class="org.thymeleaf.spring5.SpringTemplateEngine"> <property name="templateResolver"> <!-- 内部bean:templateResolver的值 --> <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver"> <!-- 视图前缀:访问页面的包名 --> <property name="prefix" value="/WEB-INF/templates/"/> <!-- 视图后缀:访问页面的后缀 --> <property name="suffix" value=".html"/> <property name="templateMode" value="HTML5"/> <property name="characterEncoding" value="UTF-8" /> </bean> </property> </bean> </property> </bean> <!-- mvc注解驱动 --> <mvc:annotation-driven/>
6、创建访问的首页页面
-
根据SpringMVC配置文件的视图前缀和视图后缀创建html页面
-
给页面添加thymeleaf的名称空间
<html lang="en" xml:th="http://www.thymeleaf.org">
-
编辑页面内容(自己随便搞点)
7、配置控制器中的方法
当前我们想要的功能是,当我们访问 ==/==的时候,就会跳转到/WEB-INF/templates/index.html页面
-
在控制器中编写方法,返回一个String类型的数据,方法名随便
-
给方法添加**RequestMapping注解**,value是/
@RequestMapping注解的功能:将请求和控制方法映射起来
参数value:根据请求地址调用对应方法,可以省略
// 当访问 / 的时候,就跳转到/WEB-INF/templates/index.html下
@RequestMapping(value = "/")//将请求和控制方法映射起来,当访问/的时候,会调用这个方法
public String goIndex(){
//返回视图名称(要访问的页面)
return "index";//包名和后缀thymeleaf已经给了
}
8、配置启动tomcat服务器
9、访问指定页面的方法
-
创建一个新的html页面(需要加上th域)
-
index页面添加a标签,跳转到新创建的页面
<!--th:href="@{/名字}会把/名字发送给控制器--> <a th:href="@{/target}">跳转到target页面</a>
-
控制器中创建方法,添加注解,返回值是页面的名字
@RequestMapping(value = "/target")//a标签发送过来的值是这个 public String goTarget(){ //返回视图名称(要访问的页面) return "target";//包名和后缀thymeleaf已经给了 }
10、总结
浏览器发送请求,若请求地址符合前端控制器的,该请求就会被前端控制器(DispatcherServlet)处理。前端控制器会读取SpringMVC的核心配置文件,通过扫描组件找到控制器,将请求地址和控制器中@RequestMapping注解的value属性值进行配置,若匹配成功,该注解所表示的处理器方法就是请求的方法。控制器方法返回一个字符串类型的页面名称,这个名称会被视图解析器解析,从而自动加上前缀和后缀,从而组成给页面的路径,通过Thymeleaf对视图进行渲染,最终转发到视图对应的页面
三、@RequestMapping注解
1、@RequestMapping注解的功能
@RequestMapping的作用:将请求和控制器方法关联起来
SpringMVC接收到指定的请求,就会找到映射关系中对应的控制器方法来处理这个请求
- 如果两个控制器的控制方法上@RequestMapping的value值一样,那么会报错
2、@RequsetMapping注解的位置
可以在类上,也可以在方法上
a.注解放到类上(层级匹配)
匹配的初始信息(一层一层匹配):先匹配发送过来的路径有没有类上的值,如果有再去匹配方法上面的
-
HTML页面:
<a th:href="@{/hello/testRequestMapping}">跳转到target页面</a>
-
控制器:
@Controller @RequestMapping(value = "/hello")//先匹配路径上有没有这个 public class TestRequestMapping { @RequestMapping(value = "/testRequestMapping")//发送的请求来自这个页面,会执行goIndex方法 public String goTarget(){ //返回视图名称(要访问的页面) return "target";//包名和后缀thymeleaf已经给了 } }
说明:因为跳转地址是 /hello/target ,所以先去控制器上看,控制器的类上有注解,先匹配是不是符合类上的地址/hello,符合,然后剩下的地址 /testRequestMapping 再去匹配类里方法上面注解的地址
b.注解放到方法上(直接匹配)
直接让浏览器发送的请求和控制器方法创建关联
@RequestMapping(value = "/testRequestMapping")//发送的过来的请求地址是/testRequestMapping,会执行goIndex方法
public String goIndex(){
//返回视图名称(要访问的页面)
return "index";//包名和后缀thymeleaf已经给了
}
3、@RequestMapping注解的value属性
value( ):根据**请求地址判断调用的请求,接收的参数可以是一个String类型数组**
要求:请求的请求地址和注解的value值相等
- @RequestMapping注解的value属性通过请求的请求地址匹配请求映射
- @RequestMapping注解的value属性是一个字符串类型的数组,表示该请求映射能够匹配多个请求地址所对应的请求
- @RequestMapping注解的**value属性必须设置**,至少通过请求地址匹配请求映射
-
多地址跳转统一页面案例
-
html页面
<!--两个请求地址--> <a th:href="@{/testRequestMapping}">跳转到target页面</a> <a th:href="@{/gototarget}">跳转到target页面</a>
-
java代码
//请求过来的地址指向同一页面 @RequestMapping(value = {"/testRequestMapping","/gototarget"})//设置多个路径都可一请求过来 public String goTarget(){ //返回视图名称(要访问的页面) return "target";//包名和后缀thymeleaf已经给了 }
-
4、@RequestMapping注解的method属性
method( ):根据请求方式(post、get)判断调用的请求
说明:@RequestMapping注解的value属性是必须的,所以method是配合value一块使用的
-
如果不设置method属性,则任何请求方式都能匹配(前提:请求地址能和value匹配成功)
-
设置method的值(可以设置多个):RequestMethod.请求方式
-
案例:
@RequestMapping(value = {"/testRequestMapping","/gototarget"}, method={RequestMethod.GET,RequestMethod.POST})//设置两种请求方式 public String goTarget(){ //返回视图名称(要访问的页面) return "target";//包名和后缀thymeleaf已经给了 }
-
说明:
- 如果value属性匹配成功,但是method属性不匹配,会报错405
-
派生注解
对于处理指定请求方式的控制器方法,SpringMVC种提供了@RequestMapping的派生注解
- 处理get请求的映射—>@GetMapping
- 处理post请求的映射—>@PostMapping
- 处理put请求的映射—>@PutMapping
- 处理delete请求的映射—>@DeleteMapping
//使用案例 @GetMapping(value="/testGetMapping")//请求方式已经给了,所以直接给一个地址就可以了 public String testGetMapping(){ return "target"; }
-
常用的请求方式:get、post、put、delete
浏览器目前只支持get、post方式的请求,如果再表单提交时method使用put或者delete,则按get方式提交
若要发送put和delete请求,则需要通过spring提供的过滤器HiddenHttpMethodFilter。后面会讲
5.@RequestMapping注解的params属性(了解)
通过请求的参数匹配请求的方法,请求参数必须同时满足才能进行请求映射,如果匹配不上报错400
-
请求参数的四种形式
-
params = {“username”}:表示发送过来的请求必须以携带username属性(不管值是多少)
@RequestMapping(value = {"/gototarget"}, params = {"username"})
-
params = {“!username”}:表示发送过来的请求不能携带username参数
-
params = {“username=admin”}:表示发送过来的请求必须以携带username属性,并且值必须是admin
-
params = {“username=!admin”}:表示发送过来的请求必须以携带username属性,但是值不能是admin
-
6.@RequestMapping注解的headers属性(了解)
通过请求头匹配请求映射,使用方法和params一样,如果匹配不上报错404
7.SpringMVC支持ant风格的路径
即支持模糊匹配
?:表示任意的单个字符
*:表示任意的0个或者多个字符
**:表示任意的一层或多层目录
注意:在使用时,必须写成/ * */xxx
8.SpringMVC支持路径中的占位符(重点)
请求参数不再使用?拼接,而是将参数作为路径的一部分,直接拼接,中间使用/分开
-
例子:
- 原始方式:/deluser?username=1&pwd=123
- rest方式:/deluser/1/123
-
案例:
@RequestMapping(value = {"/gototarget/{id}"})//{ }表示一个占位符,里面给传过来的参数起一个别名 public String rest(@PathVariable("id")Integer id){//将上面的参数赋值给方法中的形参 @PathVariable只能用在形参上 System.out.println(id); return "target"; }
-
说明:
- 如果使用了占位符,发送请求的时候,必须要将参数发送过来
- { }表示一个占位符,里面给传过来的参数起一个别名
- @PathVariable注解只能用在形参上
- 如果有多个参数,使用/{别名}往后拼接即可
四、SpringMVC获取请求参数
1、通过ServletAPI获取(request对象)(用的少)
即使用HttpServletRequest
@RequestMapping(value = {"/servletAPI"})
public String servletAPI(HttpServletRequest request){//这里web.xml中的前端控制器会自动将本次对话的request对象传入
String username = request.getParameter("username");
String password = request.getParameter("password");
return "target";
}
2、通过控制器方法的形参获取请求参数
只需要保证前端传过去的参数名和控制器方法中的形参名一样,就可以自动赋值
@RequestMapping(value = {"/servletAPI"})
public String getParam(String username,String password){//参数名需要和前端的参数名一样,参数会自动注入
System.out.println(username+" "+password);
return "target";
}
@RequestMapping(value = {"/servletAPI"})
public String getParam(String [] hobby){//会将前端传过来的多个同名参数传进去
for(String i :hobby){
System.out.println(i);
}
return "target";
}
3、@RequestParam注解
用于处理前端传过来的参数名和后端的形参名不一致
-
例子:
前端传过来的参数名是user_name,后端的参数名是username
-
案例:@RequestParam中的参数名是前端参数的名字
@RequestMapping(value = {"/servletAPI"}) public String getParam(@RequestParam("user_name")String username){//@RequestParam中的参数名是前端参数的名字 System.out.println("username"); return "target"; }
-
@RequestParam的参数
- value:参数名字(必须要写)
- require:是否必须要有这个参数值(默认是true)(可以忽略不写)
- defaultvalue:默认值(给参数一个默认的值)(可以忽略不写)
4、@RequestHeader注解
将请求头信息和控制器方法的形参创建映射关系
参数信息和使用方法与上面的@RequestParam一样
5、@CookieValue注解
将cookie数据和控制器方法的形参创建映射关系
参数信息和使用方法与上面的@RequestParam一样
6、通过pojo获取请求参数
可以在控制器方法的形参位置传入一个pojo对象,如果浏览器传输的请求参数的参数名和pojo中的属性名一一对应,那么请求参数就会自动给这个属性赋值
适用于参数很多的环境下
-
htm代码(name属性要和pojo对象的属性名一样)
<form th:action="@{/testBean}" method="get"> 用户名:<input type="text" name="username"><br/> 密码:<input type="password" name="password"><br/> 年龄:<input type="text" name="age"><br/> <input type="submit" value="提交"> </form>
-
控制器方法(对象的属性名要和表单中属性的name一样)
@RequestMapping(value = {"/testBean"}) public String testBean(User user){ System.out.println(user); return "target"; }
7、过滤器处理请求参数乱码问题
前提说明:
- 获取参数之后再设置编码格式是没有效果的,所以应该在获取参数之前就设置编码格式
- get方式没有编码错误问题(tomcat的本地配置中设置过的),只有post才有编码错误问题
- 因为配置文件中load-on-startup已经设置为服务器启动时了,所以需要一种比servlet还提早启动的技术实现编码的设置
-
启动顺序
(servlet-context)监听器、过滤器、servlet
-
配置过滤器
web.xml文件中进行如下配置:
<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> <!--设置响应编码--> <init-param> <param-name>forceResponseEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern><!--表示对所有的路径都过滤--> </filter-mapping>
五、域对象添加数据
必须知道的三种域对象:request(一次请求)、session(一次会话)、application(服务器启动时创建,关闭时销毁)
1.向request域添加数据(5种方式)
1、通过servletAPI添加域数据
-
java代码
@RequestMapping("/") public String goIndex(HttpServletRequest request){ request.setAttribute("key1","123");//设置request域对象:键,值 return "index";//请求转发到index页面 }
-
html代码
request域:直接通过键获得
session域:通过session.键获得
servletContext域:通过application.键获得
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"><!--添加thymeleaf的th标签--> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> this is index page <!--thymeleaf通过键获得值,会报错不用管他--> <p th:text="${key1}"></p> </body> </html>
2、使用ModelAndView向request域对象共享数据(建议使用)
ModelAndView有Model和View两个功能
- Model:主要用于向请求域共享数据
- View:主要用于设置视图(实现页面跳转)
@RequestMapping("/")
public ModelAndView goIndex(){//需要返回一个ModelAndView对象
ModelAndView mav = new ModelAndView();
//往域中添加数据
mav.addObject("testkey","yfj");//将数据添加到域中
//设置跳转界面
mav.setViewName("index");
return mav;//将对象返回
}
3、使用Model向request域对象共享数据
@RequestMapping("/")
public String goIndex(Model model){
//往域中添加数据
model.addAttribute("testkey","zyj");//
//跳转界面
return "index";
}
4、使用map向request域对象共享数据
@RequestMapping("/")
public String goIndex(Map<String,Object> map){//键是String类型,值根据实际情况定
//往域中添加数据
map.put("testkye","zyj");
//跳转界面
return "index";
}
5、使用ModelMap向request域对象共享数据
@RequestMapping("/")
public String goIndex(ModelMap modelMap){
//往域中添加数据
modelMap.addAttribute("testkey","hello,modelmap");
//跳转界面
return "index";
}
6、Model、ModelMap、Map的关系
Model、ModelMap、Map类型的参数本质上都是BindingAwareModelMap类型的,最后都会封装到ModelAndView中
2.向Session域添加数据
推荐使用Servlet原生API
-
java代码
@RequestMapping("/") public String goIndex(HttpSession Session){ //往域中添加数据 Session.setAttribute("testkey","hello,modelmap"); //跳转界面 return "index"; }
-
html代码(使用session.key值)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"><!--添加thymeleaf的th标签--> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> this is index page <!--thymeleaf通过键获得值,会报错不用管他--> <p th:text="${session.testkey}"></p> </body> </html>
3.向application域添加数据
通过session对象获取application对象
-
java代码
@RequestMapping("/") public String goIndex(HttpSession Session){ ServletContext servletContext = httpSession.getServletContext();//通过httpsession获取application域 //往域中添加数据 servletContext.setAttribute("testkey","hello,application"); //跳转界面 return "index"; }
-
html代码
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"><!--添加thymeleaf的th标签--> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> this is index page <!--thymeleaf通过键获得值,会报错不用管他--> <p th:text="${application.testkey}"></p> </body> </html>
六、SpringMVC的视图
SpringMVC的视图是View接口,视图的作用是渲染数据,将模型Model中的数据展示给用户
SpringMVC视图的种类很多,默认有转发视图(InternalResourceView)和重定向视图(RedirectView)
其他视图种类:
- 当引入jstl依赖时,**转发视图**会自动==转化为jstlView==
- 若使用的视图技术为Thymeleaf,在SpringMVC的配置中配置了Themeleaf的视图解析器,由此视图解析器解析之后得到的时ThymeleafView
创建视图的对象种类只和对象名称有关:如果没有前缀创建的就是 ThymeleafView;如果前缀是 Redirect:,就会创建RedirectView(重定向视图);如果前缀是forward: ,就会创建InternalResourceView(转发视图)
1.Thymeleaf(用来跳转页面)
当控制器方法设置的视图名称没有任何前缀,此时的视图名称会被SpringMVC的配置文件中配置的Themeleaf的视图解析器解析,视图名称拼接视图前缀和视图后缀从而得到最总路径,会通过转发的方式实现跳转,用于跳转到Thymeleaf解析的页面,不能用于跳转到下一个请求
@RequestMapping("/")
public String goIndex(){
return "index";
}
2.转发视图(用来跳转请求)(用的少)
当控制器方法设置的视图名称前缀是**forward:,会创建InternalResourceView视图,视图把前缀去掉,然后转发到后面的请求(只能跳转请求**)
//请求转发
@RequestMapping("/")
public String forward(){
return "forward:/test";//会跳转到/test请求
}
3.重定向视图(用来跳转请求)(不同功能的跳转)
当控制器方法设置的视图名称前缀是**redirect:,会创建RedirectView视图,视图把前缀去掉,然后重定向到后面的请求(只能跳转请求和WEB-INF以外的界面**)
//请求重定向
@RequestMapping("/")
public String forward(){
return "redirect:/text";//重定向到/test请求
}
4.视图控制器view-controller(仅用于实现页面跳转)
使用条件:当跳转路径不需要其他处理,可以直接跳转到相应页面的情况下使用
不需要在控制器方法中再去跳转
当SpringMVC中设置任何一个view-controller时,其他控制器中的请求映射将全部失效,此时需要在SpringMVC配置文件中添加mvc注解驱动标签
-
添加mvc名称空间
-
添加标签,配置跳转路径(path:表示当前路径,view-name:表示需要跳转的页面)
<mvc:view-controller path="/" view-name="index"></mvc:view-controller><!--报错但是可以正常使用-->
-
开启mvc的注解驱动(不开启会导致后面的请求映射全部失效)
<mvc:annotation-driven/>
5.兼容jsp页面
-
SpringMVC配置文件中添加标签
<mvc:annotation-driven/> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/templates/"></property><!--页面路径前缀--> <property name="suffix" value=".jsp"></property><!--页面路径后缀--> </bean>
七、RESTFul(重要)
1.RESTFul简介
REST:Representational State Transfer 表现层资源状态转移
RESTFul是一种代码风格,制定一个规则,让前后端统使用同一个路径获取资源,根据请求方式调用不同方法
2.模拟RESTFul
GET(获取)、POST(增加)PUT(修改)DELETE(删除)
请求参数从前到后使用 / 分开,不使用问号键值对携带请求参数,而是将数据作为url请求地址的一部分
使用了上面笔记中的占位符操作
操作 | 传统方式 | REST风格 |
---|---|---|
查询操作 | getUSerById?id=1 | user/1—>get请求方式 |
保存操作 | saveUSer | user—>post请求方式 |
删除操作 | deleteUser?id=1 | user/1—>delete请求方式 |
更新操作 | updateUser | user—>put请求方式 |
1.模拟get和post方法
-
HTML代码
<a th:href="@{/user}">查询所有用户信息</a><br/> <a th:href="@{/user/1}">查询id=1的用户信息</a><br/> <form th:action="@{/user}" method="post"> 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> <input type="submit" value="添加"> </form>
-
Java代码
//查询所有用户信息--》get方式 @RequestMapping(value="/user",method = RequestMethod.GET) public String getUsers(){ System.out.println("查询所有用户信息"); return "success"; } //根据id查询单个用户信息---》get方式 @RequestMapping(value="/user/{id}",method = RequestMethod.GET)//{}:是占位符,里面是参数名 public String getUser(@PathVariable("id") Integer id){//@PathVariable用于把参数名注入形参 System.out.println("查询id="+id+"的用户信息"); return "success"; } //添加用户信息--》post方式 @RequestMapping(value="/user",method = RequestMethod.POST) public String addUser(String username,String password){//也可以使用一个pojo对象直接注入 System.out.println("添加用户信息用户名:"+username+"\t密码:"+password); return "success"; }
2.模拟put方法
- 首先需要**配置下面的HiddenHttpMethodFilter过滤器**
- 然后设置**表单的方法为post**
- 设置一个名字为**_method的隐藏域,值是delete或者put**
-
HTML代码
<form th:action="@{/user}" method="post"> <input type="hidden" name="_method" value="put"> 用户名:<input type="text" name="username"> 密码:<input type="password" name="password"> <input type="submit" value="修改"> </form>
-
Java代码
@RequestMapping(value="/user",method = RequestMethod.PUT) public String putUser(String username,String password){//也可以使用一个pojo对象直接注入 System.out.println("修改的用户信息用户名:"+username+"\t密码:"+password); return "success"; }
2.模拟delete方法
-
因为删除和修改一般都是超连接,所以超链接里面的地址需要用下面的方式拼接
<a th:href="@{/user/}+${参数}">查询id=参数的用户信息</a>
-
因为超链接使用的是get方法,所以需要创建一个表单,内容是一个隐藏域
<form method="post"><!--提交地址让js赋值--> <input type="hidden" name="_method" value="delete"> </form>
-
mvc配置文件开放对静态资源的访问
<mvc:defaylt-servlet-handler><!--防止js被前端控制器处理-->
-
当点击超链接时,我们使用js控制表单提交
<!--只是一个演示,不知道对不对--> 超链接的名字.function(event){ var deleteForm=document.getElementbyid("表单的名字"); <!--超链接的地址给表单的action--> deleteForm.action=event.target.href; deleteForm.submit();<!--提交表单--> event.preventDefault();<!--取消默认行为--> }
3、HiddenHttpMethodFilter的配置
用来实现delete和put方法,本质是一个过滤器,所以需要在web.xml中配置
要放在CharacterEncodingFilter后面
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern><!--拦截路径-->
</filter-mapping>
八、RESTFul案例
1.创建Maven的web工程
2.导入maven依赖
暂时没有使用数据库
<dependencies>
<!--springMVC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.1</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- thymeleaf和spring5 -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.12.RELEASE</version>
</dependency>
</dependencies>
3.配置web.xml文件(标配:两个过滤器、一个控制器)
<!--编码过滤器-->
<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>
<!--设置响应编码-->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern><!--表示对所有的路径都过滤-->
</filter-mapping>
<!--隐藏域过滤器(用来实现delete和put方法)-->
<filter>
<filter-name>HiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern><!--拦截路径-->
</filter-mapping>
<!--配置SpringMVC的前端控制器,统一处理除jsp以外的的所有请求-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--设置springMVC配置文件的名称和路径-->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC.xml</param-value>
</init-param>
<!--设置dispatcherServlet的初始化时间,1表示在服务器启动时(可以减少用户等待时间)-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
<!-- / 可以匹配除jsp页面以外所有的请求,因为jsp有他自己的servlet程序-->
</servlet-mapping>
4.创建、配置springMVC配置文件
- 开启扫组件
- 开启thtmeleaf视图解析器
- 如果开启了view-controller,还需要开启mvc注解驱动
<!--需要扫描的包-->
<context:component-scan base-package="love.junqing.controller"></context:component-scan>
<!-- 配置Thymeleaf视图解析器 -->
<bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
<!-- 配置视图解析器优先级 -->
<property name="order" value="1"/>
<!-- 编码格式 -->
<property name="characterEncoding" value="UTF-8"/>
<!--模板-->
<property name="templateEngine">
<!-- 内部bean:templateEngine的值 -->
<bean class="org.thymeleaf.spring5.SpringTemplateEngine">
<property name="templateResolver">
<!-- 内部bean:templateResolver的值 -->
<bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
<!-- 视图前缀:访问页面的包名 -->
<property name="prefix" value="/WEB-INF/templates/"/>
<!-- 视图后缀:访问页面的后缀 -->
<property name="suffix" value=".html"/>
<property name="templateMode" value="HTML5"/>
<property name="characterEncoding" value="UTF-8" />
</bean>
</property>
</bean>
</property>
</bean>
<mvc:defaylt-servlet-handler><!--开放对静态资源的访问-->
<mvc:annotation-driven/><!--开启mvc注解驱动-->
5.创建控制器类
使用注解@Controller
6.创建bean类
使用注解@Reponsitory,开启之后service层可以根据类型使用@Autowired自动注入
7.编写前端界面
-
修改数据
-
点击超链接,将id发送到后端控制器
-
后端控制器根据id查询单条数据
-
将数据封装到Model中,然后跳转到前端页面显示原来的信息,等待用户修改数据
<!--回显输入的数据--> <input type="text" name="email" th:value="${employee.email}" <!--回显单选框--> <input type="text" name="sex" th:field="${employee.sex}"
-
用户点击修改后,将表单提交到后台控制器(两个隐藏域:put方式和id值),根据id值查询对象,然后把对象的数据修改,重定向到所有数据的页面
-
8.配置tomcat
九、HttpMessageConverter
报文信息转化器:将请求报文转换为java对象,或者将java对象转换为响应报文
HttpMessageConverter提供了两个注解和两个类型(两个关于请求报文的、两个关于响应报文的):@RequestBody(请求报文转换为java对象)、@ResponseBody(将java对象转换为响应报文)、RequestEntity(可以接受整个请求报文)、ResponseEntity(可以接受整个响应报文)
1、@RequestBody
- @RequestBody可以获取请求体(请求体就是请求参数,所以post才有请求体)
- 在控制器方法中设置一个形参,使用@RequestBody进行表示,当前请求的请求体就会为形参自动赋值
-
html代码
<form th:action="@{/test}" method="post"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" value="提交"> </form>
-
java代码
@RequestMapping("/test") public String test(@RequestBody String requestBody){ System.out.println(requestBody); return "success"; }
2、RequestEntity
封装整个请求报文的一种类型
-
java代码
@RequestMapping("/test") public String test(RequestEntity<String> requestEntity){ System.out.println(requestEntity.getHeaders());//获取请求头信息 System.out.println(requestEntity.getBody());//获取请求体信息 return "success"; }
3、@ResponseBody
响应浏览器数据
功能类似于:response.getwriter().writer(“hello,world”);
-
html代码
<a th:href="@{/test}">测试</a>
-
java代码
@RequestMapping("/test") @ResponseBody public String test(){ return "hello,world!";//响应体信息,屏幕上只会显示这一句话 }
4、ResponseEntity
响应实体
用在控制器方法的返回值类型上
就是自定义的响应报文
可以干什么:文件下载(看笔记的下载)
5、Spring处理json
@ResponseBody如果返回一个对象,浏览器不能解析,所以需要使用json
-
导入json的依赖
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.12.1</version> </dependency>
-
开启mvc注解驱动
<mvc:annotation-driven/><!--view-controller 静态资源、json对象都使用到了-->
-
在控制器方法上添加@ResponseBody注解,将对象作为欸返回值
会自动转换为json格式的字符串
@RequestMapping("/test") @ResponseBody public User test(){ return new User("yfj","123455",22); }
6、SpringMVC处理ajax
ajax:页面不发生跳转,和后台发生数据交换
使用vue+ajax
jquery也可以但是忘了
前端数据发送到后端,并回传
-
导入vue和axios的js文件
-
html文件中导入js文件
<script type="text/javascript" th:src="@{static/js/axios.min.js}"></script> <script type="text/javascript" th:src="@{static/js/vue.js}"></script>
-
给超链接一个点击事件
<a @click="testAxios" th:href="@{/testAjax}">测试Ajax</a>
-
编写js代码
<script type="text/javascript"> //1.new Vue对象 new Vue({ el:"#app",//表示容器的id method:{//功能 testAxios:function (event) {//点击超链接出触发的功能,event表示当前事件 axios({ method: "post",//请求方式 url:event.target.href,//触发事件的地址 params:{//传输的数据 username="admain", password="123" } }).then(function (response) {//表示处理成功之后 alert(response.data);//显示响应的数据 }); event.preventDefault();//取消默认事件 } } }); </script>
-
后端
@RequestMapping("/test") @ResponseBody public String test(String username ,String password){ System.out.println("前端发送过来的数据:"+username+password); return "回传给后端的数据"; }
7、@RestController注解(重要)
- 这个注解加在类上(因为微服务每一个控制器方法都需要加上@ResponseBody注解)
- 这个注解是一个复合注解:@ResponseBody和@Controller
- 加上之后说明:每一个控制器方法的返回值都作为响应体
十、文件下载和上传
1.文件下载
使用ResponseEntity实现下载文件的功能
@RequestMapping("/testDown")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realPath = servletContext.getRealPath("/static/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组
byte[] bytes = new byte[is.available()];//available()获取输入流所有字节
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
//关闭输入流
is.close();
return responseEntity;
}
2.文件上传
- 上传只能使用post方式
-
依赖
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.1</version> </dependency>
-
配置上传解析器
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
-
前端代码
form表单需要设置属性:enctype=“multipart/form-data”
<form th:action="@{/upload}" method="post" enctype="multipart/form-data"> 头像:<input type="file" name="photo"> <input type="submit" value="提交"> </form>
-
后端代码
@RequestMapping("/upload") public String testUp(MultipartFile photo, HttpSession session) throws IOException { //获取上传的文件的文件名 String fileName = photo.getOriginalFilename(); //处理文件重名问题 //截取最后一个.之前的内容 String hzName = fileName.substring(fileName.lastIndexOf(".")); //获取随机uuid String uuid= UUID.randomUUID().toString() ; //拼接名字 fileName =uuid+hzName; //获取服务器中保存photo资源的目录 ServletContext servletContext = session.getServletContext(); String photoPath = servletContext.getRealPath("photo");//photo资源保存的真实路径 File file = new File(photoPath); if(!file.exists()){ //如果不存在创建目录 file.mkdir(); } String finalPath = photoPath + File.separator + fileName;//文件保存路径 //实现上传功能 photo.transferTo(new File(finalPath));//设置文件上传的位置 return "success"; }
十一、拦截器
拦截器的英文:interceptor
作用于控制器方法
一共有三种:一个在控制器执行之前,一个在控制器执行之后,一个在视图渲染完毕之后
1.拦截器的配置
第一种:拦截所有的控制器方法
-
SpringMVC配置文件中配置
<!--拦截器--> <mvc:interceptors> <!--bean 表示哪个类是拦截器--> <bean class="love.junqing.controller.FirstInterceptor"></bean> </mvc:interceptors>
-
创建一个类(放在interceptors包下)
-
实现接口HandlerInterceptor
-
重写里面的三个方法(Ctrl+O)
public class FirstInterceptor implements HandlerInterceptor { //控制器方法执行之前 //功能是拦截:返回true表示放行,返回false表示拦截 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("FirstInterceptor--->preHandle"); return true;//true表示放行请求 } //控制器方法执行之后 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("FirstInterceptor--->postHandle"); } //视图渲染完成之后 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("FirstInterceptor--->afterCompletion"); } }
第二种:拦截所有的控制器方法
-
创建一个控制器类添加注解
@Component public class FirstInterceptor implements HandlerInterceptor { //控制器方法执行之前 //功能是拦截:返回true表示放行,返回false表示拦截 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("FirstInterceptor--->preHandle"); return true;//true表示放行请求 } //控制器方法执行之后 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("FirstInterceptor--->postHandle"); } //视图渲染完成之后 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("FirstInterceptor--->afterCompletion"); } }
-
SpringMVC配置文件中配置
<!--拦截器--> <mvc:interceptors> <!--所在包需要被扫描到--> <ref bean="firstInterceptor"></ref> </mvc:interceptors>
第三种:拦截部分控制器的方法
<!--拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/*"/><!--拦截路径:一个*表示一层路径;**表示所有路径-->
<mvc:exclude-mapping path="/"/><!--排除需要拦截的路径(即:不需要拦截的路径)-->
<bean class="love.junqing.controller.FirstInterceptor"></bean><!--bean 和 ref 标签都行-->
</mvc:interceptor>
</mvc:interceptors>
2.拦截器的三个抽象方法
preHandle:控制器方法执行之前拦截,返回true表示放行,就会调用控制器方法,返回false表示拦截,就不会调用控制器方法
postHandle:控制器方法执行完成之后
afterCompletion:视图渲染完成之后
3.多个拦截器的执行顺序
-
若每个拦截器的preHandle()都返回true
拦截器的执行顺序与在SpringMVC的配置文件的配置顺序有关:
-
preHandle()会按照配置的顺序执行
-
postHandle()和afterComplation()会按照配置的反序执行
-
-
若某个拦截器的preHandle()返回了false
-
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行
-
返回false的拦截器之前的拦截器的afterComplation()会执行
-
十二、异常处理器
SpringMVC提供的一个处理控制器方法执行过程中出现的异常的接口:HandlerExceptionResolver
接口的实现类有两个:
- DefaultHandlerExceptionResolver(默认异常处理类(返回一个新的ModelAndView))
- SimpleMappingExceptionResolver(自定义异常处理类(可以实现自己的功能))
1.基于配置的异常处理
-
配置SpringMVC,添加一个bean标签
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props><!--键值对注入数据--> <!--key:是异常的全类名--> <!--里面的值是产生异常后跳转的视图(遵循视图创建规则)--> <prop key="java.lang.ArithmeticException">error</prop> </props> </property> <!--将异常信息保存到请求域中(可以不设置),前端界面用ex作为键输出--> <property name="exceptionAttribute" value="ex"></property> </bean>
-
前端页面
<a th:href="@{/test}">数学运算异常</a>
-
后端控制器(故意产生一个数学运算错误)
@RequestMapping("/test") public String test(){ System.out.println(1/0);//故意抛出错误 return "success"; }
-
发生异常后跳转的界面(error界面)
<body> 运算错误 <!--输出域中的异常信息--> <p th:text="${ex}"></p> </body>
2.基于注解的异常处理
-
创建一个类添加@ControllerAdvice
-
创建方法添加@ExceptionHandler(value值是异常的class对象)
@ControllerAdvice public class ExceptionController { @ExceptionHandler(value = {ArithmeticException.class,NullPointerException.class}) public String testException(Exception ex, Model model){//Exception 异常信息 ,model用来往请求域中添加数据 model.addAttribute("ex",ex);//将异常信息放到请求域中 return "error";//当发生异常后跳转的界面 } }
十三、注解配置SpringMVC
使用配置类和注解代替web.xml和SpringMVC配置文件
当我们的类扩展了AbstractAnnotationConfigDispatcherServletInitializer并将其部署到**Servlet3.0容器**的时候,容器会自动发现它,并用它来配置Servlet上下文。
1.创建一个类继承AbstractAnnotationConfigDispatcherServletInitializer类,实现他的三个方法
public class WebInit extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 指定Spring的配置类
* @return
*/
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
/**
* 设置SpringMVC的配置类
* @return
*/
protected Class<?>[] getServletConfigClasses() {
return new Class[0];
}
/**
* 指定DispatcherServlet的映射规则,即url-pattern(拦截路径)
* @return
*/
protected String[] getServletMappings() {
return new String[0];
}
}
2.创建SpringConfig类,使用@Configuration注解
@Configuration
public class SpringConfig {
//ssm整合之后,spring的配置信息写在此类中
}
3.修改初始化类的getRootConfigClasses方法,将Spring配置类放进数组
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
4.创建拦截器类,实现HandlerInterceptor接口,实现三个方法
public class TestInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("testpreHandle");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
5.创建SpringMVC的配置类
/**
* SpringMVC的配置文件需要的功能:
* 1.扫描组件 2.视图解析器 3.view-controller(不需要处理的页面跳转) 4.default-servlet-handler(静态资源处理)
* 5.mvc注解驱动 6.文件上传解析器 7.异常处理 8.拦截器
*/
@Configuration//配置类的注解
@ComponentScan("love.junqing.controller")//开启注解扫描
@EnableWebMvc//开启mvc注解驱动()
public class SpringMVCConfig implements WebMvcConfigurer {//实现这个接口用来实现其他插件
/**
* 4.default-servlet-handler(静态资源处理)
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 8.拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
TestInterceptor testInterceptor = new TestInterceptor();//创建拦截器对象
registry.addInterceptor(testInterceptor).addPathPatterns("/**");//添加拦截路径(/** 是所有请求 /* 是拦截一层)
//excludePathPatterns() 排除拦截路径
}
/**
* 3.view-controller(不需要处理的页面跳转)
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/success").setViewName("success");//当访问/suceess路径时访问success界面
}
/**
* 6.文件上传解析器
*/
public MultipartResolver multipartResolver(){
CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
return commonsMultipartResolver;
}
/**
* 7.异常处理
*/
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
SimpleMappingExceptionResolver simpleMappingExceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("java.lang.ArithmeticException","error");//第一个参数时异常类型,第二个参数时跳转页面
simpleMappingExceptionResolver.setExceptionMappings(properties);
simpleMappingExceptionResolver.setExceptionAttribute("ex");//将异常信息放到ex中,控制器可以将ex作为键获取信息
exceptionResolvers.add(simpleMappingExceptionResolver);
}
/**
* 2.视图解析器
*/
//配置生成模板解析器
@Bean
public ITemplateResolver templateResolver() {
WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
// ServletContextTemplateResolver需要一个ServletContext作为构造参数,可通过WebApplicationContext 的方法获得
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(
webApplicationContext.getServletContext());
templateResolver.setPrefix("/WEB-INF/templates/");//视图前缀
templateResolver.setSuffix(".html");//视图后缀
templateResolver.setCharacterEncoding("UTF-8");
templateResolver.setTemplateMode(TemplateMode.HTML);
return templateResolver;
}
//生成模板引擎并为模板引擎注入模板解析器
@Bean
public SpringTemplateEngine templateEngine(ITemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
return templateEngine;
}
//生成视图解析器并未解析器注入模板引擎
@Bean
public ViewResolver viewResolver(SpringTemplateEngine templateEngine) {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setCharacterEncoding("UTF-8");
viewResolver.setTemplateEngine(templateEngine);
return viewResolver;
}
}
6.修改初始化类的getServletConfigClasses方法,将SpringMVC的配置类放进去(和3一样)
7.将DispatcherServlet的拦截路径放进去
protected String[] getServletMappings() {
return new String[]{"/"};
}
8.设置过滤器(重写方法)
@Override
protected Filter[] getServletFilters() {
//1.编码过滤器
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("utf-8");//设置编码格式
characterEncodingFilter.setForceRequestEncoding(true);//设置响应编码
//2.隐藏域过滤器(用来实现delete和put方法)
HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
return new Filter[]{characterEncodingFilter,hiddenHttpMethodFilter};
}
十四、SpringMVC执行流程
1、SpringMVC常用组件
-
DispatcherServlet:前端控制器,不需要工程师开发,由框架提供
作用:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
-
HandlerMapping:控制器映射器,不需要工程师开发,由框架提供
作用:根据请求的url、method等信息查找Handler(将请求和方法进行映射),即控制器方法
-
Handler:控制器,需要工程师开发
作用:在DispatcherServlet的控制下Handler对具体的用户请求进行处理
-
HandlerAdapter:控制器适配器,不需要工程师开发,由框架提供
作用:通过HandlerAdapter对处理器(控制器方法)进行执行
-
ViewResolver:视图解析器,不需要工程师开发,由框架提供
作用:进行视图解析,得到相应的视图,例如:ThymeleafView、InternalResourceView、RedirectView
-
View:视图
作用:将模型数据通过页面展示给用户
2、DispatcherServlet初始化过程
DispatcherServlet本质就是一个Servlet,所以遵循Servlet的生命周期
1.初始化WebApplicationContext
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建WebApplicationContext
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// 刷新WebApplicationContext
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 将IOC容器在应用域共享
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
2.创建WebApplicationContext
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 通过反射创建 IOC 容器对象
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置父容器
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
configureAndRefreshWebApplicationContext(wac);
return wac;
}
3.DispatcherServlet初始化策略
FrameworkServlet创建WebApplicationContext后,刷新容器,调用onRefresh(wac),此方法在DispatcherServlet中进行了重写,调用了initStrategies(context)方法,初始化策略,即**初始化DispatcherServlet的各个组件**
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
3、DispatcherServlet调用组件处理请求
1.processRequest()
FrameworkServlet重写HttpServlet中的service()和doXxx(),这些方法中调用了processRequest(request, response)
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
// 执行服务,doService()是一个抽象方法,在DispatcherServlet中进行了重写
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
2.doService()
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath requestPath = null;
if (this.parseRequestPath && !ServletRequestPathUtils.hasParsedRequestPath(request)) {
requestPath = ServletRequestPathUtils.parseAndCache(request);
}
try {
// 处理请求和响应
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (requestPath != null) {
ServletRequestPathUtils.clearParsedRequestPath(request);
}
}
}
3.doDispatch()
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);
// Determine handler for the current request.
/*
mappedHandler:调用链
包含handler、interceptorList、interceptorIndex
handler:浏览器发送的请求所匹配的控制器方法
interceptorList:处理控制器方法的所有拦截器集合
interceptorIndex:拦截器索引,控制拦截器afterCompletion()的执行
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
// 通过控制器方法创建相应的处理器适配器,调用所对应的控制器方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器的preHandle()
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 由处理器适配器调用具体的控制器方法,最终获得ModelAndView对象
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 调用拦截器的postHandle()
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
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 {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
4.processDispatchResult()
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable 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);
}
}
// Did the handler return a view to render?
if (mv != null && !mv.wasCleared()) {
// 处理模型数据和渲染视图
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
if (mappedHandler != null) {
// Exception (if any) is already handled..
// 调用拦截器的afterCompletion()
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
4、SpringMVC的执行流程
-
用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
-
DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
-
不存在
-
存在
-
根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回。
-
DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
-
如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
-
提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:
a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
-
Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。
-
此时将开始执行拦截器的postHandle(…)方法【逆向】。
-
根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。
-
渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。
-
将渲染结果返回给客户端。
-
-
十五、SpringMVC快速使用总结
1.请求路径的设置
@RequestMapping
注解:如果放在方法上用于设置当前控制器方法的请求访问路径,如果放在类上用于设置当前控制器的请求访问前缀路径
2.请求参数的接收【非json类型】
-
无论是GET请求还是POST请求,普通参数可以直接在方法里面接收【只要方法的参数名和前端传递的参数名一样就行】
//例如: public void getParam(String name,String password){}//这里的意思就是传过来了name和password
这里说的是名称一样不加注解可以直接接收,但是如果名字不一致那么不加注解就不会被接收到
-
如果前后端的参数名不一致可以使用
@RequestParam
注解,他的值是前端的参数名。//例如:前端传过来的参数是username,而我们方法中的参数名是name public void getParam(@RequestParam("username")String name,String password){}//这个意思就是前端的username参数用name接收
-
如果前端的属性名和后端方法中对象的属性一样,那么可以自动注入
//例如: public void getParam(User user){}//前后端参数名一样的话,参数可以自动注入到user对象的属性中
-
如果数据需要用数组接收那么直接收就行,可是如果数据需要用List集合接收就需要
@RequestParam
注解//例如: public void getParam(String [] likes){}//可以直接接收,但是要求前端传过来的参数也是likes //例如: public void getParam(List<String> likes){}//这个就是错误的,因为他不知道,需要用下面的方法 public void getParam(@RequestParam List<String> likes){}//这个就可以接前端的likes集合
3.请求参数的接收【json类型】
后端想要接收前端的json数据,必须要有jackson依赖,同时需要在配置类上使用@EnableWebMvc注解开启json转java对象【SpringMVC才这么做】
因为数据是请求体中的,所以方法的参数上直接加@RequestBody
注解就可以【对象和集合都一样】
//例如:
public void getParam(@RequestBody User user){}//这个意思就是接收前端传过来的json数据直接封装到对象中【前端的参数和后端的对象的属性名一致】
4.请求参数的接收【日期类型参数】
-
日期类型的参数如果是标准格式可以直接用Date类型接收【只需要前后端名字一致】【标准类型格式(就是中间用斜杠分开):2000/10/03】
-
如果是非标准类型的时间可以使用
@DateTimeFormat
注解,然后设置pattern属性//例如: public void getParam(@DateTimeFormat(pattern="yyyy-MM-dd") Date date){}//这样就可以接收前端以-分开的时间date public void getParam(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){}//这样就可以接收年月日时分秒
5.响应数据给前端
@ResponseBody
注解的作用:设置当前控制器方法的返回值作为响应体【集合、对象会自动转为Json】
-
响应页面:如果后端需要将页面响应给前端,那么就直接返回页面,方法的返回类型是String
说明:不要加@ResponseBody注解或者@RestController注解
//例如: @RequestMapping("/index") public String toJump(){//返回页面一定不要加@ResponseBody或者@RestController注解 return "index.jsp"; }
-
响应字符串:如果需要给前端响应字符串,需要加@ResponseBody,不然会认为返回页面
//例如: @RequestMapping("/text") @ResponseBody //返回数据要加@ResponseBody public String toJump(){ return "hello world"; }
-
响应Json数据:如果需要给前端响应json数据,只需要加@ResponseBody,然后将数据封装到pojo中返回即可【得有jackon的坐标】
```java //例如: @RequestMapping("/json") @ResponseBody //返回json数据要加@ResponseBody public User toJump(){ return new User(); //集合类型的也是这样 } ```
6.Rest风格
-
传统风格资源描述形式:
http://localhost/user/getById?id=1
REST风格的描述形式:http://localhost/user/1
-
按照REST风格访问资源
http://localhost/users //查询全部用户信息 GET http://localhost/users/1 //查询指定用户信息 GET http://localhost/users //添加用户信息 POST http://localhost/users //修改用户信息 PUT http://localhost/user/1 //删除用户信息DELETE
-
接收路径中的参数:如果参数直接以
/
的方式拼接到了路径中,那么我们后端的请求路径要用占位符,同时方法的参数列表要用@PathVariable
注解。说明:占位符需要和参数列表中的参数名一样
//例如:前端的请求路径http://localhost/users/1,参数是1 //后端的方式就是: @GetMapping("/users/{id}") //占位符需要和参数列表中参数的名一致 public void getUser(@PathVariable Integer id){//参数需要用@PathVariable注解 }
-
REST风格引出的派生注解
@RestController //是@Controller和@ResponseBody的合成注解 @GetMapping @PutMapping @PostMapping @DeleteMapping
7.资源控制器
资源拦截器可以拦截资源请求,例如访问某个资源是否让访问。
-
使用方法:
-
继承
WebMvcConfigurationSupport
,然后使用@Configuration注解将类加入到容器中 -
然后重写
addResourceHandlers
方法 -
调用
registry
对象的addResourceHandler
方法设置拦截路径 -
调用
registry
对象的addResourceLocations
方法设置映射路径
-
-
使用案例:访问/page下的所有页面的时候,你就去对应的地方找
@Configuration
public class WebMvcConfiguration extends WebMvcConfigurationSupport {
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/page/**").//拦截的请求
addResourceLocations("/page/");//让他去/page/下面找
}
}
8.拦截器(Interceptor)
-
拦截器的作用:是一种动态拦截方法调用的机制,可以在指定方法调用的前后执行预先设定的方法,能够阻止原始方法的执行
-
拦截器和过滤器的区别:过滤器能够对所有的访问进行拦截,而拦截器只能针对于SpringMVC的访问进行拦截
-
拦截器的创建方法:
- 创建一个类,实现
HandlerInterceptor接口
(这个接口有三个方法:前置拦截,后置拦截,和最终拦截) - 根据需要实现其中的方法,然后返回true放行,返回false终止访问。
- 然后在资源拦截器中重写
addInterceptors
方法,用来设置设置资源拦截器拦截的路径。 - 然后将拦截器对象注入到资源拦截器中。
- 调用registry的
addInterceptor
方法指定是为哪个拦截器配置的,然后调用addPathPatterns方法指定拦截路径(这一步一个方法里可以指定多个registry的addInterceptor) - 最后还可以调用
excludePathPatterns
方法执行排除哪些路径
- 创建一个类,实现
-
使用案例:JWT为例
@Component public class JwtTokenUserInterceptor implements HandlerInterceptor { @Autowired private JwtProperties jwtProperties; /** * 前置校验:校验jwt */ public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //判断当前拦截到的是Controller的方法还是其他资源 if (!(handler instanceof HandlerMethod)) { //当前拦截到的不是动态方法,直接放行 return true; } //1、从请求头中获取令牌 String token = request.getHeader(jwtProperties.getUserTokenName()); //2、校验令牌 try { log.info("jwt校验:{}", token); Claims claims = JwtUtil.parseJWT(jwtProperties.getUserSecretKey(), token); Long userId = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString()); log.info("当前用户的id:{}", userId); BaseContext.setCurrentId(userId); //3、通过,放行 return true; } catch (Exception ex) { //4、不通过,响应401状态码 response.setStatus(401); return false; } } }
@Configuration @Slf4j public class WebMvcConfiguration extends WebMvcConfigurationSupport { @Autowired private JwtTokenAdminInterceptor jwtTokenAdminInterceptor;//注入拦截器对象 /** * 注册自定义拦截器 * @param registry */ protected void addInterceptors(InterceptorRegistry registry) { log.info("开始注册自定义拦截器..."); registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns("/admin/**")//作用于哪些路径 .excludePathPatterns("/admin/employee/login");//不拦截哪些路径 } }
9.异常处理
9.1异常出现的常见位置和原因:
-
框架内部抛出的异常:因使用不合规导致
-
数据层抛出的异常:因外部服务器故障导致(如:服务器访问超时)
-
业务层抛出的异常:因业务逻辑书写错误导致(如:用户传了一个0过来)
-
表现层抛出的异常:因数据收集、校验等规则导致
-
工具类抛出的异常:因工具类书写不规范
9.2异常处理的思路
因为异常可能不处于同一层,所以出现异常都往上抛,都抛出到表现层,然后统一处理
9.3异常处理器:
可以集中的、统一的处理项目中出现的异常
-
异常处理器的使用方法
- 先创建一个异常处理类,然后类上添加@RestControllerAdvice注解
- 然后写异常处理的方法,方法的参数是Exception(可以是Exception的子类)的class对象表示处理的异常。
- 方法上加@ExceptionHandler告诉方法你处理的是哪一种异常。
- 方法体中处理过后也要返回Result对象。
-
使用案例
@RestControllerAdvice @Slf4j public class GlobalExceptionHandler { //使用方法一 @ExceptionHandler public Result exceptionHandler(BaseException ex){ log.error("异常信息:{}", ex.getMessage()); return Result.error(ex.getMessage()); } //使用方法二 @ExceptionHandler(Excecption.class) public Result exceptionHandler(Exception ex){ return Result.error(ex.getMessage()); } }
9.4项目异常分类:
-
业务异常:规范的用户行为出现的异常和不规范的用户行为出现的异常
处理方法:发送对象的消息给用户,提醒规范操作
-
系统异常:项目运行过程中可预计且无法避免的异常
处理方法:发送固定的消息给用户,安抚用户,然后将消息发送给运维人员,提醒维护,同时记录日志
-
其他异常:编程人员未预期到的异常
处理方法:发送固定消息传递给用户,安抚用户,然后将消息发送给编程人员,提醒维护,同时记录日志
9.5自定义异常的方法:
-
先创建一个异常类,继承RuntimeException【因为他的异常都可以自动往上抛】,
-
然后添加一个code属性作为异常编号,方便后面识别异常类型。
-
然后直接快捷键重写构造方法【需要几种生成几种】
-
使用案例:
public class BaseException extends RuntimeException { //这里给了两个构造器 public BaseException() { } public BaseException(String msg) { super(msg); } }
9.6自定义异常的使用方法:
直接抓异常throw自定义异常,然后配合异常处理器处理异常【给用户发消息,通知维护人员,记录日志】