SpringMVC03_校验和拦截器

以下代码全过程在上篇

一、SpringMVC 校验

​ 举一个简单的例子,在登陆时我们要检验用户名是否输入、密码是否合法。

(一)引入依赖框架

​ 在 Spring-MVC 中我们需要添加 Hibernate 的 Validator 检验框架,注意下面的版本号,615Final 对应的应该是 import javax.validation.constraints.NotNull 这个类,之前使用了 8.0.0 Final 结果找不到类。

Error creating bean with name 'bookController': Unsatisfied dependency expressed through field 'bookService'; nested exception is org.springframework.beans.factory.CannotLoadBeanClassException: Error loading class [org.springframework.validation.beanvalidation.LocalValidatorFactoryBean] for bean with name 'validator' defined in class path resource [spring-mvc.xml]: problem with class file or dependent class; nested exception is java.lang.NoClassDefFoundError: javax/validation/ValidatorFactory
<!-- 引入 hibernate-validator 的校验-->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>

(二)配置开启校验器

​ 此外还需要配置检验器和对应的消息资源,并在 mvc:annotation-driven 中启动校验器。

	<!-- 配置注解方式的映射器、适配器 -->
    <mvc:annotation-driven validator="validator"/>

	<!-- 配置服务端校验器 -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <!-- 配置校验使用的资源文件 -->
        <property name="validationMessageSource" ref="messageSource"/>
    </bean>
    <!-- 配置消息资源类 -->
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <!-- 配置 list 集合,用于指定要加载的 properties 属性文件 -->
            <list>
                <value>classpath:errorMessage</value>
            </list>
        </property>
        <property name="fileEncodings" value="UTF-8" />
        <property name="cacheSeconds" value="120"/>
    </bean>

​ 所对应的 errMessage 写成配置文件

user.userName.isNull = userName = null
user.password.isNull = password = null

(三)添加校验规则

​ 对于需要验证的 model 加上验证规则,在这里使用的是 NotNull

package com.ls.bookmanager.model;
@Data
@Accessors(chain = true)
public class User {
    @NotNull(message = "{user.userName.isNull}")
    private String userName;
    @NotNull(message = "{user.passwor.isNull}")
    private String password;
}

(四)获取校验信息

​ 如果想获取校验器抛出的错误信息,需要在 Controller 层的处理方法中加入@Validated 注解,注解定义要进行 校验的 pojo 前面,同时需要在 pojo 后面定义 BindingResult 参数。错误消息就是通过 BindingResult 参数进行获取。校验注解@Validated 和 BindingResult bindingResult 是配对出现,并且形参顺序是固定的,一前一后。

package com.ls.bookmanager.controller;
@Controller
@RequestMapping("/user")
public class UserController {
    @RequestMapping("/logIn.action")
    @ResponseBody
    public Map logIn(@Validated User user, BindingResult errResult) {
        System.out.println(user);
        Map<String, Object> result = new HashMap<>();
        if (errResult.hasErrors()) {
            List<ObjectError> allErrors = errResult.getAllErrors();
            List<String> mesgList = new ArrayList<>();
            for (ObjectError error : allErrors) {
                mesgList.add(error.getDefaultMessage());
            }
            result.put("error", mesgList);
            return result;
        }
        result.put("message", "success");
        result.put("code", 200);
        return result;
    }
}

(五)模拟传参

​ 还是使用 Postman 模拟传给 username 和 password

image-20230503162141615

(六)检验规则

​ @Null 被注释的元素必须为 null

​ @NotNull 被注释的元素必须不为 null

​ @AssertTrue 被注释的元素必须为 true

​ @AssertFalse 被注释的元素必须为 false

​ @Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

​ @Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

​ @DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值

​ @DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max=, min=) 被注释的元素的大小必须在指定的范围内

​ @Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内

​ @Past 被注释的元素必须是一个过去的日期

​ @Future 被注释的元素必须是一个将来的日期

​ @Pattern(regex=,flag=) 被注释的元素必须符合指定的正则表达式

​ @NotBlank(message =) 验证字符串非 null,且长度必须大于

​ @Email 被注释的元素必须是电子邮箱地址

​ @Length(min=,max=) 被注释的字符串的大小必须在指定的范围内

​ @NotEmpty 被注释的字符串的不能为非空

