Spring Boot中的异常处理和验证
#java #springboot #validation #exceptionhandling

处理例外,逻辑错误并向客户提供有意义的消息非常重要,以避免Springboot中的RESTFUL API异常终止。

今天,我们将学习如何实施全球异常处理以及班级级别的异常处理。

先决条件:您必须了解Springboot和Restful API创建,JPA(Java Persistance API),以及在Java Core中进行异常处理的一些知识。

我们将使用 java-17 Spring Initilizr

创建春季启动模板项目

我们需要该项目的以下依赖项。
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>3.0.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.bukhari</groupId>
    <artifactId>exceptionhandling-validation</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>exceptionhandling-validation</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</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-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </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>
            </plugin>
        </plugins>
    </build>

</project>

application.properties文件

#Dabase Configuration
spring.application.name=Exception-Handling-Application
server.port=8082
spring.datasource.url=jdbc:mysql://localhost:3306/libdb
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1

spring.jpa.hibernate.naming-strategy=org.hibernate.dialect.MySQL8Dialect
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.formate_sql=true

班级级别异常处理

代码的层次结构

The Hierarchy of java files in an IDE

实体书

package com.bukhari.exceptionhandlingvalidation.entity;


import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Book {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String author;
    private Double price;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }
}

bookrepository

确保您从Entity.book软件包中导入本书,而不是从Java.awt.print

package com.bukhari.exceptionhandlingvalidation.repository;

import com.bukhari.exceptionhandlingvalidation.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends JpaRepository<Book, Long> {

}

我们要处理的例外是

  1. 书籍名称不能为空。
  2. 书籍作者名称不能为空。
  3. 书籍的价格必须为$ 10或大于10美元。 (假设价格始终是非空的)。

书籍服务

package com.bukhari.exceptionhandlingvalidation.service;

import com.bukhari.exceptionhandlingvalidation.classexception.LibBusinessException;
import com.bukhari.exceptionhandlingvalidation.entity.Book;
import com.bukhari.exceptionhandlingvalidation.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookService {

    @Autowired
    private BookRepository bookRepository;

    public Book addBook(Book book) {

        //If the Name of the book is Empty
        if (book.getName().isEmpty()) {
            throw new LibBusinessException("801", "The Name of the Book can't be Empty");
        }
        //If the Name of the Author is Empty
        if (book.getAuthor().isEmpty()) {
            throw new LibBusinessException("802", "The Author's Name can't be Empty");
        }

        //If the Price is less than 10
        if (book.getPrice().intValue() < 10) {
            throw new LibBusinessException("803", "The Price of the Book must be $10 or greater than $10");
        }
        return bookRepository.save(book);
    }

}

libbusinessException

