ทริกเกอร์ 404 ในคอนโทรลเลอร์ Spring-MVC


194

ฉันจะรับตัวควบคุมSpring 3.0 เพื่อทริกเกอร์ 404 ได้อย่างไร

ฉันมีคอนโทรลเลอร์พร้อม@RequestMapping(value = "/**", method = RequestMethod.GET)และสำหรับURLบางตัวที่เข้าถึงคอนโทรลเลอร์ฉันต้องการให้คอนเทนเนอร์เกิดขึ้นด้วย 404

คำตอบ:


326

ตั้งแต่ Spring 3.0 คุณสามารถโยนข้อยกเว้นที่ประกาศด้วย@ResponseStatusหมายเหตุประกอบ:

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
    ...
}

@Controller
public class SomeController {
    @RequestMapping.....
    public void handleCall() {
        if (isFound()) {
            // whatever
        }
        else {
            throw new ResourceNotFoundException(); 
        }
    }
}

2
น่าสนใจ คุณสามารถระบุ HttpStatus ที่จะใช้ที่เว็บไซต์การโยน (เช่นไม่มีการคอมไพล์ลงในคลาสข้อยกเว้น)
matt b

1
@mattb: ผมคิดว่าจุดคือที่คุณกำหนดมัดทั้งขอพิมพ์ชั้นเรียนยกเว้นดีชื่อแต่ละคนมีของตัวเอง@ResponseStatus @ResponseStatusด้วยวิธีนี้คุณแยกรหัสควบคุมออกจากรายละเอียดของรหัสสถานะ HTTP
skaffman

10
สามารถขยายเพื่อรองรับการคืนเนื้อหาที่มีคำอธิบายเพิ่มเติมเกี่ยวกับข้อผิดพลาดได้หรือไม่?
Tom

7
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason")
@Tom

6
หากคุณใช้ข้อยกเว้น ResourceNotFound นี้สำหรับการควบคุมการไหลเท่านั้นอาจเป็นความคิดที่ดีที่จะแทนที่ResourceNotFound.fillInStackTrace()ด้วยการใช้งานที่ว่างเปล่า
Ralph

41

เริ่มต้นจาก Spring 5.0 คุณไม่จำเป็นต้องสร้างข้อยกเว้นเพิ่มเติม:

throw new ResponseStatusException(NOT_FOUND, "Unable to find resource");

นอกจากนี้คุณสามารถครอบคลุมหลาย ๆ สถานการณ์ด้วยข้อยกเว้นในตัวเดียวและคุณสามารถควบคุมได้มากขึ้น

ดูเพิ่มเติม:


36

เขียนลายเซ็นวิธีการของคุณใหม่เพื่อให้ยอมรับHttpServletResponseเป็นพารามิเตอร์เพื่อให้คุณสามารถเรียกsetStatus(int)ใช้

http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-arguments


8
นี่เป็นคำตอบที่ถูกต้องเพียงข้อเดียวหากใครบางคนกำลังมองหาวิธีที่จะบอกผู้ร้องขอ http พวกเขาทำผิดพลาดในขณะที่ไม่ทำให้ทีมแยง ops มีข้อยกเว้นมากมายที่พวกเขาไม่สามารถแก้ไขได้
Alex R

4
setStatus(int)javadoc ระบุดังต่อไปนี้: หากวิธีนี้ใช้เพื่อตั้งรหัสข้อผิดพลาดกลไกหน้าข้อผิดพลาดของคอนเทนเนอร์จะไม่ถูกทริกเกอร์ หากมีข้อผิดพลาดและผู้โทรต้องการเรียกใช้หน้าข้อผิดพลาดที่กำหนดไว้ในเว็บแอปพลิเคชันsendErrorจะต้องใช้แทน
Philippe Gioseffi

@AlexR ข้อยกเว้นที่มีการจัดการไม่ควรทำให้ทีม ops เสียหาย หากเป็นเช่นนั้นการบันทึกจะไม่ถูกต้อง
Raedwald

