day62(Spring MVC框架基础2:RESTful基础,响应正文的结果类型,统一处理异常,拦截器)
1.RESTful基础
1.概念
-
RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义。RESTFUL适用于移动互联网厂商作为业务接口的场景,实现第三方ott调用移动网络资源的功能,动作类型为新增、变更、删除所调用资源
-
RESTful的设计风格的典型表现就是:将某些唯一的请求参数的值放在URL中,使之成为URL的一部分,例如:
-
这个URL的最后一部分28557115应该就是这篇贴子的id值,而不是使用例如?id=28557115这样的方式放在URL参数中。
-
注意:RESTful只是一种设计风格,并不是一种规定,也没有明确的或统一的执行方式
-
在开发实践中,如果没有明确的要求,以处理用户数据为例,可以将URL设计为:
-
RESTful建议根据希望实现的数据操作不同而使用不同的请求方式:
2.应用
-
在开发实践中,仅使用GET、POST这2种请求方式的做法更为常见,主要因为:
-
某些业务可能非常复杂,可能同时需要执行增、删、改、查的多种数据操作,不便于界定使用POST、DELETE、PUT、GET请求方式中的哪一种
-
大部分开发者更习惯于只使用GET、POST这2种请求方式
-
-
通常,以查询为主要目标的请求使用GET请求方式,否则,使用POST请求方式
-
在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;
} -
提示:在以上代码中,URL中使用的占位符是{id},则方法的参数名称也应该是id,就可以直接匹配上!如果无法保证这2处的名称一致,则需要在@PathVariable注解中配置占位符中的名称,例如:
@GetMapping("/{userId}/info.do")
public UserVO info(@PathVariable("userId") Long id) {
// ...
} -
在使用{}格式的占位符时,还可以结合正则表达式进行匹配,其基本语法是:
-
{占位符名称:正则表达式}
-
例如:
@GetMapping("/{id:[0-9]+}/info.do")
-
以上模式的多种不冲突的正则表达式是可以同时存在的,例如:
@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 + " 的用户的信息……");// ...
} -
还可以存在不使用正则表达式,但是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("即将查询 用户的列表 的信息……");
// ...
} -
执行时,如果使用/user/list/info.do,则会匹配到以上代码中的最后一个方法,并不会因为这个URL还能匹配第2个方法配置的{username:[a-zA-Z]+}而产生冲突,所以,使用了占位符的做法并不影响精准匹配的路径
-
-
将请求参数设计到URL中时,可以在@RequestMapping或相关注解配置URL时使用{名称}进行占位
-
在处理请求的方法的参数列表中,为占位符对应的参数添加@PathVariable,Spring MVC会自动获取URL中的值注入到参数中
-
在URL的占位符中,可以使用正则表达式限制占位符的文本格式,不冲突的多个使用了正则表达式的相同格式URL可以共存
-
使用了正则表达式的占位符的,与不使用占位符的,相同格式的URL可以共存,且优先匹配到不使用占位符的
2. 响应正文的结果类型
-
当响应正文时,只要方法的返回值是自定义的数据类型,则SpringMVC框架就一定会调用jackson-databind中的转换器,就可以将结果转换为JSON格式的字符串
-
显示的通用返回类型如下:
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. 统一处理异常
-
Spring MVC框架提供了统一处理异常的机制,使得特定种类的异常对应一段特定的代码,后续,当编写代码时,无论在任何位置,都可以将异常直接抛出,由统一处理异常的代码进行处理即可
-
无论哪种异常(包括RuntimeException及其子孙类异常),只要没有显式的使用try...catch语法进行捕获并处理,均视为抛出
-
-
关于统一处理异常,需要自定义方法对异常进行处理,关于此方法:
-
注解:必须添加@ExceptionHandler注解 –
-
访问权限:应该是公有的 –
-
返回值类型:可参考处理请求的方法的返回值类型–
-
参数列表:必须包含1个异常类型的参数,并且可按需添加HttpServletRequest、HttpServletResponse等少量特定的类型的参数,不可以随意添加参数
-
例如:
@ExceptionHandler
public String handleException(NullPointerException e) {
return "Error, NullPointerException!";
} -
注意:以上处理异常的代码,只能作用于当前控制器类中各个处理请求的方法,对其它控制器类的中代码并不产生任何影响,也就无法处理其它控制类中处理请求时出现的异常!
-
-
为保证更合理的处理异常,应该:
-
将处理异常的代码放在专门的类中 –
-
在此类上添加@ControllerAdvice注解 –
-
由于目前主流的响应方式都是“响应正文”的,则可以将@ControllerAdvice替换为@RestControllerAdvice
-
-
例如,创建GlobalExceptionHandler类,代码如下:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public String handleException(NullPointerException e) {
return "Error, NullPointerException!";
}
}
-
-
处理的方法的参数中的异常类型,就是Spring MVC框架将统一处理的异常类型,例如将参数声明为Throwable类型时,所有异常都可被此方法进行处理!但是,在处理过程中,应该判断当前异常对象所归属的类型,以针对不同类型的异常进行不同的处理
-
Spring MVC允许存在多个统一处理异常的方法,这些方法可以在不同的类中,只要处理的异常的类型不冲突即可(允许继承)–
-
例如:如果同时存在2个方法,分别处理NullPointerException和RuntimeException,是允许的
-
例如:
@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!";
}
-
在开发实践中,通常都会有handleThrowable()方法(方法名是自定义的),以避免某个异常没有被处理而导致500错误!
-
@ExceptionHandler注解还可用于配置被注解的方法能够处理的异常的类型,其效力的优先级高于在方法的参数上指定异常类型
-
在开发实践中,建议为每一个@ExceptionHandler配置注解参数,在注解参数中指定需要处理异常的类型,而处理异常的方法的参数类型只需要包含@ExceptionHandler配置的类型即可,甚至可以是Throwable
-
例如:
@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.概念
-
在Spring MVC框架中,拦截器是可以运行在所有控制器处理请求之前和之后的一种组件,并且,如果拦截器运行在控制器处理请求之前,还可以选择对当前请求进行阻止或放行。
-
注意:拦截器的目的并不是“拦截下来后阻止运行”,更多的是“拦截下来后执行某些代码” ,其优势在于可作用于若干种不同请求的处理过程,即写一个拦截器,就可以在很多种请求的处理过程中被执行。
2.举例
-
需要使用拦截器时,需要自定义类,实现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()");
}
} -
每个拦截器都必须注册才会被启用,注册过程通过重写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");
}
} -
当进行访问时,在浏览器窗口中将看到一片空白,在Tomcat控制台可以看到preHandle()方法已经执行
-
当把拦截器中preHandle()方法的返回值改为true时,在Tomcat控制台可以看到依次执行了preHandle() -> 控制器中处理请求的方法->postHandle() -> afterCompletion()
-
preHandle()方法的返回值为true时,表示“放行”,为false时,表示“阻止”
3.配置
-
关于注册拦截器时的配置,使用链式语法可以先调用addInterceptor()方法添加拦截器,然后调用addpathPatter()方法添加哪些路径需要被拦截,此方法的参数可以是String...,也可以是List,在编写路径值时,可以使用作为通配符,例如配置为/user/ ,则可以匹配/user/login.do、/user/reg.do等所有直接在/user下的路径,但不能匹配/user/1/info.do,如果需要匹配若干层级,必须使用2个连续的星号,例如配置为/user/
-
一旦使用通配符,就有可能导致匹配的范围过大,例如配置为/user/**时,还可以匹配到/user/reg.do(注册)和/user/login.do(登录),如果此拦截器是用于“验证用户是否登录”的,则不应该对这2个路径进行处理,那么,配置拦截器时,还可以在链式语法中调用excludePathPattern()方法,以添加“排除路径”(例外)
-
配置示例
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addpathPatterns("/user/**")
.excludePathPatterns("/user/reg.do"
,
"/user/login.do");}
5.小结
1.理解
-
理解Spring MVC框架的作用
-
认识基础的依赖项
-
spring-webmvc、javax.servlet-api、jackson-databind
-
-
了解RESTful风格
2.掌握
-
接收请求,响应结果,处理异常
-
掌握创建基于Maven的运行在Tomcat的Webapp
-
掌握配置Spring MVC的运行环境(使得控制器能接收到请求)
-
掌握以下注解的使用:
-
@Controller / @RestController –
-
@ResponseBody –
-
@RequestMapping / @GetMapping / @PostMapping ... –
-
@RequestParam / @PathVariable –
-
@ExceptionHandler / @ControllerAdvice / @RestControllerAdvice –
-
@EnableWebMvc
-
-
掌握接收请求参数的方式:
-
掌握响应JSON格式的正文的做法:
-
掌握响应JSON格式的正文时,统一的响应类型的类的设计
-
掌握统一处理异常 –
-
掌握拦截器的创建与配置