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

194

Как получить контроллер Spring 3.0 для запуска 404?

У меня есть контроллер, @RequestMapping(value = "/**", method = RequestMethod.GET)и для некоторых URL-адресов, обращающихся к контроллеру, я хочу, чтобы контейнер выдал 404.

NA.
источник

Ответы:

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(); 
        }
    }
}
axtavt
источник
2
Интересный. Можете ли вы указать, какой HttpStatus использовать на сайте броска (то есть не скомпилировать его в класс Exception)?
Мэтт б
1
@mattb: Я думаю, что смысл в @ResponseStatusтом, что вы определяете целую кучу строго типизированных, хорошо названных классов исключений, каждый со своим собственным @ResponseStatus. Таким образом, вы отделяете код контроллера от подробностей кодов состояния HTTP.
Скаффман
10
Может ли это быть расширено для поддержки возврата тела, содержащего подробное описание ошибки?
Том
7
@Tom:@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason")
Nailgun
6
Если вы используете это исключение ResourceNotFound только для управления потоком, то, возможно, будет хорошей идеей переопределить ResourceNotFound.fillInStackTrace()пустую реализацию.
Ральф
42

Начиная с Spring 5.0, вам не обязательно создавать дополнительные исключения:

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

Кроме того, вы можете охватить несколько сценариев одним встроенным исключением, и у вас будет больше контроля.

Узнать больше:

Joni
источник
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 с кучей исключений, которые он не может исправить.
Алекс Р
4
setStatus(int)Javadoc заявляет следующее: Если этот метод используется для установки кода ошибки, то механизм страницы ошибок контейнера не будет запущен. Если есть ошибка, и вызывающая сторона желает вызвать страницу ошибки, определенную в веб-приложении, то sendErrorдолжна использоваться вместо этого.
Филипп Джозеффи
@AlexR Обработанные исключения не должны наводнять команду ops. Если они есть, регистрация ведется неправильно.
Raedwald
25

Я хотел бы отметить, что есть исключение (не только) для 404 по умолчанию, предоставляемое Spring. Подробности смотрите в документации Spring . Поэтому, если вам не нужно ваше собственное исключение, вы можете просто сделать это:

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

    ...

  }
michal.kreuzman
источник
11
Это похоже на конкретный случай - когда Spring не может найти обработчик. Речь идет о том, когда Spring может найти обработчик, но пользователь хочет вернуть 404 по другой причине.
Рой Truelove
2
Я использую его, когда мое отображение ulr для метода-обработчика является динамическим. Когда сущность не существует на основе, @PathVariableс моей точки зрения, нет обработки запросов. Как вы думаете, лучше / чище использовать собственное исключение с пометкой @ResponseStatus(value = HttpStatus.NOT_FOUND) ?
michal.kreuzman
1
В вашем случае это звучит нормально, но я не знаю, что я бы порекомендовал исключения, найденные в ссылке, которую вы предоставили, для обработки всех случаев, когда исключение необходимо - иногда вы должны сделать свое собственное.
Рой Truelove
Ну, Spring предоставил одно исключение и одно только для 404. Они должны были назвать его 404Exception или создать его. Но, как и сейчас, я думаю, что можно бросать это всякий раз, когда вам нужно 404.
Авра
Ну, технически говоря, все в порядке - вы отправите заголовок статуса 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 - см. Другой вопрос )

Lu55
источник
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?
Роханагарвал
Это НЕ работает для контроллеров 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); 
    }

}
user1121883
источник
Мне нравится этот подход в целом, но использование Optionals иногда оказывается анти-паттерном. И усложняется при возврате коллекций.
19
7

Я бы порекомендовал выдать HttpClientErrorException , как это

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

Вы должны помнить, что это можно сделать только до того, как что-либо будет записано в выходной поток сервлета.

mmatczuk
источник
4
Это исключение выдается клиентом Spring HTTP. Spring MVC, похоже, не признает это исключение. Какую версию Spring вы используете? Вы получаете 404 с этим исключением?
Эдуардо
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-клиента, а не для контроллера. Поэтому использование его в указанном контексте неуместно.
Алексей
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, это только обрабатывает это, если один случается
Alex 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);
    }
}
Джейсон
источник