首页
Preview

Spring 微服务全局异常处理和字段验证

大家好,希望你们都在学习和实现最前沿的技术。本文不是关于这些的,但它肯定会帮助你更好地处理错误,这有时比其他任何事情都更重要。

在本文中,我们将使用Java 8、maven构建Spring Boot微服务。

在此,我提供了代码仓库的Github链接,以防你需要它...

kousikpaul4u/spring-microservice-demo

本次课程的内容

  • 构建Spring Boot微服务
  • 添加控制器端点和服务
  • 添加全局异常处理以处理Spring验证
  • 添加自定义异常消息枚举
  • 从.properties文件中填充字段验证消息
  • 抛出所有服务和控制器异常
  • 没有甜点派对是不完整的。我们也将编写验证单元测试;)

1. 让我们设置基本的Spring微服务

打开 https://start.spring.io/

创建一个mavengradle项目,并添加以下依赖项。在本次课程中,我们使用的是maven项目。以下是本次课程所需的依赖项。

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>

   <groupId>com.demo.service</groupId>
   <artifactId>microservicedemo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>jar</packaging>

   <name>microservicedemo</name>
   <description>microservicedemo</description>

   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>1.5.12.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter</artifactId>
         <exclusions>
            <exclusion>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
         </exclusions>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-actuator</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>
      </dependency>
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <optional>true</optional>
      </dependency>

      <!-- Logging dependencies -->
      <dependency>
         <groupId>org.apache.logging.log4j</groupId>
         <artifactId>log4j-web</artifactId>
         <version>2.6.1</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-log4j2</artifactId>
      </dependency>
      <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
         <version>3.4</version>
      </dependency>

      <!-- Testing dependencies -->
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.mockito</groupId>
         <artifactId>mockito-core</artifactId>
         <version>1.10.19</version>
         <scope>test</scope>
      </dependency>
   </dependencies>

   <repositories>
      <repository>
         <id>maven.apache</id>
         <name>Maven apache</name>
         <url>https://repo.maven.apache.org/maven2/</url>
      </repository>
   </repositories>

</project>

我们将使用lombok来减少模型中不必要的getter/setter/constructor代码。如果你使用的是Intellij Idea,请按照以下步骤操作

  • 进入 File &gt; Settings &gt; Plugins
  • 点击 Browse repositories...
  • 搜索 Lombok Plugin
  • 点击 Install plugin
  • 重启 IntelliJ IDEA

2. 添加控制器端点和请求模型

controller → RestApiController.java

用于处理请求的控制器

package com.service.example.microservicedemo.Controller;package com.service.example.microservicedemo.Controller;

import com.service.example.microservicedemo.Model.Company;
import com.service.example.microservicedemo.Model.CompanyDetailsRequest;
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;

import javax.validation.Valid;

@RestController
@RequestMapping(path = "/api/company")
public class RestApiController {

    @PostMapping(path = "/all")
    public ResponseEntity<Company> getCompanyDetails(@Valid @RequestBody CompanyDetailsRequest request) {
        Company company = new Company();
        company.setId(request.getId());
        company.setName(request.getName());
        company.setMobile(request.getMobile());
        company.setLocation(request.getLocation());
        return new ResponseEntity(company, HttpStatus.OK);
    }
}

model → CompanyDetailsRequest.java

这是用于/api/company/save api端点的请求模型

package com.service.example.microservicedemo.Model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class CompanyDetailsRequest {

    @NotBlank(message = "{id.not-null}")
    @Size(min=2, max = 10, message = "{id.size}")
    private String id;

    @NotBlank(message = "{name.not-null}")
    private String name;

    @NotNull(message = "{mobile.not-null}")
    @Size(min=10, max=13, message = "{mobile.size}")
    @Pattern(regexp = "[\\s]*[0-9]*[1-9]+",message="{mobile.pattern}")
    private String mobile;

    private String location;
}

