Trigger 404 w kontrolerze Spring-MVC?


193

Jak uzyskać kontroler Spring 3.0, aby uruchomić 404?

Mam kontroler z @RequestMapping(value = "/**", method = RequestMethod.GET)niektórymi adresami URL uzyskującymi dostęp do kontrolera, chcę, aby kontener wymyślił 404.

Odpowiedzi:


326

Od wiosny 3.0 można również wyjątek zadeklarowana z @ResponseStatusadnotacją:

@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
Ciekawy. Czy możesz określić, który HttpStatus ma być użyty w witrynie rzucania (tzn. Nie ma go skompilowany w klasie Exception)?
— matt b

1
@mattb: Myślę, że chodzi o @ResponseStatusto, że definiujesz całą grupę silnie typowanych, dobrze nazwanych klas wyjątków, każda z własną @ResponseStatus. W ten sposób oddzielasz kod kontrolera od szczegółów kodów stanu HTTP.
— skaffman

9
Czy można to rozszerzyć o obsługę zwracania treści zawierającej więcej opisu błędu?
— Tom

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

6
Jeśli użyjesz tego wyjątku ResourceNotFound tylko do kontroli przepływu, być może dobrym pomysłem jest zastąpienie ResourceNotFound.fillInStackTrace()go pustą implementacją.
— Ralph


36

Przepisz sygnaturę metody, aby akceptowała HttpServletResponsejako parametr, abyś mógł ją wywołać setStatus(int).

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


8
To jedyna poprawna odpowiedź, jeśli ktoś szuka sposobu, aby powiedzieć żądającemu http, że popełnił błąd, nie zalewając zespołu produkcyjnego kilkoma wyjątkami, których nie może naprawić.
— Alex R

4
setStatus(int)javadoc stwierdza, co następuje: Jeśli ta metoda zostanie użyta do ustawienia kodu błędu, wówczas mechanizm strony błędu kontenera nie zostanie uruchomiony. Jeśli wystąpił błąd, a osoba dzwoniąca chce wywołać stronę błędu zdefiniowaną w aplikacji internetowej, sendErrornależy go użyć.
— Philippe Gioseffi

@AlexR Obsługiwane wyjątki nie powinny zalewać zespołu operacyjnego. Jeśli tak, rejestrowanie odbywa się niepoprawnie.
— Raedwald

25

Chciałbym wspomnieć, że istnieje wyjątek (nie tylko) dla 404 domyślnie dostarczony przez Spring. Zobacz szczegóły w dokumentacji wiosennej . Jeśli więc nie potrzebujesz własnego wyjątku, możesz po prostu to zrobić:

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

    ...

  }

11
Wygląda na to, że jest przeznaczony do konkretnego przypadku - gdy Spring nie może znaleźć programu obsługi. Chodzi o to, kiedy Spring może znaleźć moduł obsługi, ale użytkownik chce zwrócić 404 z innego powodu.
— Roy Truelove,

2
Używam go, gdy moje mapowanie ulr dla metody obsługi jest dynamiczne. Gdy jednostka nie istnieje na podstawie, @PathVariablez mojego punktu widzenia nie ma obsługi żądań. Czy uważasz, że lepiej / czysto używać własnego wyjątku opatrzonego adnotacjami @ResponseStatus(value = HttpStatus.NOT_FOUND) ?
— michal.kreuzman

1
W twoim przypadku brzmi to dobrze, ale nie wiem, czy zaleciłbym wyjątki znajdujące się w podanym linku, aby poradzić sobie ze wszystkimi przypadkami, w których wyjątek jest konieczny - czasem powinieneś stworzyć własny.
— Roy Truelove,

Cóż, Spring podał jeden wyjątek i jeden tylko dla 404. Powinni byli go nazwać 404Exception lub stworzyć jeden. Ale tak jak jest teraz, myślę, że dobrze jest rzucić to, gdy potrzebujesz 404.
— autra

Cóż, technicznie rzecz biorąc jest w porządku - wyślesz nagłówek statusu 404. Ale automatyczny komunikat o błędzie - treść odpowiedzi - to „Brak metody obsługi żądań o nazwie ...”, która prawdopodobnie nie jest czymś, co chcesz pokazać użytkownikowi.
— Olli,

24

Od wersji 3.0.2 możesz zwrócić ResponseEntity <T> w wyniku zastosowania metody kontrolera:

@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> jest bardziej elastyczna niż adnotacja @ResponseBody - zobacz inne pytanie )