​ @Range(min=,max=,message=) 被注释的元素必须在合适的范围内

二、MVC 异常处理

​ SpringMVC 在处理请求过程中,如果出现异常信息则应该交由异常处理器进行处理。自定义异常处理器可以实现一个统一的异常处理逻辑。

​ 在 Java 应用中异常包括两类:预期异常和运行时异常 RuntimeException。前者主要通过捕获异常 try..catch 从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生。

​ 尽管在项目开发完成后,需要进行大量的测试,但仍会存在一些不可预知的错误异常。即使这种错误异常非常少 了,但仍不能保证异常一点也不存在,所以针对这种情况,我们就需要由一个统一异常处理机制,来针这种情况(突 发异常)进行处理,这就是自定义异常处理机制存在意义。 系统的 dao、service、controller 出现的异常都通过 throws Exception 这种方式向上抛出,最后由 SpringMVC 前端控制器交由异常处理器进行异常处理,如下图:

image-20230503191315345

​ 我们通过自定义异常和异常处理类进行实践:

package com.ls.bookmanager.exception;

public class CustomException extends Exception {
    private static final long serialVersionUID = 1L;
    private String message;

    public CustomException(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}
//——————————————————————————————————————————————————————————————————————————————————————————————
package com.ls.bookmanager.exception;
public class CustomExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelAndView mav = new ModelAndView();
        CustomException exception = null;
        if (ex instanceof CustomException) {
            exception = (CustomException) ex;
        } else {
            exception = new CustomException("未知异常:" + ex.getMessage());
        }
        String message = exception.getMessage();
        mav.addObject("errorMesg", message);
        mav.setViewName("/error/error");
        return mav;
    }
}

​ 使用上文的 logIn.action 来主动触发一个 By Zero 的异常,在 jsp 下面新建 /error/error.jsp 接收错误信息。

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    异常信息:${errorMesg}
</body>
</html>

image-20230503210741398

image-20230503210806908

三、RESTFUL 支持

​ RESTful 架构,是目前比较流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。 RESTful(即 Representational State Transfer 的缩写)其实是一个开发理念,是对 http 的很好的诠释。 Representational State Transfer 的含义就是“表现层的状态转化”。

(一)资源

​ 要让一个资源可以被识别,需要有个唯一标识,在Web中这个唯一标识就是URI(Uniform Resource Identifier)。URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。URI的设计应该遵循可寻址性原则,具有自描述性,需要在形式上给人以直觉上的关联。

(二)统一资源接口和状态转化

​ RESTful架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作,不论什么样的资源,都是通过使用相同的接口进行资源的访问。接口应该使用标准的HTTP方法如GET,PUT和POST,并遵循这些方法的语义。 HTTP 协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端 要操作服务器,必须通过某种手段,让服务器端发生“状态转化”(State Transfer),而这种转化是建立表现层之上的,所以就是“表现层状态转化”。 客户端能用到的手段就是 HTTP 协议里面,四个表示操作方式的动词:GET(用来获取资源)、 POST( 用来新建资源)、PUT(用来更新资源)、DELETE(用来删除资源)。

(三)RESTful 规范

​ 使用 RESTful 这种架构理念就需要对 http 请求进行一系列的规范。

​ 1.对 url 规范,写成 RESTful 的格式的 url

​ 非 REST 的 url:http://..../queryitem.action?id=001&itemType=01

​ REST 风格的 url:http://..../queryitem/001/01

​ 特点:url 简洁,将参数通过 url 传到服务端

​ 2.http 的方法规范 不管是删除、添加、更新、使用 url 是一致的,如果进行删除,需要设置 http 的方法为 delete,同理添加 后台 controller 方法:判断 http 方法,如果是 delete 执行删除,如果是 post 执行添加。

​ 3.对 http 的 contentType 规范 请求时指定 contentType,如果是 json 数据,设置成 json 格式的 type。

(四)RESTful 实例

​ 在 UserController 中添加,可见下面所对应的 URL 地址是一样的,但是允许的访问方式不同,我们通过 Posmman 模拟不同的访问方式访问同一个页面。

@PostMapping("/do.action")
@ResponseBody
public Map doSomeThing1(int id){
    System.out.println("添加");
    return getResult();
}

