Déclencher 404 dans le contrôleur Spring-MVC?

193

Comment puis-je obtenir un contrôleur Spring 3.0 pour déclencher un 404?

J'ai un contrôleur avec @RequestMapping(value = "/**", method = RequestMethod.GET)et pour certaines URL accédant au contrôleur, je veux que le conteneur propose un 404.

N / A.
la source

Réponses:

326

Depuis Spring 3.0, vous pouvez également lever une exception déclarée avec @ResponseStatusannotation:

@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
la source
2
Intéressant. Pouvez-vous spécifier quel HttpStatus utiliser sur le site throw (c'est-à-dire ne pas le faire compiler dans la classe Exception)?
mat le
1
@mattb: Je pense que le fait @ResponseStatusest que vous définissez tout un tas de classes d'exceptions bien typées et bien nommées, chacune avec ses propres @ResponseStatus. De cette façon, vous dissociez votre code de contrôleur du détail des codes d'état HTTP.
skaffman
9
Peut-il être étendu pour prendre en charge le retour d'un corps contenant plus de description de l'erreur?
Tom
7
@Tom:@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason")
Nailgun
6
Si vous utilisez cette exception ResourceNotFound uniquement pour le contrôle de flux, c'est peut-être une bonne idée de remplacer ResourceNotFound.fillInStackTrace()avec une implémentation vide.
Ralph
36

Réécrivez la signature de votre méthode afin qu'elle accepte HttpServletResponsecomme paramètre, afin que vous puissiez l'appeler setStatus(int).

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

mat b
la source
8
C'est la seule bonne réponse si quelqu'un cherche un moyen de dire au demandeur http qu'il a fait une erreur en n'inondant pas l'équipe de prod ops avec un tas d'exceptions qu'ils ne peuvent pas corriger.
Alex R
4
setStatus(int)javadoc déclare comme suit: Si cette méthode est utilisée pour définir un code d'erreur, le mécanisme de page d'erreur du conteneur ne sera pas déclenché. S'il y a une erreur et que l'appelant souhaite invoquer une page d'erreur définie dans l'application Web, elle sendErrordoit être utilisée à la place.
Philippe Gioseffi
@AlexR Les exceptions gérées ne devraient pas inonder l'équipe des opérations. Si tel est le cas, la journalisation n'est pas effectuée correctement.
Raedwald
25

Je voudrais mentionner qu'il y a une exception (pas seulement) pour 404 par défaut fournie par Spring. Voir la documentation Spring pour plus de détails. Donc, si vous n'avez pas besoin de votre propre exception, vous pouvez simplement le faire:

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

    ...

  }
michal.kreuzman
la source
11
Cela semble être destiné à un cas spécifique - lorsque Spring ne peut pas trouver de gestionnaire. Le cas en question est quand Spring peut trouver un gestionnaire, mais l'utilisateur veut retourner un 404 pour une autre raison.
Roy Truelove
2
Je l'utilise lorsque mon mappage ulr pour la méthode de gestionnaire est dynamique. Quand l'entité n'existe pas, @PathVariableil n'y a pas de traitement de demande de mon point de vue. Pensez-vous qu'il est préférable / plus propre d'utiliser votre propre exception annotée @ResponseStatus(value = HttpStatus.NOT_FOUND) ?
michal.kreuzman
1
Dans votre cas, cela semble bien, mais je ne sais pas si je recommanderais les exceptions trouvées dans le lien que vous avez fourni pour gérer tous les cas où une exception est nécessaire - parfois, vous devriez créer la vôtre.
Roy Truelove du
Eh bien, Spring a fourni une exception et une seule pour 404. Ils auraient dû l'appeler 404Exception ou en créer une. Mais comme c'est le cas maintenant, je pense que c'est correct de lancer cela chaque fois que vous avez besoin d'un 404.
autra
Eh bien, techniquement parlant, ça va - vous enverrez un en-tête d'état 404. Mais le message d'erreur automatique - contenu de la réponse - est "Aucune méthode de traitement des demandes avec le nom ...", ce qui n'est probablement pas quelque chose que vous souhaitez montrer à l'utilisateur.
Olli
24

Depuis Spring 3.0.2, vous pouvez renvoyer ResponseEntity <T> à la suite de la méthode du contrôleur:

@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> est une annotation plus flexible que @ResponseBody - voir une autre question )