2
elastyczny, ale oczywiście
— niweluje

3
Jeśli używasz Sentry lub podobnego w PROD i nie chcesz spamować go błędami, które nie są rzeczywistymi błędami, to rozwiązanie jest znacznie lepsze w porównaniu do rozwiązania wykorzystującego wyjątki w tej nietypowej sytuacji.
— Tobias Hermann,

Nie zapomnij, jak wypełnić ciało (swoim faktycznym przedmiotem). ogólny przykład „Object”: Object returnItemBody = new Object (); return ResponseEntity.status (HttpStatus.OK) .body (returnItemBody);
— granadaCoder

16

możesz użyć @ControllerAdvice do obsługi wyjątków. Domyślne zachowanie klasy z adnotacjami @ControllerAdvice pomoże wszystkim znanym kontrolerom.

więc zostanie wywołany, gdy dowolny kontroler, który masz, zgłasza błąd 404.

jak następujące:

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

i zamapuj ten błąd odpowiedzi 404 w pliku web.xml, na przykład:

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

Mam nadzieję, że to pomaga.


2
odwzorowano wyjątki typu Wyjątek (i podklasy) z kodem stanu 404. Czy kiedykolwiek myślałeś, że wystąpiły wewnętrzne błędy serwera? Jak zamierzasz obsługiwać je w swoim GlobalControllerExceptionHandler?
— rohanagarwal

To NIE działało dla kontrolerów REST, zwraca pustą odpowiedź.
— rustyx

10

Jeśli twoja metoda kontrolera służy do obsługi plików, ResponseEntityjest to bardzo przydatne:

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

9

Chociaż zaznaczona odpowiedź jest poprawna, istnieje sposób na osiągnięcie tego bez wyjątków. Usługa zwraca Optional<T>szukany obiekt, który jest odwzorowywany na, HttpStatus.OKjeśli został znaleziony, i na 404, jeśli jest pusty.

@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); 
    }

}

Ogólnie podoba mi się to podejście, ale czasami korzystanie z Opcjonalnych czasami jest anty-wzorem. I komplikuje się przy zwracaniu kolekcji.
— jfzr

7

Ja polecam rzucania HttpClientErrorException , jak to

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

Musisz pamiętać, że można to zrobić tylko przed zapisaniem czegokolwiek w strumieniu wyjściowym serwletu.


4
Ten wyjątek jest zgłaszany przez klienta Spring HTTP. Wydaje się, że Spring MVC nie rozpoznaje tego wyjątku. Jakiej wersji wiosennej używasz? Czy otrzymujesz 404 z tym wyjątkiem?
— Eduardo

1
Powoduje to powrót 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

Jest to wyjątek dla klienta HTTP, a nie dla kontrolera. Dlatego użycie go w określonym kontekście jest nieodpowiednie.
— Alexey

3

To trochę za późno, ale jeśli używasz Spring Data REST, to już jest. org.springframework.data.rest.webmvc.ResourceNotFoundException Używa także @ResponseStatusadnotacji. Nie ma już potrzeby tworzenia niestandardowego wyjątku czasu wykonywania.


2

Również, jeśli chcesz zwrócić status 404 ze swojego kontrolera, wystarczy to zrobić

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

W ten sposób otrzymasz błąd 404 w przypadku, gdy chcesz zwrócić 404 ze swojego kontrolera.


0

Wystarczy użyć pliku web.xml, aby dodać kod błędu i stronę błędu 404. Ale upewnij się, że strona błędu 404 nie może znaleźć się pod WEB-INF.

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

Jest to najprostszy sposób na zrobienie tego, ale ma to pewne ograniczenia. Załóżmy, że chcesz dodać ten sam styl dla tej strony, co inne strony. W ten sposób nie możesz tego zrobić. Musisz użyć@ResponseStatus(value = HttpStatus.NOT_FOUND)


W ten sposób możesz to zrobić, ale rozważ to HttpServletResponse#sendError(HttpServletResponse.SC_NOT_FOUND); return null;z kodu kontrolera. Teraz z zewnątrz odpowiedź nie różni się niczym od zwykłego 404, który nie uderzył w żaden kontroler.
— Darryl Miles,

to nie uruchamia 404, po prostu sobie z tym radzi, jeśli tak się stanie
— Alex R

0

Skonfiguruj plik web.xml z ustawieniem

<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>

Utwórz nowy kontroler

   /**
     * 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

Ponieważ zawsze dobrze jest mieć co najmniej dziesięć sposobów na zrobienie tego samego:

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);
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.