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  阅读(52)  评论(0编辑  收藏  举报