การจัดการข้อยกเว้นบริการ Spring Boot REST


172

ฉันกำลังพยายามตั้งค่าเซิร์ฟเวอร์บริการ REST ขนาดใหญ่ เรากำลังใช้ Spring Boot 1.2.1 Spring 4.1.5 และ Java 8 ตัวควบคุมของเรากำลังใช้งาน @RestController และคำอธิบายประกอบ @RequestMapping มาตรฐาน

ปัญหาของฉันที่ฤดูใบไม้ผลิ Boot /errorตั้งค่าการเปลี่ยนเส้นทางเริ่มต้นสำหรับข้อยกเว้นในการควบคุม จากเอกสาร:

Spring Boot จัดให้มีการแมป / ข้อผิดพลาดตามค่าเริ่มต้นที่จัดการข้อผิดพลาดทั้งหมดอย่างสมเหตุสมผลและมีการลงทะเบียนเป็นหน้าข้อผิดพลาด 'สากล' ในคอนเทนเนอร์ servlet

มาจากหลายปีที่เขียนแอปพลิเคชัน REST ด้วย Node.js นี่คือสิ่งที่สมเหตุสมผล ข้อยกเว้นใด ๆ ที่จุดปลายบริการสร้างขึ้นควรกลับมาในการตอบกลับ ฉันไม่สามารถเข้าใจได้ว่าทำไมคุณถึงส่งการเปลี่ยนเส้นทางไปยังผู้ใช้ Angular หรือ JQuery SPA ซึ่งน่าจะเป็นคำตอบเท่านั้นและไม่สามารถหรือไม่ดำเนินการใด ๆ ในการเปลี่ยนเส้นทาง

สิ่งที่ฉันต้องการจะทำคือการตั้งค่าตัวจัดการข้อผิดพลาดทั่วโลกที่สามารถยกเว้น - อย่างใดอย่างหนึ่งโยนออกมาจากวิธีการทำแผนที่การร้องขอหรือสร้างขึ้นโดยอัตโนมัติโดยฤดูใบไม้ผลิ (404 ถ้าไม่พบวิธีจัดการสำหรับลายเซ็นเส้นทางขอ) การตอบสนองข้อผิดพลาดในรูปแบบมาตรฐาน (400, 500, 503, 404) ไปยังไคลเอนต์โดยไม่มีการเปลี่ยนเส้นทาง MVC โดยเฉพาะอย่างยิ่งเราจะใช้ข้อผิดพลาดเข้าสู่ระบบไปยัง NoSQL ด้วย UUID จากนั้นกลับไปที่รหัสข้อผิดพลาด HTTP ที่ถูกต้องของไคลเอ็นต์พร้อมกับ UUID ของรายการบันทึกในเนื้อความ JSON

เอกสารได้คลุมเครือในการทำเช่นนี้ ดูเหมือนว่าคุณต้องสร้างการใช้งานErrorControllerของคุณเองหรือใช้ControllerAdviceในบางกรณี แต่ตัวอย่างทั้งหมดที่ฉันเห็นยังรวมถึงการส่งต่อการตอบสนองต่อการทำแผนที่ข้อผิดพลาดบางประเภทซึ่งไม่ได้ช่วยอะไร ตัวอย่างอื่น ๆ แนะนำให้คุณต้องแสดงรายการข้อยกเว้นทุกประเภทที่คุณต้องการจัดการแทนที่จะแสดงเพียง "Throwable" และรับทุกอย่าง

มีใครบอกฉันได้ไหมว่าฉันพลาดอะไรหรือชี้ทิศทางที่ถูกต้องให้ฉันเกี่ยวกับวิธีการทำเช่นนี้โดยไม่แนะนำให้ใช้โซ่ที่ Node.js จะจัดการได้ง่ายขึ้น?


6
ลูกค้าไม่เคยส่งการเปลี่ยนเส้นทางจริงๆ การเปลี่ยนเส้นทางได้รับการจัดการภายในโดย servlet container (เช่น Tomcat)
OrangeDog

