一、自定义返回HTTP状态码
  当浏览器输入一个URL地址时,浏览器会向服务器发出请求,在浏览器接收和显示响应内容之前,服务器会返回一个包含HTTP状态码的响应头,响应浏览器的请求。动态码是一个标识,标识当前响应的状态成功或者失败或者需要进行进行其他操作。
常见的HTTP状态码有200、302、404、500等
HTTP状态码有以下五种类型,HTTP状态码的第一位表示状态码的类型:
- 1xx:服务器收到客户端的请求,需要客户端继续执行操作
- 2xx:请求成功
- 3xx:重定向,需要进一步的操作完成请求
- 4xx:客户端出错,请求出错
- 5xx:服务区错误,请求处理发生错误
而我们在编写基于Spring MVC的程序时并没有定义响应的状态码,这是因为Spring MVC已经在框架中定义好了这些响应码,不需要在编写业务代码时再去定义响应码,当然Spring MVC也支持自定义状态码
需要自定义返回状态码的场景有以下几种
- 针对不容的错误类型发送特定的错误码
- 客户端的定制化需求
Spring MVC中自定义返回状态码的方式有以下几种:
- 使用ResponseEntity表示状态码、头部信息、响应体
- Controller类或者异常类上使用@ResponseStatus注解标识响应码,当方法抛出该异常时返回设置的响应码
- 使用@ControllerAdvice或者@RestControllerAdvice标识一个异常处理类,@ExceptionHanlder标识一个异常处理方法,方法中定义异常类的返回码及响应体等内容
新建一个项目spring_mvc_traps,添加maven依赖
代码语言:javascript复制<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.citi</groupId>
<artifactId>spring-mvc-traps</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-traps</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
添加启动应用主程序
代码语言:javascript复制@SpringBootApplication
public class TrapsApplication {
public static void main(String[] args) {
SpringApplication.run(TrapsApplication.class,args);
}
}
ResponseEntity实现自定义HTTP状态码
代码语言:javascript复制@RestController
@RequestMapping("/tesla")
public class TeslaController {
@GetMapping("/first")
public ResponseEntity<CommonResponse<String>> cyber(){
CommonResponse<String> result = new CommonResponse<>(0,"");
result.setData("Cyber");
// 自定义HTTP响应码
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
}
使用IDEA的插件REST Client发起HTTP请求,在resources目录下新建spring_mvc_traps.http 增加http请求
代码语言:javascript复制###
GET http://localhost:8080/tesla/first
Accept: application/json
启动该服务,点击spring_mvc_traps.http文件左边的启动按钮,发起HTTP请求
响应头为设置的400,即BAD_REQUEST的枚举值。
@ResponseStatus注解
先看@ResponseStatus注解源码
@ResponseStatus注解可以标注在类上也可以标注在方法上,有三个属性,value和code都表示HTTP状态,默认时INTERAL_SERVER_ERROR,即500。reason属性表示原因,默认为空
新建common包,增加一个CommonException
代码语言:javascript复制@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "请求错误")
public class CommonException extends RuntimeException{
}
在TeslaController中新增方法
代码语言:javascript复制@GetMapping("/second")
public CommonResponse<String> model3(){
throw new CommonException();
}
在spring_mvc_traps.http中增加请求方法
代码语言:javascript复制GET http://localhost:8080/tesla/second
Accept: application/json
重新启动SpringTrapsApplication程序,并发送HTTP请求
还可以将@ResponseStatus标注在方法上
代码语言:javascript复制@GetMapping("/third")
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "请求地址不存在")
public void response404(){
}
在spring_mvc_traps.http增加请求
代码语言:javascript复制GET http://localhost:8080/tesla/third
Accept: application/json
@ControllerAdvice或者@RestControllerAdvice及@ExceptionHanlder注解
新增advice包,增加GlobalExceptionAdvice
代码语言:javascript复制@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler(value = CustException.class)
public ResponseEntity<CommonResponse> handleCustException(HttpServletRequest request, CustException ex){
CommonResponse<String> result = new CommonResponse<>(0,"");
result.setData(ex.getMessage());
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
}
}
在TeslaController中增加方法
代码语言:javascript复制@RequestMapping("/fourth")
public CommonResponse<String> fourth() throws CustException{
throw new CustException("Some error");
}
重启启动应用,在spring_mvc_traps.http增加请求
代码语言:javascript复制###
GET http://localhost:8080/tesla/fourth
Accept: application/json
点击发送该请求
二、时间序列化和反序列化中的“陷阱”
新增一个entity包,增加UserInfo实体类
代码语言:javascript复制@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
private long id;
private String name;
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
定义一个Controller,UserController;增加GET和POST请求
代码语言:javascript复制@RestController
public class UserController {
@GetMapping("/get")
public Map<String, Long> getDateByGet(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
Map<String, Long> result = new HashMap<>();
result.put("timestamp", date.getTime());
return result;
}
@PostMapping("/post")
public Map<String, String> getDataByPost(@RequestBody UserInfo userInfo){
Map<String, String> result = new HashMap<>();
result.put("id", userInfo.getId().toString());
result.put("name", userInfo.getName());
result.put("createTime", userInfo.getCreateTime().toString());
return result;
}
}
在resource目录下新增一个spring_mvc_traps_date_transfer.http,定义GET和POST请求发起
代码语言:javascript复制###
GET http://localhost:8080/get?date=2022-02-01 23:43:00
Accept: application/json
###
POST http://localhost:8080/post
Content-Type: application/json
{
"id": "1",
"name": "stark",
"createTime": "2022-02-01 23:43:00"
}
发送GET请求
发送POST请求
POST请求中的参数是在请求的BODY中,请求的参数的属性并不会触发 @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")定义的格式,所以会出发JSON转义错误,如何解决这类错误?
使用JsonFormat注解
在UserInfo实体类中的createTime属性增加注解
代码语言:javascript复制@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT 8")
重新启动应用,发送POST请求
使用自定义格式转换器@JsonDeserialize
代码语言:javascript复制@Slf4j
public class DateJacksonConverter extends JsonDeserializer<Date> {
private static final String[] pattern = new String[] {
"yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd"
};
@Override
public Date deserialize(JsonParser jsonParser, DeserializationContext context)
throws IOException, JsonProcessingException {
Date targetDate = null;
String originDate = jsonParser.getText();
if (StringUtils.isNotEmpty(originDate)) {
try {
long longDate = Long.parseLong(originDate.trim());
targetDate = new Date(longDate);
} catch (NumberFormatException pe) {
try {
targetDate = DateUtils.parseDate(
originDate, DateJacksonConverter.pattern
);
} catch (ParseException ex) {
log.error("parse error: {}", ex.getMessage());
throw new IOException("parse error");
}
}
}
return targetDate;
}
}
修改UserInfo实体类中createTime属性,将@JsonFormat注解注释,增加@JsonDeserialize(using = DateJacksonConverter.class)注解
修改POST请求传入参数中createTime的格式,再次发起POST请求
代码语言:javascript复制POST http://localhost:8080/post
Content-Type: application/json
{
"id": "1",
"name": "stark",
"createTime": "2022/02/01"
}
仍然可以转化成功
时间格式的局部处理即对需要时间转换的属性上增加@JsonDeserialize注解,这种方式代码可维护性比较差
全局处理Date格式转换
增加时间格式处理的全局配置类,增加@Configuration及在方法上标注@Bean注解,将该类交个Spring容器管理。
代码语言:javascript复制@Configuration
public class DateConverterConfig {
@Bean
public DateJacksonConverter dateJacksonConverter() {
return new DateJacksonConverter();
}
@Bean
public Jackson2ObjectMapperFactoryBean jackson2ObjectMapperFactoryBean(
@Autowired DateJacksonConverter dateJacksonConverter) {
Jackson2ObjectMapperFactoryBean jackson2ObjectMapperFactoryBean =
new Jackson2ObjectMapperFactoryBean();
jackson2ObjectMapperFactoryBean.setDeserializers(dateJacksonConverter);
return jackson2ObjectMapperFactoryBean;
}
}
在DateJacksonConverter类中重写handleType()方法,指定针对所有Date类型的属性进行反序列化
代码语言:javascript复制@Override
public Class<?> handledType() {
return Date.class;
}
将UserInfo实体类中createTime属性上的@JsonDeserialize注解注释掉,重新启动应用,再次发起POST请求
同样可以实现时间格式的转换。