package com.bukhari.exceptionhandlingvalidation.classexception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class LibBusinessException extends RuntimeException {

    private String errorCode;
    private String errorMessage;

    public LibBusinessException(String errorCode, String errorMessage) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public String getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(String errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
}

bookController

package com.bukhari.exceptionhandlingvalidation.controller;

import com.bukhari.exceptionhandlingvalidation.classexception.LibBusinessException;
import com.bukhari.exceptionhandlingvalidation.entity.Book;
import com.bukhari.exceptionhandlingvalidation.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("digital/lib")
public class LibraryController {
    @Autowired
    private BookService bookService;

    @PostMapping("/book")
    public ResponseEntity<?> saveBook(@RequestBody Book book) {
        try {
            Book savedBook = bookService.addBook(book);
            return new ResponseEntity<Book>(savedBook, HttpStatus.CREATED);
        } catch (LibBusinessException e) {
            LibBusinessException libex = new LibBusinessException(e.getErrorCode(), e.getErrorMessage());
            return new ResponseEntity<LibBusinessException>(libex, HttpStatus.BAD_REQUEST);
        } catch (Exception e) {
            LibBusinessException libex = new LibBusinessException("804", "Something went wrong in controller");
            return new ResponseEntity<LibBusinessException>(libex, HttpStatus.BAD_REQUEST);
        }
    }
}

如果书名为空

Postman请求和响应

Request and Response

如果书籍作者名称为空

Postman请求和响应

Request and Response

如果书价小于$ 10

Postman请求和响应

Request and Response

如果本书实体为null
Request Response

使用ControllerAdvice验证和全局异常处理

让我们现在了解验证和全局异常处理
为此,我们将采用Appuser实体。 (pom.xml是相同的)。

appuser实体

package com.bukhari.exceptionhandlingvalidation.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class AppUser {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int userId;
    private String name;
    private String email;
    private String mobile;
    private String gender;
    private int age;
    private String nationality;

    public AppUser() {
    }

    public AppUser(int userId, String name, String email, String mobile, String gender, int age, String nationality) {
        this.userId = userId;
        this.name = name;
        this.email = email;
        this.mobile = mobile;
        this.gender = gender;
        this.age = age;
        this.nationality = nationality;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }
}

请求appuser

package com.bukhari.exceptionhandlingvalidation.request;

import jakarta.validation.constraints.*;

/**
 * Global Exception Handling AppUserRequest
 */
public class AppUserRequest {
    @NotNull(message = "username must not be null")
    private String name;
    @Email(message = "Invalid email address, Please provide a valid email")
    private String email;
    @Pattern(regexp = "^\\d{10}$", message = "Invalid mobile number, Please Provide a Valid Number")
    private String mobile;
    private String gender;
    @Min(value = 18, message = "Age must be 18 or greater")
    @Max(value = 60, message = "Age must be 60 or less")
    private int age;
    @NotBlank
    private String nationality;

    public AppUserRequest(String name, String email, String mobile, String gender, int age, String nationality) {
        this.name = name;
        this.email = email;
        this.mobile = mobile;
        this.gender = gender;
        this.age = age;
        this.nationality = nationality;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getNationality() {
        return nationality;
    }

    public void setNationality(String nationality) {
        this.nationality = nationality;
    }
}


我们将验证并处理以下异常

  1. 名称不得为null。
  2. 电子邮件必须有效。
  3. 手机号码必须有效。
  4. 年龄必须在18至60之间。
  5. 国籍不能是空白的(非空和非净的)。
  6. 如果找不到用户,则将自定义类映射到异常。

为此,我们将@RestControllerAdvice用作全局异常处理程序,与班级方法相比,它是一种更好的方法。 @RestControllerAdvice映射到所有控制器,一旦触发了请求,请参阅任何映射匹配项的建议检查,并相应地发送响应。由于Spring为我们提供了@ExceptionHandler注释以拦截任何Java以及逻辑异常,因此我们可以根据自己的要求实施。

RestControllerRadvice

package com.bukhari.exceptionhandlingvalidation.globalexception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

/**
 * Global Exception Handling ControllerAdvice
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleInvalidArgument(MethodArgumentNotValidException ex) {
        Map<String, String> errorMap = new HashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error -> {
            errorMap.put(error.getField(), error.getDefaultMessage());
        });
        return errorMap;
    }

    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(UserNotFoundException.class)
    public Map<String, String> handleBusinessException(UserNotFoundException ex) {
        Map<String, String> errorMap = new HashMap<>();
        errorMap.put("errorMessage", ex.getMessage());
        return errorMap;
    }

}

usernotfoundException的自定义类

package com.bukhari.exceptionhandlingvalidation.globalexception;

/**
 * Global Exception Handling UserNotFoundException
 */
public class UserNotFoundException  extends Exception{
    public UserNotFoundException(String message) {
        super(message);
    }
}

appuserrepository

package com.bukhari.exceptionhandlingvalidation.repository;

import com.bukhari.exceptionhandlingvalidation.entity.AppUser;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
 * Global Exception Handling AppUserRepository
 */
@Repository
public interface AppUserRepository extends JpaRepository<AppUser,Integer> {

}

AppuserService

package com.bukhari.exceptionhandlingvalidation.service;

import com.bukhari.exceptionhandlingvalidation.entity.AppUser;
import com.bukhari.exceptionhandlingvalidation.globalexception.UserNotFoundException;
import com.bukhari.exceptionhandlingvalidation.repository.AppUserRepository;
import com.bukhari.exceptionhandlingvalidation.request.AppUserRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
 * Global Exception Handling AppUserService
 */
@Service
public class AppUserService {

    @Autowired
    private AppUserRepository appUserRepository;


    public AppUser saveUser(AppUserRequest userRequest) {
        AppUser user = new AppUser(0, userRequest.getName(), userRequest.getEmail(),
                userRequest.getMobile(), userRequest.getGender(), userRequest.getAge(), userRequest.getNationality());
        return appUserRepository.save(user);
    }


    public List<AppUser> getALlUsers() {
        return appUserRepository.findAll();
    }


    public AppUser getUser(int id) throws UserNotFoundException {
        Optional<AppUser> optional = appUserRepository.findById(id);
        if (optional.isPresent()) {
            return optional.get();
        } else {
            throw new UserNotFoundException("user not found with id : " + id);
        }
    }
}

appusercontroller

package com.bukhari.exceptionhandlingvalidation.controller;


import com.bukhari.exceptionhandlingvalidation.entity.AppUser;
import com.bukhari.exceptionhandlingvalidation.globalexception.UserNotFoundException;
import com.bukhari.exceptionhandlingvalidation.request.AppUserRequest;
import com.bukhari.exceptionhandlingvalidation.service.AppUserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class AppUserController {

    @Autowired
    private AppUserService appUserService;

    @PostMapping("/signup")
    public ResponseEntity<AppUser> saveUser(@RequestBody @Valid AppUserRequest userRequest){
        return new ResponseEntity<>(appUserService.saveUser(userRequest), HttpStatus.CREATED);
    }

    @GetMapping("/fetchAll")
    public ResponseEntity<List<AppUser>> getAllUsers(){
        return ResponseEntity.ok(appUserService.getALlUsers());
    }

    @GetMapping("/{id}")
    public ResponseEntity<AppUser> getUser(@PathVariable int id) throws UserNotFoundException {
        return ResponseEntity.ok(appUserService.getUser(id));
    }
}

@Valid注释用于验证有效载荷

邮递员请求和响应如下。

如果数据无效

Request and Response

如果找不到用户。

Request and Response

希望该教程有帮助。您从中学到了一些东西。如果有帮助,请确保遵循和喜欢。