在本教程中,我们将学习如何处理使用 Spring Boot 开发的 RESTful Web 服务的异常。
我们可以像在基于 SpringMVC 的 Web 应用程序中使用@ExceptionHandler 和 @ControllerAdvice 注释一样处理 REST API 中的异常。您可以不渲染视图,而是以 JSON/XML 格式返回带有相应 HTTP 状态代码和异常详细信息的 ResponseEntity。
默认情况下,Spring Boot 提供了一个 /error 映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“全局”错误页面。 Rest 客户端,它会生成一个 JSON 响应,其中包含错误的详细信息、HTTP 状态和异常消息。
与其简单地抛出带有 HTTP 状态代码的异常,不如提供有关问题的更多详细信息,例如错误代码、消息、原因等。在此示例中,我们定义了一个用 @ControllerAdvice 注释的类来自定义为特定控制器和/或异常类型返回的 JSON 文档。
我们将按照以下工作流程在 Spring boot 中实现异常处理:
Spring Boot 提供了一个名为 Spring Initializer 的网络工具来快速引导应用程序。只需转到 https://start.spring.io/ 并生成一个新的 Spring Boot 项目。
在 Spring boot 创建中使用以下详细信息:
项目名称: springboot-blog-rest-api
**项目类型:**Maven
选择依赖项: Spring Web、Lombok、Spring Data JPA、Spring Security、Dev Tools 和 MySQL Driver
**包名称:**net.javaguides.springboot
包: Jar
将 Spring Boot 项目下载为 zip 文件,解压缩并将其导入到您喜欢的 IDE 中。
这是 pom.xml 文件供您参考:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot.blog</groupId>
<artifactId>springboot-blog-rest-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-blog-rest-api</name>
<description>Spring boot blog application rest api's</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</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>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
让我们首先使用以下命令在 MySQL 服务器中创建一个数据库:
create database myblog
由于我们使用 MySQL 作为我们的数据库,我们需要配置数据库 URL、username 和 password,以便 Spring 可以在启动时与数据库建立连接。打开 src/main/resources/application.properties 文件并向其添加以下属性:
spring.datasource.url = jdbc:mysql://localhost:3306/myblog?useSSL=false&serverTimezone=UTC
spring.datasource.username = root
spring.datasource.password = root
# hibernate properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update
logging.level.org.springframework.security=DEBUG
让我们创建一个 Post
JPA 实体:
package com.springboot.blog.entity;
import lombok.*;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(
name = "posts", uniqueConstraints = {@UniqueConstraint(columnNames = {"title"})}
)
public class Post {
@Id
@GeneratedValue(
strategy = GenerationType.IDENTITY
)
private Long id;
@Column(name = "title", nullable = false)
private String title;
@Column(name = "description", nullable = false)
private String description;
@Column(name = "content", nullable = false)
private String content;
}
让我们创建一个 PostRepository
来检索和保存 MySQL 数据库中的帖子记录:
package com.springboot.blog.repository;
import com.springboot.blog.entity.Post;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PostRepository extends JpaRepository<Post, Long> {
}
Spring Boot 为 RESTful 服务的异常处理提供了一个很好的默认实现。
让我们快速了解一下 Spring Boot 提供的默认异常处理功能。
如果我们尝试从数据库中通过 id 获取帖子,并且该帖子在具有给定 id 的数据库中不存在,那么 Spring boot 将返回默认错误响应,如下所示:
{
"timestamp": "2021-02-28T14:15:18.250+00:00",
"status": 404,
"error": "Not Found",
"trace": "com.springboot.blog.exception.ResourceNotFoundEx
ception: Post not found with id : ’6’……...
"message": "Post not found with id : '6'",
"path": "/api/posts/6"
}
Spring Boot 提供的默认错误响应包含通常需要的所有详细信息。
但是,您可能希望为您的组织创建一个独立于框架的响应结构。在这种情况下,您可以定义特定的错误响应结构。例如:
{
"timestamp": "2021-02-28T14:13:47.572+00:00",
"message": "Post not found with id : '6'",
"details": "uri=/api/posts/6"
}
让我们编写代码来自定义 Spring boot REST API 的错误响应。
package com.springboot.blog.payload;
import java.util.Date;
public class ErrorDetails {
private Date timestamp;
private String message;
private String details;
public ErrorDetails(Date timestamp, String message, String details) {
this.timestamp = timestamp;
this.message = message;
this.details = details;
}
public Date getTimestamp() {
return timestamp;
}
public String getMessage() {
return message;
}
public String getDetails() {
return details;
}
}
让我们创建一个 ResourceNotFoundException.java 类并向其中添加以下内容:
package com.springboot.blog.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException{
private String resourceName;
private String fieldName;
private long fieldValue;
public ResourceNotFoundException(String resourceName, String fieldName, long fieldValue) {
super(String.format("%s not found with %s : '%s'", resourceName, fieldName, fieldValue)); // Post not found with id : 1
this.resourceName = resourceName;
this.fieldName = fieldName;
this.fieldValue = fieldValue;
}
public String getResourceName() {
return resourceName;
}
public String getFieldName() {
return fieldName;
}
public long getFieldValue() {
return fieldValue;
}
}
要使用 ErrorDetails 返回错误响应,让我们创建一个使用 @ControllerAdvice 注释的 GlobalExceptionHandler
类。此类在一个地方处理特定于异常的异常和全局异常。
package com.springboot.blog.exception;
import com.springboot.blog.payload.ErrorDetails;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.util.Date;
@ControllerAdvice
public class GlobalExceptionHandler {
// handle specific exceptions
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorDetails> handleResourceNotFoundException(ResourceNotFoundException exception,
WebRequest webRequest){
ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(),
webRequest.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
}
@ExceptionHandler(BlogAPIException.class)
public ResponseEntity<ErrorDetails> handleBlogAPIException(BlogAPIException exception,
WebRequest webRequest){
ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(),
webRequest.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
// global exceptions
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorDetails> handleGlobalException(Exception exception,
WebRequest webRequest){
ErrorDetails errorDetails = new ErrorDetails(new Date(), exception.getMessage(),
webRequest.getDescription(false));
return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
package com.springboot.blog.service;
import com.springboot.blog.payload.PostDto;
import com.springboot.blog.payload.PostResponse;
import java.util.List;
public interface PostService {
PostDto createPost(PostDto postDto);
PostResponse getAllPosts(int pageNo, int pageSize, String sortBy, String sortDir);
PostDto getPostById(long id);
PostDto updatePost(PostDto postDto, long id);
void deletePostById(long id);
}
package com.springboot.blog.service.impl;
import com.springboot.blog.entity.Post;
import com.springboot.blog.exception.ResourceNotFoundException;
import com.springboot.blog.payload.PostDto;
import com.springboot.blog.payload.PostResponse;
import com.springboot.blog.repository.PostRepository;
import com.springboot.blog.service.PostService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class PostServiceImpl implements PostService {
private PostRepository postRepository;
public PostServiceImpl(PostRepository postRepository) {
this.postRepository = postRepository;
}
@Override
public PostDto createPost(PostDto postDto) {
// convert DTO to entity
Post post = mapToEntity(postDto);
Post newPost = postRepository.save(post);
// convert entity to DTO
PostDto postResponse = mapToDTO(newPost);
return postResponse;
}
@Override
public PostResponse getAllPosts(int pageNo, int pageSize, String sortBy, String sortDir) {
Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
// create Pageable instance
Pageable pageable = PageRequest.of(pageNo, pageSize, sort);
Page<Post> posts = postRepository.findAll(pageable);
// get content for page object
List<Post> listOfPosts = posts.getContent();
List<PostDto> content= listOfPosts.stream().map(post -> mapToDTO(post)).collect(Collectors.toList());
PostResponse postResponse = new PostResponse();
postResponse.setContent(content);
postResponse.setPageNo(posts.getNumber());
postResponse.setPageSize(posts.getSize());
postResponse.setTotalElements(posts.getTotalElements());
postResponse.setTotalPages(posts.getTotalPages());
postResponse.setLast(posts.isLast());
return postResponse;
}
@Override
public PostDto getPostById(long id) {
Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id));
return mapToDTO(post);
}
@Override
public PostDto updatePost(PostDto postDto, long id) {
// get post by id from the database
Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id));
post.setTitle(postDto.getTitle());
post.setDescription(postDto.getDescription());
post.setContent(postDto.getContent());
Post updatedPost = postRepository.save(post);
return mapToDTO(updatedPost);
}
@Override
public void deletePostById(long id) {
// get post by id from the database
Post post = postRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Post", "id", id));
postRepository.delete(post);
}
// convert Entity into DTO
private PostDto mapToDTO(Post post){
PostDto postDto = new PostDto();
postDto.setId(post.getId());
postDto.setTitle(post.getTitle());
postDto.setDescription(post.getDescription());
postDto.setContent(post.getContent());
return postDto;
}
// convert DTO to entity
private Post mapToEntity(PostDto postDto){
Post post = new Post();
post.setTitle(postDto.getTitle());
post.setDescription(postDto.getDescription());
post.setContent(postDto.getContent());
return post;
}
}
package com.springboot.blog.controller;
import com.springboot.blog.payload.PostDto;
import com.springboot.blog.payload.PostResponse;
import com.springboot.blog.service.PostService;
import com.springboot.blog.utils.AppConstants;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/posts")
public class PostController {
private PostService postService;
public PostController(PostService postService) {
this.postService = postService;
}
// create blog post rest api
@PostMapping
public ResponseEntity<PostDto> createPost(@RequestBody PostDto postDto){
return new ResponseEntity<>(postService.createPost(postDto), HttpStatus.CREATED);
}
// get all posts rest api
@GetMapping
public PostResponse getAllPosts(
@RequestParam(value = "pageNo", defaultValue = AppConstants.DEFAULT_PAGE_NUMBER, required = false) int pageNo,
@RequestParam(value = "pageSize", defaultValue = AppConstants.DEFAULT_PAGE_SIZE, required = false) int pageSize,
@RequestParam(value = "sortBy", defaultValue = AppConstants.DEFAULT_SORT_BY, required = false) String sortBy,
@RequestParam(value = "sortDir", defaultValue = AppConstants.DEFAULT_SORT_DIRECTION, required = false) String sortDir
){
return postService.getAllPosts(pageNo, pageSize, sortBy, sortDir);
}
// get post by id
@GetMapping("/{id}")
public ResponseEntity<PostDto> getPostById(@PathVariable(name = "id") long id){
return ResponseEntity.ok(postService.getPostById(id));
}
// update post by id rest api
@PutMapping("/{id}")
public ResponseEntity<PostDto> updatePost(@RequestBody PostDto postDto, @PathVariable(name = "id") long id){
PostDto postResponse = postService.updatePost(postDto, id);
return new ResponseEntity<>(postResponse, HttpStatus.OK);
}
// delete post rest api
@DeleteMapping("/{id}")
public ResponseEntity<String> deletePost(@PathVariable(name = "id") long id){
postService.deletePostById(id);
return new ResponseEntity<>("Post entity deleted successfully.", HttpStatus.OK);
}
}
创建一个类 AppConstants 并向其添加以下代码:
package com.springboot.blog.utils;
public class AppConstants {
public static final String DEFAULT_PAGE_NUMBER = "0";
public static final String DEFAULT_PAGE_SIZE = "10";
public static final String DEFAULT_SORT_BY = "id";
public static final String DEFAULT_SORT_DIRECTION = "asc";
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.javaguides.net/2021/10/spring-boot-exception-handling-example.html
内容来源于网络,如有侵权,请联系作者删除!