1
การลบคำอธิบายประกอบ @ResponseStatus บนตัวจัดการข้อยกเว้นของฉันคือสิ่งที่ฉันต้องการ ดูstackoverflow.com/questions/35563968/…
pmorken

คำตอบ:


131

คำตอบใหม่ (2016-04-20)

การใช้ Spring Boot 1.3.1 การปลดปล่อย

ใหม่ขั้นตอนที่ 1 -เป็นการง่ายและล่วงล้ำน้อยกว่าในการเพิ่มคุณสมบัติต่อไปนี้ไปยังแอ็พพลิเคชันคุณสมบัติ:

spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

ง่ายกว่าการแก้ไขอินสแตนซ์ DispatcherServlet ที่มีอยู่ (ดังด้านล่าง)! - JO '

หากทำงานกับแอปพลิเคชัน RESTful เต็มรูปแบบเป็นสิ่งสำคัญมากที่จะต้องปิดใช้งานการแมปทรัพยากรคงที่โดยอัตโนมัติเนื่องจากหากคุณใช้การกำหนดค่าเริ่มต้นของ Spring Boot สำหรับการจัดการทรัพยากรคงที่จากนั้นตัวจัดการทรัพยากรจะจัดการคำขอ ** ซึ่งหมายความว่าจะรับคำขอใด ๆ ที่ไม่ได้รับการจัดการโดยตัวจัดการอื่น ๆ ในแอปพลิเคชัน) ดังนั้น servlet dispatcher จะไม่ได้รับโอกาสยกเว้น


คำตอบใหม่ (2015-12-04)

ใช้ Spring Boot 1.2.7 ปล่อย

ใหม่ขั้นตอนที่ 1 -ฉันพบวิธีการก้าวก่ายการตั้งค่าสถานะ "throExceptionIfNoHandlerFound" น้อยลง แทนที่รหัสการแทนที่ DispatcherServlet ด้านล่าง (ขั้นตอนที่ 1) ด้วยสิ่งนี้ในคลาสการเริ่มต้นแอปพลิเคชันของคุณ:

@ComponentScan()
@EnableAutoConfiguration
public class MyApplication extends SpringBootServletInitializer {
    private static Logger LOG = LoggerFactory.getLogger(MyApplication.class);
    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }

ในกรณีนี้เรากำลังตั้งค่าสถานะบน DispatcherServlet ที่มีอยู่ซึ่งเก็บรักษาการกำหนดค่าอัตโนมัติโดยเฟรมเวิร์ก Spring Boot

อีกสิ่งหนึ่งที่ฉันได้พบ - คำอธิบายประกอบ @EnableWebMvc นั้นร้ายแรงสำหรับ Spring Boot ใช่คำอธิบายประกอบนั้นช่วยให้สามารถจับข้อยกเว้นตัวควบคุมทั้งหมดได้ตามที่อธิบายไว้ด้านล่าง แต่มันยังฆ่าการกำหนดค่าอัตโนมัติที่เป็นประโยชน์มากมายซึ่งปกติแล้ว Spring Boot จะจัดเตรียมไว้ให้ ใช้คำอธิบายประกอบนั้นด้วยความระมัดระวังอย่างยิ่งเมื่อคุณใช้ Spring Boot


คำตอบเดิม:

หลังจากค้นคว้าข้อมูลเพิ่มเติมและติดตามวิธีแก้ไขปัญหาที่โพสต์ไว้ที่นี่ (ขอบคุณสำหรับความช่วยเหลือ!) และไม่มีการติดตามรันไทม์เล็กน้อยในรหัส Spring ในที่สุดฉันก็พบการกำหนดค่าที่จะจัดการข้อยกเว้นทั้งหมด (ไม่ใช่ข้อผิดพลาด แต่อ่านต่อ) รวมถึงยุค 404

