แม้ว่านี่จะเป็นคำถามที่เก่ากว่า แต่ฉันต้องการแบ่งปันความคิดของฉันเกี่ยวกับเรื่องนี้ ฉันหวังว่ามันจะเป็นประโยชน์กับคุณบางคน
ขณะนี้ฉันกำลังสร้าง REST API ซึ่งใช้ประโยชน์จาก Spring Boot 1.5.2.RELEASE กับ Spring Framework 4.3.7.RELEASE ฉันใช้วิธีการกำหนดค่า Java (ตรงข้ามกับการกำหนดค่า XML) นอกจากนี้โครงการของฉันใช้กลไกการจัดการข้อยกเว้นส่วนกลางโดยใช้@RestControllerAdvice
หมายเหตุประกอบ (ดูภายหลังด้านล่าง)
โครงการของฉันมีข้อกำหนดเช่นเดียวกับของคุณ: ฉันต้องการให้ REST API ของฉันส่งคืนHTTP 404 Not Found
พร้อมกับเพย์โหลด JSON ประกอบในการตอบกลับ HTTP ไปยังไคลเอนต์ API เมื่อพยายามส่งคำขอไปยัง URL ที่ไม่มีอยู่ ในกรณีของฉัน JSON ส่วนของข้อมูลจะมีลักษณะดังนี้ (ซึ่งแตกต่างอย่างชัดเจนจากค่าเริ่มต้นของ Spring Boot คือ btw.):
{
"code": 1000,
"message": "No handler found for your request.",
"timestamp": "2017-11-20T02:40:57.628Z"
}
ในที่สุดฉันก็ทำให้มันทำงาน นี่คือภารกิจหลักที่คุณต้องทำโดยย่อ:
- ตรวจสอบให้แน่ใจว่าสิ่ง
NoHandlerFoundException
นั้นถูกโยนทิ้งหากลูกค้า API เรียก URL ที่ไม่มีวิธีจัดการ (ดูขั้นตอนที่ 1 ด้านล่าง)
- สร้างคลาสข้อผิดพลาดที่กำหนดเอง (ในกรณีของฉัน
ApiError
) ซึ่งมีข้อมูลทั้งหมดที่ควรส่งคืนไปยังไคลเอนต์ API (ดูขั้นตอนที่ 2)
- สร้างตัวจัดการข้อยกเว้นที่ตอบสนองใน
NoHandlerFoundException
และส่งกลับข้อผิดพลาดที่เหมาะสมไปยังไคลเอนต์ API (ดูขั้นตอนที่ 3)
- เขียนทดสอบเพื่อให้แน่ใจว่าใช้งานได้ (ดูขั้นตอนที่ 4)
ตกลงตอนนี้ไปที่รายละเอียด:
ขั้นตอนที่ 1: กำหนดค่า application.properties
ฉันต้องเพิ่มการตั้งค่าการกำหนดค่าสองรายการต่อไปนี้ในapplication.properties
ไฟล์ของโครงการ:
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
สิ่งนี้ทำให้แน่ใจได้ว่าNoHandlerFoundException
จะเกิดขึ้นในกรณีที่ลูกค้าพยายามเข้าถึง URL ที่ไม่มีวิธีการควบคุมอยู่ซึ่งจะสามารถจัดการคำขอได้
ขั้นตอนที่ 2: สร้างคลาสสำหรับข้อผิดพลาด API
ฉันสร้างคลาสที่คล้ายกับที่แนะนำในบทความนี้บนบล็อกของ Eugen Paraschiv คลาสนี้แสดงถึงข้อผิดพลาด API ข้อมูลนี้จะถูกส่งไปยังลูกค้าในส่วนการตอบสนอง HTTP ในกรณีที่เกิดข้อผิดพลาด
public class ApiError {
private int code;
private String message;
private Instant timestamp;
public ApiError(int code, String message) {
this.code = code;
this.message = message;
this.timestamp = Instant.now();
}
public ApiError(int code, String message, Instant timestamp) {
this.code = code;
this.message = message;
this.timestamp = timestamp;
}
// Getters and setters here...
}
ขั้นตอนที่ 3: สร้าง / กำหนดค่าตัวจัดการข้อยกเว้นส่วนกลาง
ฉันใช้คลาสต่อไปนี้เพื่อจัดการข้อยกเว้น (เพื่อความเรียบง่ายฉันได้ลบข้อความสั่งการนำเข้ารหัสการบันทึกและโค้ดอื่น ๆ ที่ไม่เกี่ยวข้อง):
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(NoHandlerFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiError noHandlerFoundException(
NoHandlerFoundException ex) {
int code = 1000;
String message = "No handler found for your request.";
return new ApiError(code, message);
}
// More exception handlers here ...
}
ขั้นตอนที่ 4: เขียนการทดสอบ
ฉันต้องการตรวจสอบให้แน่ใจว่า API จะส่งคืนข้อความแสดงข้อผิดพลาดที่ถูกต้องไปยังไคลเอนต์ที่โทรหาเสมอแม้ในกรณีที่เกิดข้อผิดพลาด ดังนั้นฉันจึงเขียนแบบทดสอบนี้:
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SprintBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ActiveProfiles("dev")
public class GlobalExceptionHandlerIntegrationTest {
public static final String ISO8601_DATE_REGEX =
"^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$";
@Autowired
private MockMvc mockMvc;
@Test
@WithMockUser(roles = "DEVICE_SCAN_HOSTS")
public void invalidUrl_returnsHttp404() throws Exception {
RequestBuilder requestBuilder = getGetRequestBuilder("/does-not-exist");
mockMvc.perform(requestBuilder)
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.code", is(1000)))
.andExpect(jsonPath("$.message", is("No handler found for your request.")))
.andExpect(jsonPath("$.timestamp", RegexMatcher.matchesRegex(ISO8601_DATE_REGEX)));
}
private RequestBuilder getGetRequestBuilder(String url) {
return MockMvcRequestBuilders
.get(url)
.accept(MediaType.APPLICATION_JSON);
}
@ActiveProfiles("dev")
คำอธิบายประกอบสามารถด้านซ้ายออกไป ฉันใช้มันเฉพาะเมื่อฉันทำงานกับโปรไฟล์ที่แตกต่างกัน นี่RegexMatcher
คือHamcrest matcher ที่กำหนดเองที่ฉันใช้เพื่อจัดการกับเขตข้อมูลบันทึกเวลาได้ดียิ่งขึ้น นี่คือรหัส (ฉันพบที่นี่ ):
public class RegexMatcher extends TypeSafeMatcher<String> {
private final String regex;
public RegexMatcher(final String regex) {
this.regex = regex;
}
@Override
public void describeTo(final Description description) {
description.appendText("matches regular expression=`" + regex + "`");
}
@Override
public boolean matchesSafely(final String string) {
return string.matches(regex);
}
// Matcher method you can call on this matcher class
public static RegexMatcher matchesRegex(final String string) {
return new RegexMatcher(regex);
}
}
บางบันทึกเพิ่มเติมจากด้านข้างของฉัน:
- ในโพสต์อื่น ๆ อีกมากมายบน StackOverflow ผู้คนแนะนำให้ตั้งค่า
@EnableWebMvc
คำอธิบายประกอบ นี่ไม่จำเป็นในกรณีของฉัน
- วิธีนี้ใช้ได้ดีกับ MockMvc (ดูการทดสอบด้านบน)