day62(Spring MVC框架基础2:RESTful基础,响应正文的结果类型,统一处理异常,拦截器)

day62(Spring MVC框架基础2:RESTful基础,响应正文的结果类型,统一处理异常,拦截器)

1.RESTful基础

1.概念

  1. RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义。RESTFUL适用于移动互联网厂商作为业务接口的场景,实现第三方ott调用移动网络资源的功能,动作类型为新增、变更、删除调用资源

  2. RESTful的设计风格的典型表现就是:将某些唯一的请求参数的值放在URL中,使之成为URL的一部分,例如:

    1. https://www.zhihu.com/question/28557115

    2. 这个URL的最后一部分28557115应该就是这篇贴子的id值,而不是使用例如?id=28557115这样的方式放在URL参数中。

  3. 注意:RESTful只是一种设计风格,并不是一种规定,也没有明确的或统一的执行方式

  4. 在开发实践中,如果没有明确的要求,以处理用户数据为例,可以将URL设计为:

    1. /users:查看用户列表

    2. /users/9527:查询id=9527的用户的数据

    3. /users/9527/delete:删除id=9527的用户的数据

  5. RESTful建议根据希望实现的数据操作不同而使用不同的请求方式:

    1. 通过此前的学习,你应该知道HTTP请求方式包括:GET, HEAD, POST, PUT, PATCH,DELETE, OPTIONS, TRACE

    2. 查询:使用GET请求

    3. 新增:使用POST请求

    4. 修改:使用PUT请求

    5. 删除:使用DELETE请求

2.应用

  1. 在开发实践中,仅使用GET、POST这2种请求方式的做法更为常见,主要因为:

    1. 某些业务可能非常复杂,可能同时需要执行增、删、改、查的多种数据操作,不便于界定使用POST、DELETE、PUT、GET请求方式中的哪一种

    2. 大部分开发者更习惯于只使用GET、POST这2种请求方式

  2. 通常,以查询为主要目标的请求使用GET请求方式,否则,使用POST请求方式

  3. 在RESTful风格的URL中,大多是包含了某些请求参数的值,在使用Spring MVC框架时,当需要设计这类URL时,可以使用{名称}进行占位,并在处理请求的方法的参数列表中,使用@PathVariable注解请求参数,即可将占位符的实际值注入到请求参数中!

    // /user/3/info.do
    @GetMapping("/{id}/info.do")
    public UserVO info(@PathVariable Long id) {
    System.out.println("即将查询 id = " + id + " 的用户的信息……");
    UserVO userVO = new UserVO();
    userVO.setUsername("chengheng");
    userVO.setPassword("1234567890");
    userVO.setEmail("chengheng@qq.com");
    return userVO;
    }
  4. 提示:在以上代码中,URL中使用的占位符是{id},则方法的参数名称也应该是id,就可以直接匹配上!如果无法保证这2处的名称一致,则需要在@PathVariable注解中配置占位符中的名称,例如:

    @GetMapping("/{userId}/info.do")
    public UserVO info(@PathVariable("userId") Long id) {
    // ...
    }
  5. 在使用{}格式的占位符时,还可以结合正则表达式进行匹配,其基本语法是:

    1. {占位符名称:正则表达式}
    2. 例如:

      @GetMapping("/{id:[0-9]+}/info.do")
    3. 当设计成以上URL时,仅当占位符位置的是纯数字的URL才会被匹配上,如果不是纯数字的刚出现404错误页面

    4. 以上模式的多种不冲突的正则表达式是可以同时存在的,例如:

      @GetMapping("/{id:[0-9]+}/info.do")
      public UserVO info(@PathVariable Long id) {
      System.out.println("即将查询 id = " + id + " 的用户的信息……");
      // ...
      }
      @GetMapping("/{username:[a-zA-Z]+}/info.do")
      public UserVO info(@PathVariable String username) {
      System.out.println("即将查询 用户名 = " + username + " 的用户的信息……");// ...
      }
    5. 还可以存在不使用正则表达式,但是URL格式几乎一样的配置,例如:

      @GetMapping("/{id:[0-9]+}/info.do")
      public UserVO info(@PathVariable Long id) {
      System.out.println("即将查询 id = " + id + " 的用户的信息……");
      // ...
      }
      @GetMapping("/{username:[a-zA-Z]+}/info.do")
      public UserVO info(@PathVariable String username) {
      System.out.println("即将查询 用户名 = " + username + " 的用户的信息……");
      // ...
      }
      // http://localhost:8080/springmvc01_war_exploded/user/list/info.do
      @GetMapping("/list/info.do")
      public UserVO list() {
      System.out.println("即将查询 用户的列表 的信息……");
      // ...
      }
    6. 执行时,如果使用/user/list/info.do,则会匹配到以上代码中的最后一个方法,并不会因为这个URL还能匹配第2个方法配置的{username:[a-zA-Z]+}而产生冲突,所以,使用了占位符的做法并不影响精准匹配的路径

  6. 将请求参数设计到URL中时,可以在@RequestMapping或相关注解配置URL时使用{名称}进行占位

  7. 在处理请求的方法的参数列表中,为占位符对应的参数添加@PathVariable,Spring MVC会自动获取URL中的值注入到参数中

  8. 当占位符中的名称方法参数的名称不一致时,需要配置@PathVariable注解的参数

  9. 在URL的占位符中,可以使用正则表达式限制占位符的文本格式,不冲突的多个使用了正则表达式的相同格式URL可以共存

  10. 使用了正则表达式的占位符的,与不使用占位符的,相同格式的URL可以共存,且优先匹配到不使用占位符的