ขั้นตอนที่ 1 -แจ้งให้ SpringBoot หยุดใช้ MVC สำหรับสถานการณ์ "ตัวจัดการไม่พบ" เราต้องการให้ Spring โยนข้อยกเว้นแทนการส่งมุมมองไปที่ "/ error" กลับไปยังไคลเอนต์ ในการทำสิ่งนี้คุณต้องมีรายการในหนึ่งในคลาสการกำหนดค่าของคุณ:

// NEW CODE ABOVE REPLACES THIS! (2015-12-04)
@Configuration
public class MyAppConfig {
    @Bean  // Magic entry 
    public DispatcherServlet dispatcherServlet() {
        DispatcherServlet ds = new DispatcherServlet();
        ds.setThrowExceptionIfNoHandlerFound(true);
        return ds;
    }
}

ข้อเสียของสิ่งนี้คือมันแทนที่ servlet dispatcher เริ่มต้น นี่ยังไม่เป็นปัญหาสำหรับเราโดยไม่มีผลข้างเคียงหรือปัญหาการดำเนินการปรากฏขึ้น หากคุณกำลังจะทำอะไรอย่างอื่นกับ servlet dispatcher ด้วยเหตุผลอื่น ๆ นี่คือสถานที่ที่จะทำพวกเขา

ขั้นตอนที่ 2 -ตอนนี้สปริงบูตจะโยนข้อยกเว้นเมื่อไม่พบตัวจัดการข้อยกเว้นนั้นสามารถจัดการกับคนอื่น ๆ ในตัวจัดการข้อยกเว้นแบบรวม:

