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.

NA.
źródło

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(); 
        }
    }
}
axtavt
źródło
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

matowy b
źródło
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);

    ...

  }
michal.kreuzman
źródło
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 )

Lu55
źródło
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.


źródło
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);
        }
    }
}
Ralph
źródło
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); 
    }

}
użytkownik1121883
źródło
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.

mmatczuk
źródło
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.

pilot
źródło
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.

AbdusSalam
źródło
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)

Rajith Delantha
źródło
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;
        }
}
Atish Narlawar
źródło
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);
    }
}
Jason
źródło