25

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

 @RequestMapping(value = "/**", method = RequestMethod.GET)
 public ModelAndView show() throws NoSuchRequestHandlingMethodException {
    if(something == null)
         throw new NoSuchRequestHandlingMethodException("show", YourClass.class);

    ...

  }

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

2
ฉันใช้มันเมื่อการทำแผนที่ ulr ของฉันสำหรับวิธีการจัดการเป็นแบบไดนามิก เมื่อเอนทิตีไม่ได้อยู่บนพื้นฐานของ@PathVariableไม่มีการจัดการการร้องขอจากมุมมองของฉัน คุณคิดว่าจะดีกว่าหรือสะอาดกว่าในการใช้ข้อยกเว้นของคุณเองที่มีคำอธิบายประกอบอยู่ด้วย@ResponseStatus(value = HttpStatus.NOT_FOUND) ?
michal.kreuzman

1
ในกรณีของคุณมันฟังดูดี แต่ฉันไม่รู้ว่าฉันขอแนะนำข้อยกเว้นที่พบในลิงค์ที่คุณให้ไว้เพื่อจัดการทุกกรณีที่มีข้อยกเว้นตามความจำเป็น - บางครั้งคุณควรทำด้วยตัวเอง
Roy Truelove

สปริงมีข้อยกเว้นหนึ่งข้อและอย่างใดอย่างหนึ่งเท่านั้นสำหรับ 404 พวกเขาควรตั้งชื่อมันว่า 404Exception หรือสร้างขึ้นมาหนึ่งข้อ แต่มันเป็นตอนนี้ผมคิดว่ามันก็โอเคที่จะโยนนี้เมื่อใดก็ตามที่คุณจำเป็นต้องมี 404
autra

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

24

ตั้งแต่ Spring 3.0.2 คุณสามารถส่งคืนResponseEntity <T>ซึ่งเป็นผลมาจากวิธีการของคอนโทรลเลอร์:

