你有没有掉进去过这些Spring MVC中的“陷阱“(上)

简介: 你有没有掉进去过这些Spring MVC中的“陷阱“(上)

一、自定义返回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依赖

<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>
复制代码

添加启动应用主程序

@SpringBootApplication
public class TrapsApplication {
    public static void main(String[] args) {
        SpringApplication.run(TrapsApplication.class,args);
    }
}
复制代码

ResponseEntity实现自定义HTTP状态码

@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请求

###
GET http://localhost:8080/tesla/first
Accept: application/json
复制代码

启动该服务,点击spring_mvc_traps.http文件左边的启动按钮,发起HTTP请求

image.png

响应头为设置的400,即BAD_REQUEST的枚举值。

@ResponseStatus注解

先看@ResponseStatus注解源码

image.png

@ResponseStatus注解可以标注在类上也可以标注在方法上,有三个属性,value和code都表示HTTP状态,默认时INTERAL_SERVER_ERROR,即500。reason属性表示原因,默认为空

新建common包,增加一个CommonException

@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "请求错误")
public class CommonException extends RuntimeException{
}
复制代码

在TeslaController中新增方法

@GetMapping("/second")
public CommonResponse<String> model3(){
    throw new CommonException();
}
复制代码

在spring_mvc_traps.http中增加请求方法

GET http://localhost:8080/tesla/second
Accept: application/json
复制代码

重新启动SpringTrapsApplication程序,并发送HTTP请求

image.png

还可以将@ResponseStatus标注在方法上

@GetMapping("/third")
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "请求地址不存在")
public void response404(){
}
复制代码

在spring_mvc_traps.http增加请求

GET http://localhost:8080/tesla/third
Accept: application/json
复制代码

image.png

@ControllerAdvice或者@RestControllerAdvice及@ExceptionHanlder注解

新增advice包,增加GlobalExceptionAdvice

@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中增加方法

@RequestMapping("/fourth")
public CommonResponse<String> fourth() throws CustException{
    throw new CustException("Some error");
}
复制代码

重启启动应用,在spring_mvc_traps.http增加请求

###
GET http://localhost:8080/tesla/fourth
Accept: application/json
复制代码

点击发送该请求

image.png

二、时间序列化和反序列化中的“陷阱”

新增一个entity包,增加UserInfo实体类

@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请求

@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请求发起

###
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请求

image.png

发送POST请求

image.png

POST请求中的参数是在请求的BODY中,请求的参数的属性并不会触发 @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")定义的格式,所以会出发JSON转义错误,如何解决这类错误?

使用JsonFormat注解

在UserInfo实体类中的createTime属性增加注解

@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
复制代码

重新启动应用,发送POST请求

image.png

使用自定义格式转换器@JsonDeserialize

@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)注解

image.png

修改POST请求传入参数中createTime的格式,再次发起POST请求

POST http://localhost:8080/post
Content-Type: application/json
{
    "id": "1",
    "name": "stark",
    "createTime": "2022/02/01"
}
复制代码

仍然可以转化成功

image.png

时间格式的局部处理即对需要时间转换的属性上增加@JsonDeserialize注解,这种方式代码可维护性比较差

全局处理Date格式转换

增加时间格式处理的全局配置类,增加@Configuration及在方法上标注@Bean注解,将该类交个Spring容器管理。

@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类型的属性进行反序列化

@Override
public Class<?> handledType() {
    return Date.class;
}
复制代码

将UserInfo实体类中createTime属性上的@JsonDeserialize注解注释掉,重新启动应用,再次发起POST请求

image.png

同样可以实现时间格式的转换。


相关文章
|
19天前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
109 29
|
2月前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
67 4
|
3月前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
234 2
|
4月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
4月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
79 2
|
4月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
324 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
5月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
5月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
|
6月前
|
Java 数据库连接 Spring
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
文章是关于Spring、SpringMVC、Mybatis三个后端框架的超详细入门教程,包括基础知识讲解、代码案例及SSM框架整合的实战应用,旨在帮助读者全面理解并掌握这些框架的使用。
后端框架入门超详细 三部曲 Spring 、SpringMVC、Mybatis、SSM框架整合案例 【爆肝整理五万字】
|
6月前
|
前端开发 应用服务中间件 数据库
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查
这篇文章通过一个具体的项目案例,详细讲解了如何使用SpringMVC、Thymeleaf、Bootstrap以及RESTful风格接口来实现员工信息的增删改查功能。文章提供了项目结构、配置文件、控制器、数据访问对象、实体类和前端页面的完整源码,并展示了实现效果的截图。项目的目的是锻炼使用RESTful风格的接口开发,虽然数据是假数据并未连接数据库,但提供了一个很好的实践机会。文章最后强调了这一章节主要是为了练习RESTful,其他方面暂不考虑。
SpringMVC入门到实战------八、RESTful案例。SpringMVC+thymeleaf+BootStrap+RestFul实现员工信息的增删改查