文章目录
概述
- 对于任何优秀的MVC框架,都会提供一些通用的操作,如请求数据的封装、类型转换、数据校验、解析上传的文件、防止表单的多次提交等。早期的MVC框架将这些操作都写死在核心控制器中,而这些常用的操作又不是所有的请求都需要实现的,这就导致了框架的灵活性不足,可扩展性降低。
- Spring MVC中的拦截器 (
Interceptor
)类似于 Servlet 中的过滤器(Filter
),主要用于拦截用户的请求并作相应的处理。例如:权限验证、记录请求信息的日志、判断用户是否登录等。 - Spring MVC拦截器是可插拔式的设计,需要某一功能拦截器,只需在配置文件中应用该拦截器即可;如果不需要这个功能拦截器,只需在配置文件中取消应用该拦截器。
要使用拦截器需要对拦截器类进行定义和配置。有两种实现方式
- 实现
HandlerInterceptor
接口 - 实现
WebRequestInterceptor
接口【两个使用时一样的】
以第一种方式为练习。
第一步:实现HandlerInterceptor
接口
该接口的位置:org.springframework.web.servlet
包中,定义了三个 default
方法,它们都有方法体。自定义的拦截器类实现该接口的时候需要实现这三个方法。【注意实现方法的时候方法修饰符是 public, 粘贴过来记得改】
boolean preHandle()
- 该方法在执行控制器方法之前执行。返回值为Boolean类型,如果返回false,表示拦截请求,不再向下执行,如果返回true,表示放行,程序继续向下执行(如果后面没有其他Interceptor,就会执行controller方法)。所以此方法可对请求进行判断,决定程序是否继续执行,或者进行一些初始化操作及对请求进行预处理。
void postHandle()
- 该方法在执行控制器方法调用之后,且在返回
ModelAndView
之前执行。由于该方法会在DispatcherServlet
进行返回视图渲染之前被调用,所以此方法多被用于处理返回的视图【处理完视图再去跳转】,可通过此方法对请求域中的模型和视图做进一步的修改。
- 该方法在执行控制器方法调用之后,且在返回
afterCompletion
- 该方法在执行完控制器之后执行,由于是在Controller方法执行完
- 毕后执行该方法,所以该方法适合进行一些资源清理,记录日志信息等处理操作。
第二步:配置拦截器
我的 ssm 项目的主体配置都是 springmvc.xml
中进行的,拦截器配置是有先后顺序的。
<mvc:interceptors>
拦截器链标签<mvc:interceptor>
拦截器标签<mvc:mapping path="/**/goods"/>
配置拦截路径/**
:是根下的全部文件 包括子目录/*
:不包括子目录
<mvc:exclude-mapping path="/**/userinfo/login"/>
:不拦截的路径,如果分不清楚可以在拦截全部的下面选出不拦截的
<!-- 配置拦截器链 -->
<mvc:interceptors>
<!-- 配置第一个拦截器 -->
<mvc:interceptor>
<!-- 拦截路径 -->
<mvc:mapping path="/**/goods"/>
<mvc:mapping path="/**/olders"/>
<mvc:mapping path="/**/userinfo"/>
<!-- 不拦截路径 -->
<mvc:exclude-mapping path="/**/userinfo/login"/>
<mvc:exclude-mapping path="/**/userinfo/doLogin"/>
<mvc:exclude-mapping path="/**/login/login"/>
<bean class="com.gpb.interceptors.LoginHandlerInterceptor"/>
</mvc:interceptor>
<!-- 后面还可以配置别的拦截器 它是有顺序的 -->
</mvc:interceptors>
登录拦截
数据准备
-
用户实体:
UserInfo
public class UserInfo implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String userName; private String userPass; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPass() { return userPass; } public void setUserPass(String userPass) { this.userPass = userPass; } }
-
用户映射文件:
UserInfoMapper接口和UserInfoMapper.xml映射文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.gpb.mapper.UserInfoMapper"> <cache /> <!-- 登录 --> <select id="login" parameterType="java.lang.String" resultType="UserInfo"> select * from userinfo where username = #{userName} </select> </mapper>
public interface UserInfoMapper { public UserInfo login(String userName); }
视图 jsp 准备
- login文件夹下面的 login.jsp
- 注意这些都是配置视图解析器之后在
WEB-INF/views
下面的操作 - el 表达式都是取的错误信息
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
</head>
<body>
<h3>用户登录</h3>${loginErr0}
<form action="${pageContext.request.contextPath}/userinfo/doLogin" method="post" >
用户名:<input type="text" name="userName" value="${old.userName}">
${loginErr}
密码:<input type="password" name="userPass" value="${old.userPass}">
${loginErr1}
<input type="submit" value="登录">
</form>
</body>
</html>
业务层
-
传输用户名到这 调用接口的方法去查询数据【条件是用户名】
-
不顺便判断密码是为了在 controller 里面返回不同的错误信息回显
@Service public class UserInfoService { @Autowired UserInfoMapper mapper = null; /** * 根据用户名判断用户是否存在 * @param userName * @return */ public UserInfo login(String userName) { UserInfo userInfo = mapper.login(userName); if (userInfo!=null) { return userInfo; } return null; } }
控制层
-
先整个跳转到
login.jsp
的controller,WEB-INF
路径不可直接在外部访问 -
尤其要注意最后跳转的路径即页面和映射的访问路径之间的区别
-
把查询到的用户信息
user
放到session
中,通过session
保存 -
old
是页面输入传递过来的数据组合的实体user
是数据库中存在的实体数据@Controller @RequestMapping("/userinfo") public class UserInfoController { @Autowired UserInfoService service = null; @RequestMapping("/login") public String login() { return "/login/login"; } @RequestMapping("/doLogin") public String doLogin(Model model ,String userName, String userPass, HttpSession session) { UserInfo user = service.login(userName); UserInfo old = new UserInfo(); old.setUserName(userName); old.setUserPass(userPass); if (user != null) { if (user.getUserPass().equals(userPass)) { session.setAttribute("user", user); return "index"; } model.addAttribute("loginErr1", "用户密码错误"); model.addAttribute("old", old); return "/login/login"; } model.addAttribute("loginErr", "用户账号错误"); model.addAttribute("old", old); return "/login/login"; } }
拦截器类
-
拦截器包
-
拦截器类:
LoginHandlerInterceptor
-
顺便可以打印测试顺序
-
session
取出存放的用户实体,为空则没有登陆,跳到登录页面,不为空则已经登录,可以正常使用,拦截器向下走public class LoginHandlerInterceptor implements HandlerInterceptor{ /** * 在controller方法之前调用 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // System.out.println("pre"); Object obj = request.getSession().getAttribute("user"); if (obj == null) { request.setAttribute("loginErr0", "您还没有登录"); request.getRequestDispatcher("/WEB-INF/views/login/login.jsp").forward(request, response); return false; } return true; } /** * 在跳转页面之前调用【业务执行完了】 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { System.out.println("return" + "之前"); } /** * 在请求结束后 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { System.out.println("controller执行结束之后"); } }
配置文件
- 前面的配置文件
测试
RESTful支持
概念:RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。
资源:REST 是面向资源的,每个资源都有一个唯一的资源定位符(URI)。每个URI代表一种资源(resource),所以URI中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以URI中的名词也应该使用复数。
请求方式:
请求方式 | 含义 |
---|---|
GET(SELECT) | 从服务器取出资源(一项或多项) |
POST(CREATE) | 在服务器新建一个资源 |
PUT(UPDATE) | 在服务器更新资源(更新完整资源) |
PATCH(UPDATE) | 在服务器更新资源, PATCH更新个别属性 |
DELETE(DELETE) | 从服务器删除资源 |
【1】查询
查询 传统 REST REST 后台接收
查询所有 http://localhost:8080/employee/list http://localhost:8080/employees @RequestMapping(value = “/employees”, method = RequestMethod.GET)
查询单个 http://localhost:8080/employee/list?id=1 http://localhost:8080/employees/1 @RequestMapping(value = “/employees/{id}”, method = RequestMethod.GET)
@ResponseBody
public Employee queryById(@PathVariable Long id) {}
【2】添加
添加 传统 REST REST 后台接收
添加 http://localhost:8080/employee/add http://localhost:8080/employees @RequestMapping(value = “/employees”, method = RequestMethod.POST)
public Employee add(@ModelAttribute(“emp”) Employee employee) {}
【3】修改
修改 传统 REST REST 后台接收
修改 http://localhost:8080/employee/update http://localhost:8080/employees @RequestMapping(value = “/employees”, method = RequestMethod.PUT)
public Employee update(@ModelAttribute(“emp”) Employee employee) {}
【4】删除
查询 传统 REST REST 后台接收
删除 http://localhost:8080/employee/delete http://localhost:8080//employees/{id} @RequestMapping(value = “/employees/{id}”, method = RequestMethod.DELETE)
@ResponseBody
public JsonResult delete(@PathVariable Long id) {}
注意:
【1】当参数非常多的时候,不建议使用参数路径方式;
【2】如果参数名非常敏感,建议使用参数路径方式,可以隐藏参数名。
-
相关注解
@RestController
由@Controller + @ResponseBody
组成(返回 JSON 数据格式)
@PathVariable
URL 中的{xxx}
占位符可以通过@PathVariable(“xxx“)
绑定到控制器处理方法的形参中
@RequestMapping
注解用于请求地址的解析,是最常用的一种注解
@GetMapping
查询请求
@PostMapping
添加请求
@PutMapping
更新请求
@DeleteMapping
删除请求
@RequestParam
将请求参数绑定到你控制器的方法参数上(是springmvc
中接收普通参数的注解)-
@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)
-
value
:参数名 -
required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。
-
defaultValue
:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值
-
-
-
HTTP响应状态码
200 OK - [GET] 服务器成功返回用户请求的数据
201 CREATED - [POST/PUT/PATCH] 用户新建或修改数据成功
202 Accepted 表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE] 用户删除数据成功
400 INVALID REQUEST - [POST/PUT/PATCH] 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的
401 Unauthorized - [*] 表示用户没有权限(令牌、用户名、密码错误)
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的
404 NOT FOUND - [*] 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的
405 Method Not Allowed 方法不允许,服务器没有该方法
406 Not Acceptable - [GET] 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)
410 Gone -[GET] 用户请求的资源被永久删除,且不会再得到的
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误
500 INTERNAL SERVER ERROR - [*] 服务器发生错误,用户将无法判断发出的请求是否成功
Spring MVC 统一异常处理
全局处理 也有两种方式
- 实现
HandlerExceptionResolver
接口,自定义异常处理器。 - 使用
HandlerExceptionResolver
接口的子类,也就是SpringMVC
提供的异常处理器。
springmvc 提供的
DefaultHandlerExceptionResolver
,默认的异常处理器。根据各个不同类型的异常,返回不同的异常视图。SimpleMappingExceptionResolver
,简单映射异常处理器。通过配置异常类和view的关系来解析异常。ResponseStatusExceptionResolver
,状态码异常处理器。解析带有@ResponseStatus
注释类型的异常。ExceptionHandlerExceptionResolver
,注解形式的异常处理器。对@ExceptionHandler
注解的方法进行异常解析。
自定义的
-
自定义异常类
package com.kafamiao.myexception; /** * 自定义的异常类 * @author Administrator * */ public class CustcomException extends RuntimeException{ private String message; public CustcomException(String message) { this.message = message; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
-
实现
HandlerExceptionResolver
接口定义一个异常处理器@Component public class CustcomExceptionResolver implements HandlerExceptionResolver{ @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { //声明自定义异常 CustcomException custException = null; if(ex!=null && ex instanceof CustcomException) { custException = (CustcomException) ex; } else { custException = new CustcomException(ex.getMessage()); } //转向出错页面 ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("message", custException.getMessage()); modelAndView.setViewName("err/error"); return modelAndView; } }
-
测试
@RequestMapping("/findall") public String findAll(Model model) { if(0==0) { throw new CustcomException("程序员主动抛出异常,查询数据出错了。"); } //用业务类调用mapper List<Goods> goodslist= service.findAll(); model.addAttribute("goodslist", goodslist); return "goods/findall"; }
-
异常现实页
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> 出错原因:${message} </body> </html>
注解
-
@ExceptionHandler
注解用来将一个方法标注为异常处理方法。该注解中只有一个可选的属性value
,是一个Class<?>
数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。被该注解修饰的方法的返回值为异常处理后的跳转页面,其返回值可以是ModelAndView、String,或void
;方法名随意,方法的参数可以是Exception
及其子类对象、Model、HttpServletRequest、HttpServletResponse
等。系统会自动为这些方法参数赋值。@ExceptionHandler
注解处理异常的作用域:单个类,只针对当前Controller。/** * 基于注解的异常处理 */ @Controller public class ExceptionController { @RequestMapping(value = "/exception1") public String exception1() { // 模拟出现异常 System.out.println(10 / 0); return "success"; } @RequestMapping(value = "/exception2") public void exception2() throws CustomException { // 模拟出现异常 throw new CustomException("我抛出了一个异常!!!"); } //处理自定义异常 @ExceptionHandler({CustomException.class, ArithmeticException.class}) public String exceptionHandler1(Exception e, Model model) { // 打印错误信息 System.out.println(e.getMessage()); e.printStackTrace(); // 将错误数据存入请求域 model.addAttribute("exception", e); return "show-annotation-message"; } }
-
注意:如果在Controller中单独使用这个注解是有缺陷的,就是不能够全局处理异常,因为进行异常处理的方法必须与出错的方法在同一个Controller里面,也就是说每个
Controller
类中都要写一遍,所以实用性不高。 -
解决方案:可以将处理异常的信息抽取出来放在一个
BaseController
,然后对需要处理异常的Controller继承该类即可。public class BaseController { //处理自定义异常 @ExceptionHandler({CustomException.class, ArithmeticException.class}) public String exceptionHandler1(Exception e, Model model) { // 打印错误信息 System.out.println(e.getMessage()); e.printStackTrace(); // 将错误数据存入请求域 model.addAttribute("exception", e); return "show-annotation-message"; } }
- 但是还是存在同样的问题,每个类都得继承它,可见这种方式同样不可取,所以一般使用下面这种方式:
@ControllerAdvice
和@ExceptionHandle
注解配合使用。
- 但是还是存在同样的问题,每个类都得继承它,可见这种方式同样不可取,所以一般使用下面这种方式:
-
用 @ControllerAdvice+@ ExceptionHandler
注解(推荐)
-
@ExceptionHandler
注解标注的异常处理方法必须与出错的方法在同一个Controller里面,所以这种方式是只对应单个Controller类。那么此时有一种更好的解决方案:可以使用@ControllerAdvice+@ExceptionHandler
注解来解决,这个是 Spring 3.2 带来的新特性。 两者一起使用的作用域:全局异常处理,针对全部Controller中的指定异常类
/** * 基于注解的异常处理 @ControllerAdvice+@ExceptionHandler */ // 这个注解表示当前类是一个异常映射类 @ControllerAdvice public class MyException { // 在@ExceptionHandler注解中指定异常类型 @ExceptionHandler(value = {CustomException.class, ArithmeticException.class}) public ModelAndView exceptionMapping(Exception exception) {// 方法形参位置接收SpringMVC捕获到的异常对象 // 可以将异常对象存入模型;将展示异常信息的视图设置为逻辑视图名称 ModelAndView modelAndView = new ModelAndView(); modelAndView.addObject("exception", exception); modelAndView.setViewName("show-annotation-message"); // 打印一下信息 System.out.println(exception.getMessage()); return modelAndView; } }