@EnableWebMvc
@ControllerAdvice
public class ServiceExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    ResponseEntity<Object> handleControllerException(HttpServletRequest req, Throwable ex) {
        ErrorResponse errorResponse = new ErrorResponse(ex);
        if(ex instanceof ServiceException) {
            errorResponse.setDetails(((ServiceException)ex).getDetails());
        }
        if(ex instanceof ServiceHttpException) {
            return new ResponseEntity<Object>(errorResponse,((ServiceHttpException)ex).getStatus());
        } else {
            return new ResponseEntity<Object>(errorResponse,HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        Map<String,String> responseBody = new HashMap<>();
        responseBody.put("path",request.getContextPath());
        responseBody.put("message","The URL you have reached is not in service at this time (404).");
        return new ResponseEntity<Object>(responseBody,HttpStatus.NOT_FOUND);
    }
    ...
}

โปรดทราบว่าฉันคิดว่าคำอธิบายประกอบ "@EnableWebMvc" มีความสำคัญที่นี่ ดูเหมือนว่าไม่มีสิ่งใดที่สามารถใช้งานได้หากไม่มี และนั่นคือ - แอพ Spring boot ของคุณจะจับข้อยกเว้นทั้งหมดรวมถึง 404s ในคลาสตัวจัดการด้านบนและคุณสามารถทำได้ตามที่คุณต้องการ

หนึ่งจุดสุดท้าย - ดูเหมือนจะไม่มีทางที่จะทำให้เรื่องนี้เกิดข้อผิดพลาดได้ ฉันมีความคิดแปลกประหลาดในการใช้แง่มุมเพื่อตรวจจับข้อผิดพลาดและเปลี่ยนเป็นข้อยกเว้นที่โค้ดด้านบนสามารถจัดการได้ แต่ฉันยังไม่มีเวลาลองใช้งานจริง หวังว่านี่จะช่วยใครซักคน

ความคิดเห็น / การแก้ไข / การปรับปรุงใด ๆ จะได้รับการชื่นชม


แทนการสร้าง servlet bean ใหม่ dispatcher คุณสามารถพลิกการตั้งค่าสถานะในตัวประมวลผลการโพสต์: YourClass ใช้ BeanPostProcessor {... `Object สาธารณะ postProcessBeforeInitialization รับ 404 ก่อนที่ตัวจัดการข้อยกเว้นของเราจะเตะใน ((DispatcherServlet) bean) } คืนถั่ว } public object postProcessAfterInitialization (Object bean, String beanName) ส่ง BeansException {return bean; }
wwadge

1
ฉันมีปัญหานี้ แต่การปรับแต่ง DispatcherServlet ไม่ทำงานสำหรับฉัน จำเป็นต้องใช้เวทย์มนตร์เพิ่มเติมหรือไม่สำหรับการบูทเพื่อใช้บีนพิเศษและการตั้งค่านี้?
IanGilham

3
@IanGilham ฉันก็ไม่ได้เช่นนี้กับ Spring Boot 1.2.7 ฉันยังไม่ได้รับ@ExceptionHandlerวิธีการใด ๆ ที่ เรียกว่าเมื่อวางลงใน@ControllerAdviceชั้นเรียนแม้ว่าพวกเขาจะทำงานได้อย่างถูกต้องหากวางไว้ใน@RestControllerชั้นเรียน @EnableWebMvcอยู่ใน@ControllerAdviceและ@Configuration(ฉันทดสอบทุกชุด) ความคิดหรือตัวอย่างการทำงาน? //
@Andy

1
ใครก็ตามที่อ่านคำถามนี้และคำตอบควรมีลักษณะที่สอดคล้องกันฉบับ SpringBoot บนGitHub
FrVaBe

1
ไม่แน่ใจ @agpt ฉันมีโปรเจ็กต์ภายในที่ฉันสามารถเลื่อนได้สูงถึง 1.3.0 และดูว่าเอฟเฟกต์อะไรบ้างที่อยู่ในการตั้งค่าของฉัน
ogradyjd

41

ด้วย Spring Boot 1.4+ คลาส cool ใหม่ที่เพิ่มการจัดการข้อยกเว้นได้ง่ายขึ้นซึ่งช่วยในการลบรหัสสำเร็จรูป

ใหม่@RestControllerAdviceที่มีให้สำหรับการจัดการข้อยกเว้นก็คือการรวมกันของและ@ControllerAdvice @ResponseBodyคุณสามารถลบ@ResponseBodyเกี่ยวกับ@ExceptionHandlerวิธีการเมื่อใช้คำอธิบายประกอบใหม่นี้

กล่าวคือ

@RestControllerAdvice
public class GlobalControllerExceptionHandler {

    @ExceptionHandler(value = { Exception.class })
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ApiErrorResponse unknownException(Exception ex, WebRequest req) {
        return new ApiErrorResponse(...);
    }
}

สำหรับการจัดการข้อผิดพลาด 404 เพิ่ม@EnableWebMvcคำอธิบายประกอบและต่อไปนี้เพื่อ application.properties ก็เพียงพอแล้ว:
spring.mvc.throw-exception-if-no-handler-found=true

คุณสามารถค้นหาและเล่นกับแหล่งข้อมูลได้ที่นี่:
https://github.com/magiccrafter/spring-boot-exception-handling


7
มีประโยชน์จริง ๆ ขอบคุณ แต่ฉันไม่เข้าใจว่าทำไมเราถึงต้อง @EnableWebMvc `กับ 'spring.mvc.throw-exception-if-no-handler-found = true' ความคาดหวังของฉันคือการจัดการข้อยกเว้นทั้งหมดผ่าน@RestControllerAdviceไม่มีการกำหนดค่าเพิ่มเติม ฉันหายไปนี่อะไร
fiskra

28

ฉันคิดว่าResponseEntityExceptionHandlerตรงตามความต้องการของคุณ ตัวอย่างโค้ดสำหรับ HTTP 400:

@ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {

  @ResponseStatus(value = HttpStatus.BAD_REQUEST)
  @ExceptionHandler({HttpMessageNotReadableException.class, MethodArgumentNotValidException.class,
      HttpRequestMethodNotSupportedException.class})
  public ResponseEntity<Object> badRequest(HttpServletRequest req, Exception exception) {
    // ...
  }
}

คุณสามารถตรวจสอบโพสต์นี้


6
ฉันเคยเห็นรหัสนี้มาก่อนและหลังจากใช้งานชั้นเรียนได้ตรวจจับข้อยกเว้นที่เกิดขึ้นในวิธีการร้องขอการแมปตัวควบคุม สิ่งนี้ยังคงไม่พบข้อผิดพลาด 404 ซึ่งได้รับการจัดการในเมธอด ResourceHttpRequestHandler.handleRequest หรือหากใช้คำอธิบายประกอบ @EnableWebMvc ใน DispatcherServlet.noHandlerFound เราต้องการจัดการข้อผิดพลาดใด ๆ รวมถึง 404s แต่ Spring Boot เวอร์ชันล่าสุดดูเหมือนจะป้านอย่างไม่น่าเชื่อเกี่ยวกับวิธีการทำเช่นนั้น
ogradyjd

ฉันเขียนวิธีเดียวกันในการจัดการHttpRequestMethodNotSupportedExceptionและปลั๊กอิน jar เดียวกันในบริการไมโครหลายรายการเพื่อวัตถุประสงค์ทางธุรกิจบางอย่างเราต้องตอบชื่อนามแฝงบริการไมโครในการตอบกลับ มีวิธีใดบ้างที่เราจะได้รับชื่อบริการไมโครคอนโทรลเลอร์ / ชื่อคอนโทรลเลอร์ ฉันรู้ว่าHandlerMethodจะให้ชื่อวิธีการจาวาจากที่เกิดข้อยกเว้น แต่ที่นี่ไม่มีวิธีการใดที่ได้รับการร้องขอดังนั้นHandlerMethodจะไม่เริ่มต้น ดังนั้นมีวิธีแก้ไขปัญหานี้หรือไม่?
Paramesh Korrakuti

คำแนะนำของผู้ควบคุมเป็นวิธีการที่ดี แต่โปรดจำไว้เสมอว่าข้อยกเว้นไม่ได้เป็นส่วนหนึ่งของการไหลที่ต้องเกิดขึ้นในกรณีพิเศษ!
JorgeTovar

17

แม้ว่านี่จะเป็นคำถามที่เก่ากว่า แต่ฉันต้องการแบ่งปันความคิดของฉันเกี่ยวกับเรื่องนี้ ฉันหวังว่ามันจะเป็นประโยชน์กับคุณบางคน

ขณะนี้ฉันกำลังสร้าง 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 (ดูการทดสอบด้านบน)

สิ่งนี้ช่วยแก้ไขปัญหาให้ฉันได้ เพียงเพิ่มฉันไม่มีหมายเหตุประกอบ @ RestControllerAdvice ดังนั้นฉันจึงเพิ่มสิ่งนั้นพร้อมกับคำอธิบายประกอบ @ ControllerAdvice เพื่อให้สามารถจัดการได้ทั้งหมดและนั่นก็เป็นเคล็ดลับ
PGMacDesign

13

แล้วรหัสนี้ล่ะ? ฉันใช้การจับคู่คำขอทางเลือกเพื่อตรวจจับข้อผิดพลาด 404

@Controller
@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
        //If exception has a ResponseStatus annotation then use its response code
        ResponseStatus responseStatusAnnotation = AnnotationUtils.findAnnotation(ex.getClass(), ResponseStatus.class);

        return buildModelAndViewErrorPage(request, response, ex, responseStatusAnnotation != null ? responseStatusAnnotation.value() : HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @RequestMapping("*")
    public ModelAndView fallbackHandler(HttpServletRequest request, HttpServletResponse response) throws Exception {
        return buildModelAndViewErrorPage(request, response, null, HttpStatus.NOT_FOUND);
    }

    private ModelAndView buildModelAndViewErrorPage(HttpServletRequest request, HttpServletResponse response, Exception ex, HttpStatus httpStatus) {
        response.setStatus(httpStatus.value());

        ModelAndView mav = new ModelAndView("error.html");
        if (ex != null) {
            mav.addObject("title", ex);
        }
        mav.addObject("content", request.getRequestURL());
        return mav;
    }

}

6

โดยค่าเริ่มต้น Spring Boot ให้ json ด้วยรายละเอียดข้อผิดพลาด

curl -v localhost:8080/greet | json_pp
[...]
< HTTP/1.1 400 Bad Request
[...]
{
   "timestamp" : 1413313361387,
   "exception" : "org.springframework.web.bind.MissingServletRequestParameterException",
   "status" : 400,
   "error" : "Bad Request",
   "path" : "/greet",
   "message" : "Required String parameter 'name' is not present"
}

นอกจากนี้ยังทำงานได้กับข้อผิดพลาดการแมปคำขอทุกประเภท ตรวจสอบบทความนี้ http://www.jayway.com/2014/10/19/spring-boot-error-responses/

หากคุณต้องการสร้างบันทึกไปยัง NoSQL คุณสามารถสร้าง @ControllerAdvice ที่คุณจะเข้าสู่ระบบแล้วโยนข้อยกเว้นอีกครั้ง มีตัวอย่างในเอกสารประกอบ https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc


DispatcherServlet เริ่มต้นจะฮาร์ดโค้ดเพื่อทำการเปลี่ยนเส้นทางด้วย MVC แทนที่จะส่งข้อยกเว้นเมื่อได้รับการร้องขอสำหรับการจับคู่ที่ไม่มีอยู่ - เว้นแต่คุณจะตั้งค่าสถานะเหมือนที่ฉันทำในโพสต์ด้านบน
ogradyjd

นอกจากนี้เหตุผลที่เราใช้คลาส ResponseEntityExceptionHandler คือเพื่อให้เราสามารถควบคุมรูปแบบของการติดตามผลลัพธ์และการติดตามสแต็กข้อผิดพลาดไปยังโซลูชัน NoSQL จากนั้นส่งข้อความข้อผิดพลาดที่ปลอดภัยสำหรับลูกค้า
ogradyjd

6

@RestControllerAdvice เป็นคุณลักษณะใหม่ของ Spring Framework 4.3 เพื่อจัดการกับข้อยกเว้นด้วย RestfulApi โดยวิธีแก้ไขปัญหาที่เกี่ยวข้อง:

 package com.khan.vaquar.exception;

import javax.servlet.http.HttpServletRequest;

import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.khan.vaquar.domain.ErrorResponse;

/**
 * Handles exceptions raised through requests to spring controllers.
 **/
@RestControllerAdvice
public class RestExceptionHandler {

    private static final String TOKEN_ID = "tokenId";

    private static final Logger log = LoggerFactory.getLogger(RestExceptionHandler.class);

    /**
     * Handles InstructionExceptions from the rest controller.
     * 
     * @param e IntrusionException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IntrusionException.class)
    public ErrorResponse handleIntrusionException(HttpServletRequest request, IntrusionException e) {       
        log.warn(e.getLogMessage(), e);
        return this.handleValidationException(request, new ValidationException(e.getUserMessage(), e.getLogMessage()));
    }

    /**
     * Handles ValidationExceptions from the rest controller.
     * 
     * @param e ValidationException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = ValidationException.class)
    public ErrorResponse handleValidationException(HttpServletRequest request, ValidationException e) {     
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);

        if (e.getUserMessage().contains("Token ID")) {
            tokenId = "<OMITTED>";
        }

        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(),
                                    e.getUserMessage());
    }

    /**
     * Handles JsonProcessingExceptions from the rest controller.
     * 
     * @param e JsonProcessingException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = JsonProcessingException.class)
    public ErrorResponse handleJsonProcessingException(HttpServletRequest request, JsonProcessingException e) {     
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(),
                                    e.getOriginalMessage());
    }

    /**
     * Handles IllegalArgumentExceptions from the rest controller.
     * 
     * @param e IllegalArgumentException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = IllegalArgumentException.class)
    public ErrorResponse handleIllegalArgumentException(HttpServletRequest request, IllegalArgumentException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = UnsupportedOperationException.class)
    public ErrorResponse handleUnsupportedOperationException(HttpServletRequest request, UnsupportedOperationException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    /**
     * Handles MissingServletRequestParameterExceptions from the rest controller.
     * 
     * @param e MissingServletRequestParameterException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public ErrorResponse handleMissingServletRequestParameterException( HttpServletRequest request, 
                                                                        MissingServletRequestParameterException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.BAD_REQUEST.value(), 
                                    e.getClass().getSimpleName(), 
                                    e.getMessage());
    }

    /**
     * Handles NoHandlerFoundExceptions from the rest controller.
     * 
     * @param e NoHandlerFoundException
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(value = NoHandlerFoundException.class)
    public ErrorResponse handleNoHandlerFoundException(HttpServletRequest request, NoHandlerFoundException e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.info(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.NOT_FOUND.value(), 
                                    e.getClass().getSimpleName(), 
                                    "The resource " + e.getRequestURL() + " is unavailable");
    }

    /**
     * Handles all remaining exceptions from the rest controller.
     * 
     * This acts as a catch-all for any exceptions not handled by previous exception handlers.
     * 
     * @param e Exception
     * @return error response POJO
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(value = Exception.class)
    public ErrorResponse handleException(HttpServletRequest request, Exception e) {
        String tokenId = request.getParameter(TOKEN_ID);
        log.error(e.getMessage(), e);
        return new ErrorResponse(   tokenId,
                                    HttpStatus.INTERNAL_SERVER_ERROR.value(), 
                                    e.getClass().getSimpleName(), 
                                    "An internal error occurred");
    }   

}

3

Zalando Problem Spring Webสำหรับตัวควบคุมส่วนที่เหลือผมจะแนะนำให้ใช้

https://github.com/zalando/problem-spring-web

หาก Spring Boot มีจุดประสงค์เพื่อฝังการกำหนดค่าอัตโนมัติบางอย่างไลบรารีนี้จะทำสิ่งต่างๆให้มากขึ้นสำหรับการจัดการข้อยกเว้น คุณเพียงแค่ต้องเพิ่มการพึ่งพา:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>problem-spring-web</artifactId>
    <version>LATEST</version>
</dependency>

จากนั้นกำหนดคุณสมบัติคำแนะนำอย่างน้อยหนึ่งข้อสำหรับข้อยกเว้นของคุณ (หรือใช้คุณลักษณะที่มีให้ตามค่าเริ่มต้น)

public interface NotAcceptableAdviceTrait extends AdviceTrait {

    @ExceptionHandler
    default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
            final HttpMediaTypeNotAcceptableException exception,
            final NativeWebRequest request) {
        return Responses.create(Status.NOT_ACCEPTABLE, exception, request);
    }

}

จากนั้นคุณสามารถกำหนดคำแนะนำของตัวควบคุมสำหรับการจัดการข้อยกเว้นเป็น:

@ControllerAdvice
class ExceptionHandling implements MethodNotAllowedAdviceTrait, NotAcceptableAdviceTrait {

}

2

สำหรับผู้ที่ต้องการตอบสนองตามรหัสสถานะ http คุณสามารถใช้ErrorControllerวิธี:

@Controller
public class CustomErrorController extends BasicErrorController {

    public CustomErrorController(ServerProperties serverProperties) {
        super(new DefaultErrorAttributes(), serverProperties.getError());
    }

    @Override
    public ResponseEntity error(HttpServletRequest request) {
        HttpStatus status = getStatus(request);
        if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)){
            return ResponseEntity.status(status).body(ResponseBean.SERVER_ERROR);
        }else if (status.equals(HttpStatus.BAD_REQUEST)){
            return ResponseEntity.status(status).body(ResponseBean.BAD_REQUEST);
        }
        return super.error(request);
    }
}

ResponseBeanนี่คือ POJO กำหนดเองของฉันสำหรับการตอบสนอง


0

แก้ปัญหาด้วย dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);และ @EnableWebMvc @ControllerAdvice ทำงานให้ฉันด้วย Spring Boot 1.3.1 ในขณะที่ไม่ได้ทำงานกับ 1.2.7

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.