Lu55
la source
2
bien sûr flexible mais vainc les avantages de la programmation déclarative
rohanagarwal
3
Si vous utilisez Sentry ou similaire dans PROD et que vous ne voulez pas le spammer avec des erreurs qui ne sont pas des erreurs réelles, cette solution est bien meilleure que celle qui utilise des exceptions pour cette situation non exceptionnelle.
Tobias Hermann
N'oubliez pas comment remplir le corps (avec votre objet réel). exemple générique "Object": Object returnItemBody = new Object (); return ResponseEntity.status (HttpStatus.OK) .body (returnItemBody);
granadaCoder
16

vous pouvez utiliser @ControllerAdvice pour gérer vos exceptions. Le comportement par défaut de la classe annotée @ControllerAdvice aidera tous les contrôleurs connus.

il sera donc appelé lorsqu'un contrôleur dont vous disposez lance une erreur 404.

comme ceci:

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

et mappez cette erreur de réponse 404 dans votre web.xml, comme suit:

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

J'espère que cela pourra aider .


la source
2
vous avez mappé des exceptions de type Exception (et sous-classes) avec un code d'état 404. Avez-vous déjà pensé qu'il y avait des erreurs de serveur internes? Comment prévoyez-vous de les gérer dans votre GlobalControllerExceptionHandler?
rohanagarwal
Cela n'a PAS fonctionné pour les contrôleurs REST, renvoie une réponse vide.
rustyx
10

Si votre méthode de contrôleur est pour quelque chose comme la gestion de fichiers, alors ResponseEntityc'est très pratique:

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

Bien que la réponse marquée soit correcte, il existe un moyen d'y parvenir sans exception. Le service renvoie Optional<T>l'objet recherché et est mappé à HttpStatus.OKs'il est trouvé et à 404 s'il est vide.

@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
la source
J'aime cette approche en général, mais l'utilisation d'Optionals finit parfois par être un anti-pattern. Et devient compliqué lors du retour des collections.
jfzr
7

Je recommanderais de lancer HttpClientErrorException , comme ceci

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

Vous devez vous rappeler que cela ne peut être fait qu'avant que quoi que ce soit ne soit écrit dans le flux de sortie du servlet.

mmatczuk
la source
4
Cette exception est levée par le client HTTP Spring. Spring MVC ne semble pas reconnaître cette exception. Quelle version Spring utilisez-vous? Obtenez-vous un 404 avec cette exception?
Eduardo
1
Cela provoque le retour de 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
Il s'agit d'une exception pour un client HTTP, pas pour un contrôleur. Il est donc inapproprié de l'utiliser dans le contexte spécifié.
Alexey
3

C'est un peu tard, mais si vous utilisez Spring Data REST il existe déjà org.springframework.data.rest.webmvc.ResourceNotFoundException Il utilise également des @ResponseStatusannotations. Il n'est plus nécessaire de créer une exception d'exécution personnalisée.

pilote
la source
2

De plus, si vous souhaitez renvoyer l'état 404 de votre contrôleur, tout ce dont vous avez besoin est de le faire

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

En faisant cela, vous recevrez une erreur 404 au cas où vous voudriez renvoyer un 404 de votre contrôleur.

AbdusSalam
la source
0

Vous pouvez simplement utiliser web.xml pour ajouter un code d'erreur et une page d'erreur 404. Mais assurez-vous que la page d'erreur 404 ne doit pas se trouver sous WEB-INF.

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

C'est le moyen le plus simple de le faire, mais cela a certaines limites. Supposons que si vous souhaitez ajouter le même style pour cette page que vous avez ajouté d'autres pages. De cette façon, vous ne pouvez pas faire ça. Vous devez utiliser le@ResponseStatus(value = HttpStatus.NOT_FOUND)

Rajith Delantha
la source
C'est la façon de le faire, mais considérez-le à HttpServletResponse#sendError(HttpServletResponse.SC_NOT_FOUND); return null;partir du code du contrôleur. De l'extérieur, la réponse ne ressemble pas à une 404 normale qui n'a touché aucun contrôleur.
Darryl Miles du
cela ne déclenche pas un 404, il le gère simplement si cela se produit
Alex R
0

Configurer web.xml avec le paramètre

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

Créer un nouveau contrôleur

   /**
     * 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
la source
0

Parce qu'il est toujours bon d'avoir au moins dix façons de faire la même chose:

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