2. 响应正文的结果类型

  1. 当响应正文时,只要方法的返回值是自定义的数据类型,则SpringMVC框架就一定会调用jackson-databind中的转换器,就可以将结果转换为JSON格式的字符串

  2. 通常,在项目开发中,会定义一个“通用”的数据类型,无论是哪个控制器的哪个处理请求的方法,最终都将返回此类型

  3. 显示的通用返回类型如下:

    public class JsonResult<T> {
    private Integer state; // 业务返回码
    private String message; // 消息
    private T data; // 数据
    // Setters & Getters
    private JsonResult() { }
    public static JsonResult<Void> ok() {
    return ok(null);
    }
    public static <T> JsonResult<T> ok(T data) {
    JsonResult<T> jsonResult = new JsonResult<>();
    jsonResult.state = State.OK.getValue();
    jsonResult.data = data;
    return jsonResult;
    }
    public static JsonResult<Void> fail(State state, String message){JsonResult<Void> jsonResult = new JsonResult<>();
    jsonResult.state = state.getValue();
    jsonResult.message = message;
    return jsonResult;
    }
       
    public enum State {
    OK(20000),
    ERR_USERNAME(40400),
    ERR_PASSWORD(40600);
    Integer value;
    State(Integer value) {
    this.value = value;
    }
    public Integer getValue() {
    return value;
    }
    }
    }

