Тригер 404 в контролері Spring-MVC?


Відповіді:


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 використовувати на сайті викидання (тобто його не компілювати в клас Exception)?
мат б

1
@mattb: Я думаю, сенс @ResponseStatusполягає в тому, що ви визначаєте цілу купу сильно типізованих, добре названих класів винятків, кожен з яких має свої власні @ResponseStatus. Таким чином, ви відокремлюєте код свого контролера від деталей кодів статусу HTTP.
скафман

10
Чи можна це розширити, щоб підтримати повернення тіла, що містить більше опису про помилку?
Том

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

6
Якщо ви використовуєте цей виняток ResourceNotFound лише для контролю потоку, то, можливо, буде гарною ідеєю замінити ResourceNotFound.fillInStackTrace()порожню реалізацію.
Ральф

41

Починаючи з весни 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-запитувачу, що вони допустили помилку, не заполонивши команду prod ops з купою винятків, які вони не можуть виправити.
Алекс R

4
setStatus(int)javadoc заявляє так: Якщо цей метод використовується для встановлення коду помилки, механізм сторінки помилок контейнера не буде запускатися. Якщо виникла помилка, і абонент бажає викликати сторінку помилки, визначену у веб-програмі, її sendErrorпотрібно використовувати.
Філіп Джозеффі

@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
Це, мабуть, призначене для конкретного випадку - коли Spring не може знайти обробника. Справа йдеться про те, коли Spring може знайти обробник, але користувач хоче повернути 404 з іншої причини.
Roy Truelove

2
Я використовую його, коли моє відображення ульрів для оброблювача динамічне. Коли суб'єкт господарювання не існує на базі, @PathVariableз моєї точки зору не звертається із запитом. Чи вважаєте ви, що краще / чистіше використовувати власний виняток, позначений за допомогою @ResponseStatus(value = HttpStatus.NOT_FOUND) ?
michal.kreuzman

1
У вашому випадку це звучить чудово, але я не знаю, що я б рекомендував винятки, знайдені у посиланні, яке ви надали, щоб обробляти всі випадки, коли необхідний виняток - іноді ви повинні зробити своє.
Roy Truelove

Ну, Spring запропонував один виняток і один лише для 404. Вони мали назвати його 404Exception або створити його. Але як це зараз, я думаю, що нормально це кидати, коли вам потрібно 404.
autra

Ну, технічно кажучи, це нормально - ви надішліть заголовок статусу 404. Але автоматичне повідомлення про помилку - вміст відповіді - це "Немає способу обробки запиту з іменем ...", що, ймовірно, не те, що ви хочете показати користувачеві.
Оллі

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 returnItemBody = новий Object (); повернути 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
ви зіставили винятки типу Виняток (та підкласи) з кодом статусу 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); 
    }

}

Мені подобається такий підхід загалом, але використання необов'язкових варіантів іноді виявляється антидіаграмою. І ускладнюється при поверненні колекцій.
jfzr

7

Я рекомендую викинути HttpClientErrorException , як це

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

Ви повинні пам'ятати, що це можна зробити лише до того, як щось буде записано у вихідний потік сервлетів.


4
Цей виняток кидає Spring HTTP-клієнт. Весняний MVC, схоже, не визнає цього винятку. Яку весняну версію ви використовуєте? Чи отримуєте ви 404 за цим винятком?
Едуардо

1
Це призводить до повернення весняного черевика:Whitelabel Error Page \n .... \n There was an unexpected error (type=Internal Server Error, status=500). \n 404 This is your not found error
стрункий

Це виняток для HTTP-клієнта, а не для контролера. Тому використовувати його у вказаному контексті недоцільно.
Олексій

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, який не потрапив на жоден контролер.
Дарріл Майлз

це не запускає 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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.