@DeleteMapping("/do.action")
@ResponseBody
public Map doSomeThing2(int id){
    System.out.println("删除");
    return getResult();
}

@PutMapping("/do.action")
@ResponseBody
public Map doSomeThing3(int id){
    System.out.println("修改");
    return getResult();
}

@GetMapping("/do.action")
@ResponseBody
public Map doSomeThing4(int id){
    System.out.println("查询");
    return getResult();
}

private Map getResult() {
    Map<String,Object> result = new HashMap<>();
    result.put("message","success");
    result.put("code",200);
    return result;
}

image-20230503213143089

image-20230503213231040

四、MVC 拦截器

(一)拦截器配置

​ SpringMVC 的拦截器 Interceptor 类似于 JSP/Servlet 中的 Filter 过滤器,可以对处理器进行预处理和后处理操作。Filter 过滤器可以过滤所有的请求,而 MVC 拦截器智能拦截处理器请求。

​ 在 SpringMVC 中提供了一个 HandlerInterceptor 接口,实现了该接口的实现类即为 SpringMVC 的拦截器。

​ 调用顺序和是否执行:

​ 1.preHandle 方法按拦截器的配置顺序调用

​ 2.postHandler 方法按拦截器的配置顺序逆向调用

​ 3.afterCompletion 方法按拦截器的配置顺序逆向调用

​ 4.postHandler 方法在拦截器链内的所有拦截器返回为 true 才调用。

​ 5.afterCompletion 方法在拦截器的 preHandle 方法返回为 true 才调用。

package com.ls.bookmanager.interceptor;
public class MyInterceptor implements HandlerInterceptor {
/**
     * Handler 执行前调用此方法,返回 true 表示放行,返回 false 表示拦截
     * 可以在此方法中进行登录验证,权限拦截等操作
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle.....");
        return true;
    }

    /**
     * Handler 执行后,返回 ModelAndView 前执行此方法,可以对返回的逻辑视图
     * 进行加工处理,将公共信息加入到模型中,方便页面展示数据
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle.....");
    }

    /**
     * Handler 执行后,视图返回后调用此方法,在此方法中可以获取 Handler 执行
     * 时抛出的异常信息,因此可以执行日志操作,资源清理等
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion.....");
    }
}

​ 下面是基于 MVC 处理过程中对拦截器执行位置的标注

image-20230504103533255

​ 写好之后必然要在 spring-mvc.xml 中配置拦截器,一般是针对所有 mapping 映射配置拦截器,即下面这种方式。

    <!-- 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- path 属性中/**表示所有的 url 拦截,/*表示只拦截根路径的 url 拦截,子层不拦截 -->
            <mvc:mapping path="/**"/>
            <bean class="com.ls.bookmanager.interceptor.MyInterceptor"/>
        </mvc:interceptor>
<!--        <mvc:interceptor>-->
<!--            <mvc:mapping path="/**"/>-->
<!--            <bean class="com.ls.bookmanager.interceptor.MyInterceptor2"/>-->
<!--        </mvc:interceptor>-->
    </mvc:interceptors>

image-20230504102838904

​ 拦截器的实现细节

(二)再谈 MVC 处理过程

image-20230504101453092

​ 1、发起请求到前端控制器 DispatcherServlet,处理请求,但不执行具体操作(model 层)

​ 2、前端控制 DispatcherServlet 请求 HandlerMapping 查找 Handler,(XML 配置或注解)

​ 3、处理器映射器 HandlerMapping 向前端控制器返回 handler 执行链

​ 4、前端控制器调用处理器适配器去执行 handler

​ 5、处理器适配器执行 handle

​ 6、handler 执行完成后返回给处理器适配器 ModelAndView(SpringMVC 框架的一个底层对象,包括 model、 view)

​ 7、处理器适配器向前端控制器返回 ModelAndView

​ 8、前端控制器请求视图解析器去进行视图解析,根据逻辑视图名解析成真正的视图(jsp)

​ 9、视图解析器向前端控制器返回 view

​ 10、前端控制器进行视图渲染,将模型数据(在 ModelAndView)填充到 request 域中。

​ 11、前端控制器向用户响应结果。

五、Spring、SpringMVC所有代码

​ 百度网盘

posted @ 2023-05-04 10:48  Purearc  阅读(97)  评论(0)    收藏  举报