3. 统一处理异常

  1. Spring MVC框架提供了统一处理异常的机制,使得特定种类的异常对应一段特定的代码,后续,当编写代码时,无论在任何位置,都可以将异常直接抛出,由统一处理异常的代码进行处理即可

    1. 无论哪种异常(包括RuntimeException及其子孙类异常),只要没有显式的使用try...catch语法进行捕获并处理,均视为抛出

  2. 关于统一处理异常,需要自定义方法对异常进行处理,关于此方法

    1. 注解:必须添加@ExceptionHandler注解 –

    2. 访问权限:应该是公有的 –

    3. 返回值类型:可参考处理请求的方法的返回值类型–

    4. 方法名称自定义

    5. 参数列表:必须包含1个异常类型的参数,并且可按需添加HttpServletRequest、HttpServletResponse等少量特定的类型的参数,不可以随意添加参数

    6. 例如:

      @ExceptionHandler
      public String handleException(NullPointerException e) {
      return "Error, NullPointerException!";
      }
    7. 注意:以上处理异常的代码,只能作用于当前控制器类中各个处理请求的方法,对其它控制器类的中代码并不产生任何影响,也就无法处理其它控制类中处理请求时出现的异常!

  3. 为保证更合理的处理异常,应该:

    1. 将处理异常的代码放在专门的类中 –

    2. 在此类上添加@ControllerAdvice注解 –

      1. 由于目前主流的响应方式都是“响应正文”的,则可以将@ControllerAdvice替换为@RestControllerAdvice

    3. 例如,创建GlobalExceptionHandler类,代码如下:

      @RestControllerAdvice
      public class GlobalExceptionHandler {
      @ExceptionHandler
      public String handleException(NullPointerException e) {
      return "Error, NullPointerException!";
      }
      }
  4. 处理的方法的参数中的异常类型,就是Spring MVC框架将统一处理的异常类型,例如将参数声明为Throwable类型时,所有异常都可被此方法进行处理!但是,在处理过程中,应该判断当前异常对象所归属的类型,以针对不同类型的异常进行不同的处理

  5. Spring MVC允许存在多个统一处理异常的方法,这些方法可以在不同的类中,只要处理的异常的类型不冲突即可(允许继承)–

    1. 例如:如果有2个或多个方法都处理NullPointerException,是错误的–

    2. 例如:如果同时存在2个方法,分别处理NullPointerException和RuntimeException,是允许的

    3. 例如:

      @ExceptionHandler
      public String handleNullPointerException(NullPointerException e) {
      return "Error, NullPointerException!";
      }
      @ExceptionHandler
      public String handleNumberFormatException(NumberFormatException e) {
      return "Error, NumberFormatException!";
      }
      @ExceptionHandler
      public String handleThrowable(Throwable e) {
      e.printstacktrace();
      return "Error, Throwable!";
      }
  6. 在开发实践中,通常都会有handleThrowable()方法方法名是自定义的),以避免某个异常没有被处理而导致500错误

    1. 方法中应该输出异常的相关信息,甚至跟踪信息,否则,当程序运行此至处时,可能不便于观察、分析、记录出现异常

  7. @ExceptionHandler注解还可用于配置被注解的方法能够处理的异常的类型,其效力的优先级高于在方法的参数上指定异常类型

  8. 在开发实践中,建议为每一个@ExceptionHandler配置注解参数,在注解参数中指定需要处理异常的类型,而处理异常的方法的参数类型只需要包含@ExceptionHandler配置的类型即可,甚至可以是Throwable

    1. 例如:

      @ExceptionHandler({
      NullPointerException.class,
      ClassCastException.class
      })
      public String handleNullPointerException(Throwable e) {
      return "Error, NullPointerException or ClassCastException!";
      }
      @ExceptionHandler(NumberFormatException.class)
      public String handleNumberFormatException(Throwable e) {
      return "Error, NumberFormatException!";
      }
      @ExceptionHandler(Throwable.class)
      public String handleThrowable(Throwable e) {
      return "Error, Throwable!";
      }

注意:

  • 处理的异常的阶段为与用户直接打交道的阶段,例如mapper中的异常可以抛出,交给controller处理

4.拦截

1.概念

  1. 在Spring MVC框架中,拦截器是可以运行在所有控制器处理请求之前和之后的一种组件,并且,如果拦截器运行在控制器处理请求之前,还可以选择对当前请求进行阻止或放行。

  2. 注意:拦截器的目的并不是“拦截下来后阻止运行”,更多的是“拦截下来后执行某些代码” ,其优势在于可作用于若干种不同请求的处理过程,即写一个拦截器,就可以在很多种请求的处理过程中被执行。

  3. 只要是若干种不同的请求过程中都需要执行同样的或高度相似的代码,都可以使用拦截解决,典型的例如验证用户是否已经登录等等

