大家好,希望你们都在学习和实现最前沿的技术。本文不是关于这些的,但它肯定会帮助你更好地处理错误,这有时比其他任何事情都更重要。
在本文中,我们将使用Java 8、maven构建Spring Boot微服务。
在此,我提供了代码仓库的Github链接,以防你需要它...
kousikpaul4u/spring-microservice-demo
本次课程的内容
- 构建Spring Boot微服务
- 添加控制器端点和服务
- 添加全局异常处理以处理Spring验证
- 添加自定义异常消息枚举
- 从.properties文件中填充字段验证消息
- 抛出所有服务和控制器异常
- 没有甜点派对是不完整的。我们也将编写验证单元测试;)
1. 让我们设置基本的Spring微服务
创建一个maven或gradle项目,并添加以下依赖项。在本次课程中,我们使用的是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 > Settings > 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;
}
}
评论(0)