@RequestMapping.....
public ResponseEntity<Object> handleCall() {
    if (isFound()) {
        // do what you want
        return new ResponseEntity<>(HttpStatus.OK);
    }
    else {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

(ResponseEntity <T> มีความยืดหยุ่นมากกว่าคำอธิบายประกอบ @ResponseBody - ดูคำถามอื่น )


2
ความยืดหยุ่นของหลักสูตร แต่เอาชนะผลประโยชน์ของการเขียนโปรแกรมที่ประกาศ
rohanagarwal

3
หากคุณใช้ Sentry หรือคล้ายกันใน PROD และไม่ต้องการสแปมด้วยข้อผิดพลาดที่ไม่มีข้อผิดพลาดจริงวิธีนี้จะดีกว่ามากเมื่อเทียบกับที่ใช้ข้อยกเว้นสำหรับสถานการณ์ที่ไม่พิเศษนี้
โทเบียสเฮอร์มันน์

อย่าลืมวิธีเติมร่างกาย (ด้วยวัตถุจริงของคุณ) ตัวอย่าง "Object" ทั่วไป: Object returnItemBody = new Object (); return ResponseEntity.status (HttpStatus.OK) .body (returnItemBody);
granadaCoder

16

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

ดังนั้นมันจะถูกเรียกเมื่อตัวควบคุมใด ๆ ที่คุณมีข้อผิดพลาด 404

ชอบดังต่อไปนี้:

@ControllerAdvice
class GlobalControllerExceptionHandler {
    @ResponseStatus(HttpStatus.NOT_FOUND)  // 404
    @ExceptionHandler(Exception.class)
    public void handleNoTFound() {
        // Nothing to do
    }
}

และจับคู่ข้อผิดพลาดการตอบกลับนี้ 404 ใน web.xml ของคุณเช่น:

<error-page>
        <error-code>404</error-code>
        <location>/Error404.html</location>
</error-page>

หวังว่าจะช่วย


2
คุณได้ทำข้อยกเว้นประเภท Exception (และคลาสย่อย) ที่มีรหัสสถานะ 404 คุณเคยคิดว่ามีข้อผิดพลาดเซิร์ฟเวอร์ภายในหรือไม่? คุณวางแผนที่จะจัดการกับสิ่งเหล่านั้นใน GlobalControllerExceptionHandler ของคุณอย่างไร?
rohanagarwal

สิ่งนี้ใช้ไม่ได้กับตัวควบคุม REST ส่งคืนการตอบกลับที่ว่างเปล่า
rustyx

10

หากวิธีการควบคุมของคุณสำหรับสิ่งที่ต้องการจัดการไฟล์นั้นResponseEntityมีประโยชน์มาก:

@Controller
public class SomeController {
    @RequestMapping.....
    public ResponseEntity handleCall() {
        if (isFound()) {
            return new ResponseEntity(...);
        }
        else {
            return new ResponseEntity(404);
        }
    }
}

9

ในขณะที่คำตอบที่ทำเครื่องหมายไว้ถูกต้องมีวิธีการบรรลุนี้โดยไม่มีข้อยกเว้น บริการกำลังส่งคืนOptional<T>วัตถุที่ค้นหาและสิ่งนี้จะถูกแมปไปHttpStatus.OKหากพบและถึง 404 หากว่างเปล่า

@Controller
public class SomeController {

    @RequestMapping.....
    public ResponseEntity<Object> handleCall() {
        return  service.find(param).map(result -> new ResponseEntity<>(result, HttpStatus.OK))
                .orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }
}

@Service
public class Service{

    public Optional<Object> find(String param){
        if(!found()){
            return Optional.empty();
        }
        ...
        return Optional.of(data); 
    }

}

ฉันชอบวิธีการนี้โดยทั่วไป แต่บางครั้งการใช้ Optionals ก็กลายเป็นรูปแบบการต่อต้าน และซับซ้อนขึ้นเมื่อส่งคืนคอลเล็กชัน
jfzr

7

ฉันขอแนะนำให้ขว้างHttpClientErrorExceptionแบบนี้

@RequestMapping(value = "/sample/")
public void sample() {
    if (somethingIsWrong()) {
        throw new HttpClientErrorException(HttpStatus.NOT_FOUND);
    }
}

คุณต้องจำไว้ว่าสิ่งนี้สามารถทำได้ก่อนที่สิ่งใดจะถูกเขียนไปยังสตรีมเอาต์พุตของเซิร์ฟเล็ต


4
ข้อยกเว้นนั้นถูกส่งออกไปโดยไคลเอนต์ Spring HTTP Spring MVC ดูเหมือนจะไม่รู้จักข้อยกเว้นนั้น คุณใช้เวอร์ชั่นฤดูใบไม้ผลิอะไร? คุณได้รับ 404 โดยมีข้อยกเว้นนั้นหรือไม่?
Eduardo

1
สิ่งนี้ทำให้ Spring Boot กลับมา:Whitelabel Error Page \n .... \n There was an unexpected error (type=Internal Server Error, status=500). \n 404 This is your not found error
slim

นี่เป็นข้อยกเว้นสำหรับไคลเอนต์ HTTP ไม่ใช่สำหรับตัวควบคุม ดังนั้นการใช้มันในบริบทที่ระบุจึงไม่เหมาะสม
Alexey

3

มันช้าไปหน่อย แต่ถ้าคุณใช้Spring Data RESTก็มีอยู่แล้วorg.springframework.data.rest.webmvc.ResourceNotFoundException มันยังใช้@ResponseStatusหมายเหตุประกอบด้วย ไม่จำเป็นต้องสร้างข้อยกเว้นรันไทม์แบบกำหนดเองอีกต่อไป


2

นอกจากนี้หากคุณต้องการคืนสถานะ 404 จากคอนโทรลเลอร์ของคุณสิ่งที่คุณต้องทำก็คือ

@RequestMapping(value = "/somthing", method = RequestMethod.POST)
@ResponseBody
public HttpStatus doSomthing(@RequestBody String employeeId) {
    try{
  return HttpStatus.OK;
    } 
    catch(Exception ex){ 
  return HttpStatus.NOT_FOUND;
    }
}

โดยการทำเช่นนี้คุณจะได้รับข้อผิดพลาด 404 ในกรณีที่เมื่อคุณต้องการส่งคืน 404 จากคอนโทรลเลอร์ของคุณ


0

เพียงคุณสามารถใช้ web.xml เพื่อเพิ่มรหัสข้อผิดพลาดและหน้าข้อผิดพลาด 404 แต่ตรวจสอบให้แน่ใจว่าหน้าข้อผิดพลาด 404 ต้องไม่ค้นหาภายใต้ WEB-INF

<error-page>
    <error-code>404</error-code>
    <location>/404.html</location>
</error-page>

นี่เป็นวิธีที่ง่ายที่สุดที่จะทำ แต่มีข้อ จำกัด บางอย่าง สมมติว่าคุณต้องการเพิ่มสไตล์เดียวกันสำหรับหน้านี้ที่คุณเพิ่มหน้าอื่น ๆ ด้วยวิธีนี้คุณไม่สามารถทำได้ คุณต้องใช้@ResponseStatus(value = HttpStatus.NOT_FOUND)


วิธีนี้จะทำ แต่พิจารณาด้วยHttpServletResponse#sendError(HttpServletResponse.SC_NOT_FOUND); return null;จากรหัสควบคุม ตอนนี้จากภายนอกการตอบสนองดูเหมือนไม่แตกต่างจากปกติ 404 ที่ไม่ได้ตีตัวควบคุมใด ๆ
Darryl Miles

นี้ไม่ได้เรียก 404 ก็แค่จัดการกับมันถ้าเกิดขึ้น
อเล็กซ์ R

0

กำหนดค่า web.xml ด้วยการตั้งค่า

<error-page>
    <error-code>500</error-code>
    <location>/error/500</location>
</error-page>

<error-page>
    <error-code>404</error-code>
    <location>/error/404</location>
</error-page>

สร้างตัวควบคุมใหม่

   /**
     * Error Controller. handles the calls for 404, 500 and 401 HTTP Status codes.
     */
    @Controller
    @RequestMapping(value = ErrorController.ERROR_URL, produces = MediaType.APPLICATION_XHTML_XML_VALUE)
    public class ErrorController {


        /**
         * The constant ERROR_URL.
         */
        public static final String ERROR_URL = "/error";


        /**
         * The constant TILE_ERROR.
         */
        public static final String TILE_ERROR = "error.page";


        /**
         * Page Not Found.
         *
         * @return Home Page
         */
        @RequestMapping(value = "/404", produces = MediaType.APPLICATION_XHTML_XML_VALUE)
        public ModelAndView notFound() {

            ModelAndView model = new ModelAndView(TILE_ERROR);
            model.addObject("message", "The page you requested could not be found. This location may not be current.");

            return model;
        }

        /**
         * Error page.
         *
         * @return the model and view
         */
        @RequestMapping(value = "/500", produces = MediaType.APPLICATION_XHTML_XML_VALUE)
        public ModelAndView errorPage() {
            ModelAndView model = new ModelAndView(TILE_ERROR);
            model.addObject("message", "The page you requested could not be found. This location may not be current, due to the recent site redesign.");

            return model;
        }
}

0

เพราะเป็นการดีที่มีอย่างน้อยสิบวิธีในการทำสิ่งเดียวกัน:

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class Something {
    @RequestMapping("/path")
    public ModelAndView somethingPath() {
        return new ModelAndView("/", HttpStatus.NOT_FOUND);
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.