2.举例

  1. 需要使用拦截器时,需要自定义类,实现handlerinterceptor接口,例如:

    public class LoginInterceptor implements handlerinterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
    System.out.println("LoginInterceptor.preHandle()");
    return false;
    }
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception {
    System.out.println("LoginInterceptor.postHandle()");
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) throws Exception {
    System.out.println("LoginInterceptor.afterCompletion()");
    }
    }
  2. 每个拦截器都必须注册才会被启用,注册过程通过重写WebMvcConfigure接口中的addInterceptors()方法即可,例如:

    @Configuration // 此注解不是必须的
    @EnableWebMvc
    @ComponentScan("cn.tedu.springmvc") // 必须配置在当前配置类,不可配置在Spring的配置类public class SpringMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
    .addpathPatterns("/user/login.do");
    }
    }
  3. 当进行访问时,在浏览器窗口中将看到一片空白,在Tomcat控制台可以看到preHandle()方法已经执行

  4. 当把拦截器中preHandle()方法的返回值改为true时,在Tomcat控制台可以看到依次执行了preHandle() -> 控制器中处理请求的方法->postHandle() -> afterCompletion()

  5. preHandle()方法的返回值为true时,表示“放行”,为false时,表示“阻止”

3.配置

  1. 关于注册拦截器时的配置,使用链式语法可以先调用addInterceptor()方法添加拦截器,然后调用addpathPatter()方法添加哪些路径需要被拦截,此方法的参数可以是String...,也可以是List,在编写路径值时,可以使用作为通配符,例如配置为/user/ ,则可以匹配/user/login.do、/user/reg.do等所有直接在/user下的路径,但不能匹配/user/1/info.do,如果需要匹配若干层级,必须使用2个连续的星号,例如配置为/user/

  2. 一旦使用通配符,就有可能导致匹配的范围过大,例如配置为/user/**时,还可以匹配到/user/reg.do(注册)和/user/login.do(登录),如果此拦截器是用于“验证用户是否登录”的,则不应该对这2个路径进行处理,那么,配置拦截器时,还可以在链式语法中调用excludePathPattern()方法,以添加“排除路径”(例外)

  3. 配置示例

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(new LoginInterceptor())
    .addpathPatterns("/user/**")
    .excludePathPatterns("/user/reg.do"
    ,
    "/user/login.do");}

5.小结

1.理解

  1. 理解Spring MVC框架的作用

  2. 认识基础的依赖项

  3. 了解RESTful风格

2.掌握

  1. 接收请求,响应结果,处理异常

  2. 掌握创建基于Maven的运行在Tomcat的Webapp

  3. 掌握配置Spring MVC的运行环境(使得控制器能接收到请求)

  4. 掌握以下注解的使用:

    1. @Controller / @RestController –

    2. @ResponseBody –

    3. @RequestMapping / @GetMapping / @PostMapping ... –

    4. @RequestParam / @PathVariable –

    5. @ExceptionHandler / @ControllerAdvice / @RestControllerAdvice –

    6. @EnableWebMvc

  5. 掌握接收请求参数的方式:

    1. 将请求参数直接声明在处理请求的方法的参数列表中 –

    2. 将若干个请求参数进行封装,并将封装的类型声明在处理请求的方法的参数列表中–

    3. 如果是URL中的路径,则需要使用@PathVariable

  6. 掌握响应JSON格式的正文的做法:

    1. 处理请求的方法必须添加@ResponseBody,或当前控制器添加@ResponseBody,或当前控制器添加@RestController –

    2. 在Spring MVC配置类上添加@EnableWebMvc –

    3. 在项目的pom.xml中添加jackson-databind

    4. 处理请求的方法返回自定义的数据类型

  7. 掌握响应JSON格式的正文时,统一的响应类型的类的设计

  8. 掌握统一处理异常 –

  9. 掌握拦截器的创建与配置

相关文章

这篇文章主要介绍了spring的事务传播属性REQUIRED_NESTED的原...
今天小编给大家分享的是一文解析spring中事务的传播机制,相...
这篇文章主要介绍了SpringCloudAlibaba和SpringCloud有什么区...
本篇文章和大家了解一下SpringCloud整合XXL-Job的几个步骤。...
本篇文章和大家了解一下Spring延迟初始化会遇到什么问题。有...
这篇文章主要介绍了怎么使用Spring提供的不同缓存注解实现缓...