注意:我在@NotNull@Size验证中使用id. 我们将在CustomizedResponseEntityExceptionHandler.java中抛出和处理{id.NotEmpty}消息。{id.NotEmpty}将在FieldValidationMessages.properties中。

如何从.properties文件中获取值,我们将在本课程的后面讨论。

3. 添加全局异常处理程序

exception → CustomizedResponseEntityExceptionHandler.java

处理Spring @Valid抛出的所有异常的ExceptionHandler类。

package com.service.example.microservicedemo.Exception;import com.service.example.microservicedemo.Constants.StatusConstants;
import com.service.example.microservicedemo.Model.Response;
import com.service.example.microservicedemo.Model.Status;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;@ControllerAdvice
public class CustomizedResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
                                                                  HttpHeaders headers, HttpStatus status, WebRequest request) {
        Status errorDetail = new Status(StatusConstants.HttpConstants.CUSTOM_FIELD_VALIDATION.getCode(),
                ex.getBindingResult().getFieldError().getDefaultMessage());
        return new ResponseEntity(new Response(errorDetail, null), HttpStatus.BAD_REQUEST);
    }
}

注意:@ControllerAdvice需要处理所有控制器和服务抛出的所有异常。

_4. _添加自定义异常/错误消息枚举

constant →StatusConstants.java

在这里添加自定义异常消息

package com.service.example.microservicedemo.Constants;

import lombok.AllArgsConstructor;
import lombok.Getter;

public class StatusConstants {

    @Getter
    @AllArgsConstructor
    public enum HttpConstants {

        SUCCESS(1, "Success"),

        CUSTOM_FIELD_VALIDATION(2, null),

        INTERNAL_SERVER_ERROR(0, "System error! Please try after some time");

        private Integer code;

        private String desc;

    }
}

5. 从.properties文件中填充字段验证消息

config →WebConfig.java

你可以在资源文件夹中创建ValidationMessages.properties文件并添加所有属性,Spring将自动从该文件中读取。如果你需要从自定义命名的属性文件中读取,则按以下步骤操作。

需要这些配置才能在模型中加载.properties文件。

package com.service.example.microservicedemo.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.validation.Validator;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
    @Bean(name = "messageSource")
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource bean = new ReloadableResourceBundleMessageSource();
        bean.setBasename("classpath:FieldValidationMessages");
        bean.setDefaultEncoding("UTF-8");
        return bean;
    }

    @Bean(name = "validator")
    public LocalValidatorFactoryBean validator() {
        LocalValidatorFactoryBean bean = new LocalValidatorFactoryBean();
        bean.setValidationMessageSource(messageSource());
        return bean;
    }

    @Override
    public Validator getValidator() {
        return validator();
    }

}

你可以看到我们已经将FieldValidationMessages.properties文件名添加到

bean.setBasename("classpath:FieldValidationMessages");

resources → FieldValidationMessages.properties

我们将在这个.properties文件中添加所有验证消息

id.not-null=id is required
id.size=id should be between 2 to 10 characters
name.not-null=name is required
mobile.not-null=mobile is required
mobile.size=mobile should be 10-13 digits e.g. 9999999999

6. 抛出所有服务和控制器异常

现在所有的设置都已经完成,可以全局抛出验证异常。运行你的服务并通过postman/CURL发送以下请求

验证空id

请求:

POST /api/company/all HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 3effb13f-7a1c-4959-ab34–8461b388c97b{
 “id”: null,
 "name": "Google",
 "mobile": 9999999999,
 "location": "San Francisco"
}

响应:

{
   “status”: {
       “code”: 2,
       “message”: “id is required”
    },
    “data”: null
}

验证4位数移动号码

请求:

POST /api/company/all HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 3effb13f-7a1c-4959-ab34–8461b388c97b{
 “id”: "1234",
 "name": "Google",
 "mobile": 9999,
 "location": "San Francisco"
}

响应:

{
    "status": {
        "code": 2,
        "message": "mobile should be 10-13 digits (+919999999999)"
    },
    "data": null
}

