SpringMVC
- 搭建SpringMVC环境(一)创建项目、依赖、配置
- 搭建SpringMVC环境(二)解决静态资源访问不了问题,方案1
- 搭建SpringMVC环境(二)解决静态资源访问不了问题,方案2
- 控制器方法返回值
- 交互JSON数据:@RequestBody与@ResponseBody注解
- Restful 风格的 URL(1)简介
- Restful 风格的 URL(2)基于HiddenHttpMethodFilter 的示例
- SpringMVC 实现文件上传
- SpringMVC异常处理(一)异常处理思路
- SpringMVC异常处理(二)异常处理实现
- SpringMVC拦截器(一)拦截器作用
- SpringMVC拦截器(二)自定义拦截器
- SpringMVC拦截器(三)多个拦截器执行
搭建SpringMVC环境(一)创建项目、依赖、配置
步骤
- 创建项目、依赖
- 配置web.xml
- 配置springMVC.xml
- 编写index.html
- 测试
实现
1、创建项目、依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xxx.xxx</groupId>
<artifactId>springmvc02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
</dependencies>
</project>
2、配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--
配置SpringMVC的前端控制器:DispatcherServlet
1.关于拦截请求路径:
方式1: *.do 表示拦截所有以.do作为后缀的请求
缺点:路径不够简洁;不支持restful请求
方式2: / 表示拦截所有请求,不包括jsp请求
注意:不能写/*
2. 如果拦截请求路径是/ ,会导致静态资源访问不了这个问题
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.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>
3、配置springMVC.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--1. 注解扫描-->
<context:component-scan base-package="xxx.xxx"/>
<!--2. 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--跳转路径的前缀-->
<property name="prefix" value="/pages/"/>
<!--跳转路径的后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--3. 注解驱动-->
<mvc:annotation-driven/>
</beans>
4、编写index.html
5、测试
搭建SpringMVC环境(二)解决静态资源访问不了问题,方案1
问题
springMVC中拦截请求路径是“/”, 导致静态资源index.html访问不了。
分析
为什么导致这种情况的发生呢?
1、查看tomcat/conf/web.xml
2、自己项目配置的DispatcherServlet拦截的路径也是/, 会覆盖DefaultServlet。
3、DefaultServlet的作用: tomcat提供的默认sevlet,专门用于处理静态资源。
解决
让指定的静态资源找default名称对应的servlet即可:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!--
配置SpringMVC的前端控制器:DispatcherServlet
1.关于拦截请求路径:
方式1: *.do 表示拦截所有以.do作为后缀的请求
缺点:路径不够简洁;不支持restful请求
方式2: / 表示拦截所有请求,不包括jsp请求
注意:不能写/*
2. 如果拦截请求路径是/ ,会导致静态资源访问不了这个问题
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.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>
<!--
如果自己项目的servlet拦截的请求路径是/,会覆盖tomcat中的DefaultServlet,
而这个servlet是用来处理静态资源的。
所以,DispatcherSevlet拦截请求路径是/,会导致静态资源访问不了。
解决: 让静态资源去找DefaultServlet处理即可
-->
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.html</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>*.js</url-pattern>
</servlet-mapping>
</web-app>
搭建SpringMVC环境(二)解决静态资源访问不了问题,方案2
解决1:不推荐
<!--方式1:mapping 映射路径,就是访问路径。location 访问路径对应的真实路径。-->
<mvc:resources mapping="/pages/**" location="/pages/"/>
<mvc:resources mapping="/index.html" location="/index.html"/>
解决2:推荐 <mvc:default-servlet-handler/>
springmvc.xm添加如下配置:
<!--4. 把静态资源交给默认sevlet处理(DefaultServlet), 解决拦截请求路径是/静态资源无法访问的问题。-->
<mvc:default-servlet-handler/>
default-servlet-handler 将在 SpringMVC 上下文中定义一个DefaultServletHttpRequestHandler,它会对进入 DispatcherServlet 的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由 WEB 应用服务器默认的Servlet 处理。如果不是静态资源的请求,才由 DispatcherServlet 继续处理。
一般 WEB 应用服务器默认的 Servlet 的名称都是 default。若所使用的 WEB 服务器的默认 Servlet 名称不是 default,则需要通过 default-servlet-name 属性显式指定。
控制器方法返回值
说明
SpringMVC控制器方法参数,有哪些写法?
- 简单类型作为方法参数
- 对象作为方法参数
- servletApi作为方法参数
- …
SpringMVC控制器方法返回值?
- 返回String字符串
- 返回void
- 返回ModelAndView对象
- 返回任意对象。(配置@ResponseBody注解)
- Map
- Model
- ModelMap
示例
第一步:编写控制器
@Controller
public class ReturnController {
/**
* 控制器方法的返回值(1)返回字符串 A 普通字符串
*/
@RequestMapping("returnString")
public String returnString(){
return "success"; // 转发
}
/**
* 控制器方法的返回值(1)返回字符串 B forward关键字,实现转发
* 应用场景:
* 转发后的资源与视图解析器配置的前缀后缀不一致时候使用。
*/
@RequestMapping("forward")
public String forward(){
// 需求:转发到页面 http://localhost:8080/index.html
return "forward:/index.html";
}
/**
* 控制器方法的返回值(1)返回字符串 C redirect关键字,实现重定向
* 应用场景:
* 转发后的资源与视图解析器配置的前缀后缀不一致时候使用。
*/
@RequestMapping("redirect")
public String redirect(){
// 需求:重定向到页面 http://localhost:8080/index.html
return "redirect:/index.html";
}
/**
* 控制器方法的返回值(2)返回void
*/
@RequestMapping("returnVoid")
public void returnVoid(HttpServletResponse response) throws IOException {
// 使用servletapi实现重定向
response.sendRedirect("/index.html");
}
/**
* 控制器方法的返回值(3)返回返回ModelAndView
* ModelAndView
* 1. Model:可以往request域中存储数据
* 2. View:也可以设置转发的路径名称
*/
@RequestMapping("mv")
public ModelAndView mv() {
ModelAndView mv = new ModelAndView();
// 往模型中存储数据(自动存储到request域中)
mv.addObject("cn","China");
// 设置转发的路径名称 (会匹配视图解析器配置的前缀和后缀)
mv.setViewName("success");
return mv;
}
}
@Controller
@RequestMapping("/order")
public class OrderController {
//A. 控制器方法中传入servletApi,保存数据
@RequestMapping("/save1")
public String save1(HttpServletRequest request){
request.setAttribute("cn","China");
return "success";
}
//B. 通过控制器方法的返回值ModelAndView
@RequestMapping("/save2")
public ModelAndView save2(){
// 传入参数:转发的页面名称(前缀后缀在视图解析器中配置)
ModelAndView mv = new ModelAndView("success");
mv.addObject("cn","China2");
return mv;
}
//C. 控制器方法中通过Map参数存储数据到request域
@RequestMapping("/save3")
public String save3(Map<String,Object> map){
// 存储数据到request域
map.put("cn","China3");
return "success";
}
//D. 控制器方法中通过Model、ModelMap参数,存储数据到request域
@RequestMapping("/save4")
public String save4(Model model){
// 存储数据到request域
model.addAttribute("cn","China4");
return "success";
}
//E
@RequestMapping("/save5")
public String save5(ModelMap model){
// 存储数据到request域
model.addAttribute("cn","China5");
return "success";
}
}
第二步:页面
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>success</title>
</head>
<body>
success!!! <br>
<h3>从request请求域中获取数据(`page`<`request`<`session`<`application`):</h3>
<%--requestScope 是一个关键字,只会从request请求域中找数据--%>
${requestScope.cn}
</body>
</html>
访问测试:
交互JSON数据:@RequestBody与@ResponseBody注解
介绍
@RequestBody
作用:在处理器方法形参上使用,把请求的json格式数据,转换成java对象。
@ResponseBody
作用:在处理器方法返回值上使用,或者方法上使用。把响应的java对象,转换成json格式数据。
注意
- @RequestBody、@ResponseBody实现对象与json之间的互换
- @RequestBody 把请求的json字符串,自动转换为对象
- @ResponseBody 把方法返回的对象,自动转json
- 如果要用这两个注解,必须要先引入jackson支持包
示例
步骤:
- 添加jackson支持包
- 引入jQuery类库
- 编写ajaxRequest.html,发送ajax异步请求
- 编写JsonController,使用@RequestBody、@ResponseBody
- 测试
演示:
-
添加jackson支持包
<!--添加jackson支持包--> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.0</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> <version>2.9.0</version> </dependency>
-
引入jQuery类库
-
编写ajaxRequest.html,发送ajax异步请求
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>ajax</title> <script src="/js/jquery-3.3.1.min.js" type="text/javascript"></script> <script> $(function(){ // 给按钮绑定点击事件 $("#ajaxPost").click(function(){ // 发送异步请求,传入json字符串 $.ajax({ url : "/requestJson", type: "post", dataType : "json", // 请求数据:注意这里一定要指定json字符串,要带上引号 data : '{"id":100,"name":"小明","money":100}', // 这里的请求类型也要指定 contentType:"application/json;charset=UTF-8", success:function(result){ console.log(result); alert(result.id + "," + result.name + "," + result.money); } }) }) }) </script> </head> <body> <h2>RequestBody获取请求JSON格式数据 & ResponseBody自动把对象转json并响应</h2> <button id="ajaxPost">测试ajax请求json与响应json</button> </body> </html>
-
编写JsonController,使用@RequestBody、@ResponseBody
步骤1: 编写Account对象
public class Account { private Integer id; private String name; private Double money; // 省略get、set }
步骤2: 编写控制器
/** * 演示通过@RequestBody、@ResponsBody实现对象与json的转换 */ @Controller public class JsonController { /** * 请求地址:/requestJson * 请求的json字符串:'{"id":100,"name":"小明","money":100}' * * @RequestBody 把请求的json字符串,自动转换为java对象。json的key与对象属性要一致。 * @ResponseBody 把方法返回的对象,自动转换为json * 注意: * 要先引入jackson支持包。 */ @ResponseBody @RequestMapping("/requestJson") public Account requestJson(@RequestBody Account account){ // 返回的对象 account.setName("xiaohong"); account.setMoney(1d); return account; } }
-
测试
小贴士
1、@PostMapping = @RequestMapping + RequestMethod.POST
@ResponseBody
//@RequestMapping(value = "/requestJson",method = RequestMethod.POST)
@PostMapping("/requestJson") // 同上
public Account requestJson(@RequestBody Account account){
// 返回的对象
account.setName("qiuqiu");
account.setMoney(1d);
return account;
}
2、@RestController
//@Controller
@RestController //相当于:@Controller + @ResponseBody
// 当前类的所有方法都必须要返回json
public class JsonController {
@PostMapping("/requestJson") // 同上
public Account requestJson(@RequestBody Account account){
// 返回的对象
account.setName("qiuqiu");
account.setMoney(1d);
return account;
}
}
Restful 风格的 URL(1)简介
介绍
REST全称是Representational State Transfer,中文意思是表现层状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。
REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。
简单来说,
-
restful就是一个url地址。是一种url地址的编写风格。
-
restful风格的url地址的特点:更简洁、更易于实现缓存、更有层次感。
-
举例:
应用实战:
使用规则:
优点
RESTful优点:
- 结构清晰
- 符合标准
- 易于理解
- 扩展方便
- 地址简洁
- 应用广泛
需求
需求:如何实现一个url地址,对应后台多个方法?
传统的方式?
http://localhost:8080/user?method=get 查询
http://localhost:8080/user?method=save 添加
http://localhost:8080/user?method=update 修改
http://localhost:8080/user?method=delete 删除
基于restful风格的url实现?
地址:http://localhost:8080/user
查询
1. 后台方法:get()
2. 请求方式:get
3. 后台只处理get请求
添加
1. 后台方法:save()
2. 请求方式:post
3. 后台只处理post请求
修改
1. 后台方法:update()
2. 请求方式:put
3. 后台只处理put请求
删除
1. 后台方法:delete()
2. 请求方式:delete
3. 后台只处理delete请求
http支持的7种请求方式:
Restful 风格的 URL(2)基于HiddenHttpMethodFilter 的示例
引入
- 如果要用restful风格的url地址,可以直接用,不用做任何操作或配置等。
- 配置HiddenHttpMethodFilter与restful没有关系, 主要是为了让页面支持put、delete请求而已。
- 总结两点问题:
- 使用HiddenHttpMethodFilter过滤请求,主要是为了让页面支持put、delete请求
- put、delete请求, 只支持返回json类型的数据
示例
第一步:配置HiddenHttpMethodFilter 过滤器,让页面支持put、delete请求。
<!--
配置HiddenHttpMethodFilter 过滤器,让页面支持put、delete请求。
1. 页面要有一个form表单,提交方式必须是post
2. 表单中要有一个隐藏域(_method),隐藏域就是真正的提交方式
-->
<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>
第二步:编写restfulTest.html页面,发送restful风格的url请求。
<input type="hidden" name="_method" value="get">
这里隐藏域的值,表示具体的请求方式。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>基于restful风格的请求,页面发送:get、post、put、delete请求</title>
</head>
<body>
<form action="http://localhost:8080/user/100" method="get">
<input type="submit" value="get 请求">
</form>
<form action="http://localhost:8080/user/200" method="post">
<input type="submit" value="post 请求">
</form>
<form action="http://localhost:8080/user/300" method="post">
<!--添加隐藏域,名称是_method(固定的), 值就是真正的提交方式-->
<input type="hidden" name="_method" value="put">
<input type="submit" value="put 请求">
</form>
<form action="http://localhost:8080/user/400/500" method="post">
<!--添加隐藏域,名称是_method(固定的), 值就是真正的提交方式-->
<input type="hidden" name="_method" value="delete">
<input type="submit" value="delete 请求">
</form>
</body>
</html>
第三步:编写UserController,处理同一个地址的不同提交方式的请求
@PathVariable
用于获取restful请求中占位符参数值,赋值给方法的参数。
@Controller
public class RestController {
/**
* 只处理get请求,一般执行查询操作。
* 请求地址:http://localhost:8080/user/100
* 路径映射:@GetMapping("/user/{id}")
* 方法参数:
* @PathVariable("id")
* 1. 获取rest请求中占位符参数值
* 2. value参数,对应路径中的占位符
*/
@GetMapping("/user/{id}")
public String list(@PathVariable("id") Integer id){
System.out.println("查询:" + id);
return "success";
}
/**
* 只处理post请求,一般执行添加操作。
* http://localhost:8080/user/200
*/
@PostMapping("/user/{id}")
public String add(@PathVariable("id") Integer id){
System.out.println("添加:" + id);
return "success";
}
/**
* 只处理put请求,一般执行修改操作。
* 注意:
* put与delete请求,必须返回json字符串,不能跳转。
*/
@PutMapping("/user/{id}")
@ResponseBody // 返回json字符串
public String update(@PathVariable("id") Integer id){
System.out.println("修改:" + id);
return "success";
}
/**
* 只处理delete请求,一般执行删除操作。
* 注意:
* put与delete请求,必须返回json字符串,不能跳转。
*
* http://localhost:8080/user/300/400
*/
@DeleteMapping("/user/{userId}/{deptId}")
@ResponseBody
public String delete(
@PathVariable("userId") Integer userId,
@PathVariable("deptId") Integer deptId){
System.out.println("删除:" + userId + "," + deptId);
return "{\"id\":100,\"name\":\"jack\",\"money\":9999}";
}
}
第四步:测试
SpringMVC 实现文件上传
原理
springMVC文件上传,就是用到了Apache的文件上传组件实现。简化了过程。
实现
-
添加依赖
<!--引入文件上传的支持包--> <dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.3.3</version> </dependency>
-
编写uploadFile.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>文件上传</title> </head> <body> <form method="post" enctype="multipart/form-data" action="/upload"> 文件:<input type="file" name="imgFile"> <br> <input type="submit" value="文件上传"> </form> </body> </html>
-
编写UploadController.java
@Controller public class UploadController { /** * 文件上传 * 请求参数:<input type="file" name="imgFile"> * 上传需求: * 1. 上传到target/upload目录 * 2. 要保证文件名唯一,文件名 = uuid + 文件后缀 * 如何实现? * 1. 控制器方法通过MultipartFile接收上传的文件对象 * 2. springmvc.xml中配置文件上传解析器 */ @RequestMapping("/upload") public String upload(MultipartFile imgFile, HttpServletRequest request) throws Exception { // 1. 获取上传的目录的路径 // 调试观察变量值:springmvc02/target/springmvc02-1.0-SNAPSHOT/upload String realPath = request.getServletContext().getRealPath("/upload"); // 2. 每天生成一个新的目录,目录名称就是当前的日期 String now = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); // 3. 创建目录对象 File file = new File(realPath,now); if (!file.exists()) { // 如果目录不存在,就创建目录以及子目录 file.mkdirs(); } // 4. 获取原生的文件名, 上传的文件名: xxx.jpg String fileName = imgFile.getOriginalFilename(); // 5. 处理文件名唯一 fileName = UUID.randomUUID().toString() + fileName.substring(fileName.lastIndexOf(".")); // 6. 最关键的代码:文件上传,上传到指定的目录中 // 语义:把imgFile这个文件流,写入到file目录下的fileName文件中。 imgFile.transferTo(new File(file,fileName)); // 7. 文件上传结束,转发到成功页面 return "success"; } }
-
配置springMVC.xml,配置文件上传解析器
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!--1. 注解扫描--> <context:component-scan base-package="xxx.xxx"/> <!--2. 配置视图解析器--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <!--跳转路径的前缀--> <property name="prefix" value="/pages/"/> <!--跳转路径的后缀--> <property name="suffix" value=".jsp"/> </bean> <!--3. 注解驱动--> <mvc:annotation-driven/> <!--4. 把静态资源交给默认sevlet处理(DefaultServlet), 解决拦截请求路径是/静态资源无法访问的问题。--> <mvc:default-servlet-handler/> <!--5. 配置文件上传解析器;注意:这里的id固定,不能写其他名称,springmvc根据对象名称找解析器的。--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!--设置允许上传的文件最大值:10M (10 * 1024 * 1024)--> <property name="maxUploadSize" value="10485760"/> </bean> </beans>
SpringMVC异常处理(一)异常处理思路
Java异常分类
|–Throwable
|-- Error
|-- Exception 检查异常(编译异常)
|–RuntimeException 运行时期异常
检查异常: 会给调用者带来麻烦。调用者必须处理异常,不处理异常程序无法继续。
运行时期异常:调用者可以处理异常,也可以不处理异常。更灵活。
项目中如何处理异常
在三层架构中,如果Dao持久层有异常可以抛出到调用方(Service), Service有异常可以抛出到Controller,但是Controller有异常,不建议抛出到客户端(用户), 因为用户看不懂异常, 无法进行处
理会导致用户体验非常差。
异常处理的原则:出现异常要给用户友好提示,自动跳转到友好提示页面。
-
代码层面:
dao 抛异常
service 抛异常
web 处理异常
能够控制器每个方法都写try…catch… 太麻烦!
-
异常处理方案
2.1 表现层,处理异常,写try.catch 不推荐
2.2 自定义异常过滤器器 (处理所有的异常)
举例如下:
public class ExceptionFilter implements Filter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){ try{ chain.doFilter(request, response); }catch(Exception e){ // 异常处理 } } }
2.3 使用框架自带的异常处理机制。更加简单。
SpringMVC异常处理(二)异常处理实现
实现步骤
通过SpringMVC自定义异常类,可以自动捕获到控制器中的所有方法的异常。
- 写控制器 (模拟异常,int i=1/0;)
- 测试
- 如果没有用springMVC的异常处理,传统的异常处理如何实现?
- 改为springMVC提供的异常处理机制实现异常处理
实现
1、写控制器 (模拟异常,int i=1/0;)
@Controller
public class UserController {
@RequestMapping("save")
public String save(HttpServletRequest request){
System.out.println("保存用户...");
// 模拟异常
int i= 1/0;
return "success";
}
}
2、测试: 直接打印异常到浏览器
3、如果没有用springMVC的异常处理,传统的异常处理如何实现?
@Controller
public class UserController {
@RequestMapping("save")
public String save(HttpServletRequest request){
try {
System.out.println("保存用户...");
// 模拟异常
int i= 1/0;
} catch (Exception e) {
e.printStackTrace();
// 保存错误信息,进入错误页面!
request.setAttribute("error","对不起,系统忙,请稍后再试!或者联系管理员电话:xxxxxx");
return "error";
}
return "success";
}
}
4、改为springMVC提供的异常处理机制实现异常处理
修改控制器,不写try…catch,希望出现异常,自动跳转到异常页面:
@Controller
public class UserController {
@RequestMapping("save")
public String save(HttpServletRequest request){
System.out.println("保存用户...");
// 模拟异常
int i= 1/0;
return "success";
}
}
自定义全局异常:
/**
* 自定义全局异常:
* 1. 写一个类实现HandlerExceptionResolver接口即可;
* 2. 创建全局异常类,且加入容器, 自动生效
* 3. 当控制器方法(service、dao)出现异常时候,自动来到全局异常处理类的resolveException()处理异常的方法
*/
@Component
public class CustomException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(
HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
// 打印异常
ex.printStackTrace();
ModelAndView mv = new ModelAndView();
// 存储数据
mv.addObject("error","对不起,系统忙,请稍后再试!或者联系管理员电话:120");
// 设置转发的页面名称
mv.setViewName("error");
return mv;
}
}
SpringMVC拦截器(一)拦截器作用
过滤器作用
-
拦截请求
-
可以拦截所有的请求(/*):jsp、html、css、js、img、servlet、controller
-
作用
处理请求之前做一些初始化操作
请求处理完成后,也可以做一些其他操作。如:释放资源。
-
是servlet的技术,应用范围比较广
拦截器
什么是拦截器
springmvc框架中的拦截器,相当于web阶段学习的过滤器(filter),可以实现前置增强和后
置增强功能。在springmvc框架中,拦截器可以对处理器方法执行预处理(前置增强),和执行
后处理(后置增强)。
拦截器的作用
- Spring MVC 的处理器拦截器类似于Servlet 开发中的过滤器Filter
- 用于对处理器进行预处理 和 后处理。
- 用户可以自己定义一些拦截器来实现特定的功能
拦截器与过滤器区别
-
springmvc的拦截器是属于springmvc的技术,只能在使用springmvc的技术上,才可以使用其拦截器
-
拦截的请求: 主要是拦截springmvc的请求
-
他们的作用基本上是一样的
SpringMVC拦截器(二)自定义拦截器
使用步骤
- 写一个普通java类,实现接口HandlerInterceptor
- springMVC中,配置拦截器
示例代码
1、写一个普通java类,实现接口HandlerInterceptor
/**
* 自定义拦截器
*/
@Component
public class DemoInterceptor implements HandlerInterceptor {
/**
* preHandle()
* 1. 请求的预处理操作,在执行控制器方法之前先执行
* 2. 返回值
* true 默认返回true, 表示放行,执行下一个拦截或控制器的方法
* false 不放行,不执行后面所有。
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("1. DemoInterceptor.preHandle()");
return true;
}
/**
* postHandle()
* 1. 当preHandle()返回true的时候才执行
* 2. 当控制器方法正常执行结束后才执行. 控制器方法有异常就不执行
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("3. DemoInterceptor.postHandle()");
}
/**
* afterCompletion
* 1. 当preHandle()返回true的时候才执行
* 2. 最后执行的方法,不管控制器方法时候有异常都执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("4.DemoInterceptor.afterCompletion()");
}
}
2、springMVC中,配置拦截器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--1. 注解扫描-->
<context:component-scan base-package="xxx.xxx"/>
<!--2. 配置视图解析器-->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--跳转路径的前缀-->
<property name="prefix" value="/pages/"/>
<!--跳转路径的后缀-->
<property name="suffix" value=".jsp"/>
</bean>
<!--3. 注解驱动-->
<mvc:annotation-driven/>
<!--4. 把静态资源交给默认sevlet处理(DefaultServlet), 解决拦截请求路径是/静态资源无法访问的问题。-->
<mvc:default-servlet-handler/>
<!--5. 配置文件上传解析器;注意:这里的id固定,不能写其他名称,springmvc根据对象名称找解析器的。-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置允许上传的文件最大值:10M (10 * 1024 * 1024)-->
<property name="maxUploadSize" value="10485760"/>
</bean>
<!--6. 拦截器的配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截的路径. /** 拦截所有路径及其子路径-->
<mvc:mapping path="/**"/>
<!--注入自定义的拦截器-->
<ref bean="demoInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
测试
观察执行顺序:
执行流程
SpringMVC拦截器(三)多个拦截器执行
实现步骤
1、编写自定义的拦截器类
@Component
public class DemoInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("DemoInterceptor2.preHandle()");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("DemoInterceptor2.postHandle()");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("DemoInterceptor2.afterCompletion()");
}
}
2、配置拦截器
<!--6. 拦截器的配置-->
<mvc:interceptors>
<mvc:interceptor>
<!--拦截的路径. /** 拦截所有路径及其子路径-->
<mvc:mapping path="/**"/>
<!--注入自定义的拦截器-->
<ref bean="demoInterceptor"/>
</mvc:interceptor>
<!--配置第二个拦截器-->
<mvc:interceptor>
<!--拦截指定的路径-->
<mvc:mapping path="/save"/>
<ref bean="demoInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
#### 代码
```java
@Controller
@RequestMapping("/order")
public class OrderController {
//A. 控制器方法中传入servletApi,保存数据
@RequestMapping("/save1")
public String save1(HttpServletRequest request){
request.setAttribute("cn","China");
return "success";
}
//B. 通过控制器方法的返回值ModelAndView
@RequestMapping("/save2")
public ModelAndView save2(){
// 传入参数:转发的页面名称(前缀后缀在视图解析器中配置)
ModelAndView mv = new ModelAndView("success");
mv.addObject("cn","China2");
return mv;
}
//C. 控制器方法中通过Map参数存储数据到request域
@RequestMapping("/save3")
public String save3(Map<String,Object> map){
// 存储数据到request域
map.put("cn","China3");
return "success";
}
//D. 控制器方法中通过Model、ModelMap参数,存储数据到request域
@RequestMapping("/save4")
public String save4(Model model){
// 存储数据到request域
model.addAttribute("cn","China4");
return "success";
}
//E
@RequestMapping("/save5")
public String save5(ModelMap model){
// 存储数据到request域
model.addAttribute("cn","China5");
return "success";
}
}