验证适当的请求

请求:

POST /api/company/all HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Cache-Control: no-cache
Postman-Token: 3effb13f-7a1c-4959-ab34–8461b388c97b{
 “id”: "1234",
 "name": "Google",
 "mobile": 9999999999,
 "location": "San Francisco"
}

响应:

{
    "id": "1234",
    "name": "Google",
    "mobile": "9999999999",
    "location": "San Fransisco"
}

最好编写单元测试用例来覆盖几乎所有可想象的可能性。

7. 验证单元测试

test → FieldValidationTest.java

package com.service.example.microservicedemo;

import com.service.example.microservicedemo.Model.CompanyDetailsRequest;
import com.service.example.microservicedemo.Util.TestUtils;
import org.hibernate.validator.HibernateValidator;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

@RunWith(MockitoJUnitRunner.class)
public class FieldValidationTest {

    private LocalValidatorFactoryBean localValidatorFactory;

    @Before
    public void setUp() {
        localValidatorFactory = new LocalValidatorFactoryBean();
        localValidatorFactory.setProviderClass(HibernateValidator.class);
        localValidatorFactory.afterPropertiesSet();

    }

    /**
     * Test with null id
     * Expected: {id.not-null}
     */
    @Test
    public void idIsNull() {
        final CompanyDetailsRequest payload = new CompanyDetailsRequest(null, "Test", "9999999999", "India");
        assertEquals("{id.not-null}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with empty id
     * Expected: {id.not-null}
     */
    @Test
    public void idIsEmpty() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("", "Test", "9999999999", "India");
        assertEquals("{id.not-null}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with 14 digit id
     * Expected: {id.size}
     */
    @Test
    public void idIsFourteenDigit() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("C0000000000123", "Test", "9999999999",  "India");
        assertEquals("{id.size}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with null mobile
     * Expected: {mobile.not-null}
     */
    @Test
    public void mobileIsNull() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("C12345", "Test", null,  "India");
        assertEquals("{mobile.not-null}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with 4 digit mobile
     * Expected: {mobile.size}
     */
    @Test
    public void mobileIsFourDigit() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("C12345", "Test", "9999",  "India");
        assertEquals("{mobile.size}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with 14 digit mobile
     * Expected: {mobile.size}
     */
    @Test
    public void mobileIsFourteenDigit() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("C12345", "Test", "99999999999999",  "India");
        assertEquals("{mobile.size}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with character mobile
     * Expected: {mobile.pattern}
     */
    @Test
    public void mobileIsNotPositiveDigits() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("C12345", "Test", "ABCD9999999",  "India");
        assertEquals("{mobile.pattern}", TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }

    /**
     * Test with proper id
     * Expected: status 200
     */
    @Test
    public void successPayload() {
        CompanyDetailsRequest payload = new CompanyDetailsRequest("9999999999", "Test", "9999999999", "India");
        assertNull(TestUtils.getFieldErrorMessageKey(payload, localValidatorFactory));
    }
}

你需要一个FieldValidationTest的Util类。

测试 → 工具 → TestUtils.java

package com.service.example.microservicedemo.Util;

import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import javax.validation.ConstraintViolation;
import java.util.Set;

public class TestUtils {
    public static String getFieldErrorMessageKey(Object payload, LocalValidatorFactoryBean localValidatorFactory) {
        Set<ConstraintViolation<Object>> constraintViolations = localValidatorFactory.validate(payload);
        return constraintViolations.size() > 0 ? constraintViolations.iterator().next().getMessageTemplate() : null;
    }
}

译自:https://medium.com/@kousikpaul/microservice-global-exception-handling-and-field-validations-3fc955a80692

版权声明:本文内容由TeHub注册用户自发贡献,版权归原作者所有,TeHub社区不拥有其著作权,亦不承担相应法律责任。 如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

点赞(0)
收藏(0)
阿波
The minute I see you, I want your clothes gone!

评论(0)

添加评论