在 Spring-MVC 控制器中触发 404?

Posted

技术标签:

【中文标题】在 Spring-MVC 控制器中触发 404?【英文标题】:Trigger 404 in Spring-MVC controller? 【发布时间】:2011-01-05 05:37:12 【问题描述】:

如何让Spring 3.0 控制器触发 404?

我有一个带有 @RequestMapping(value = "/**", method = RequestMethod.GET) 的控制器,并且对于一些 URLs 访问控制器,我希望容器提供 404。

【问题讨论】:

【参考方案1】:

从 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(); 
        
    

【讨论】:

有趣。你能指定在抛出站点使用哪个 HttpStatus(即没有将其编译到 Exception 类中)吗? @mattb:我认为@ResponseStatus 的意义在于您定义了一大堆强类型、良好命名的异常类,每个都有自己的@ResponseStatus。这样,您就可以将控制器代码与 HTTP 状态代码的详细信息分离。 可以扩展它以支持返回包含更多错误描述的正文吗? @Tom: @ResponseStatus(value = HttpStatus.NOT_FOUND, reason="Your reason") 如果您将此 ResourceNotFound 异常仅用于流量控制,那么使用空实现覆盖 ResourceNotFound.fillInStackTrace() 可能是个好主意。【参考方案2】:

从 Spring 5.0 开始,您不一定需要创建额外的异常:

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

此外,您可以使用一个内置异常覆盖多个场景,并且您拥有更多控制权。

查看更多:

ResponseStatusException (javadoc) https://www.baeldung.com/spring-response-status-exception

【讨论】:

请注意,从 Spring Boot 2.3 开始,原因将不会发送,因为存在泄露信息的风险。【参考方案3】:

重写你的方法签名,让它接受HttpServletResponse作为参数,这样你就可以在它上面调用setStatus(int)

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

【讨论】:

如果有人正在寻找一种方法来告诉 http 请求者他们犯了一个错误,同时又不会用一堆他们无法修复的异常来淹没 prod ops 团队,那么这是唯一正确的答案。跨度> setStatus(int)javadoc 声明如下: 如果使用该方法设置错误码,则不会触发容器的错误页面机制。如果出现错误并且调用者希望调用 Web 应用程序中定义的错误页面,则必须使用 sendError @AlexR 处理的异常不应该淹没运营团队。如果是,则日志记录不正确。【参考方案4】:

我想提一下,Spring 默认提供的 404 有异常(不仅)。有关详细信息,请参阅Spring documentation。因此,如果您不需要自己的异常,您可以简单地这样做:

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

    ...

  

【讨论】:

这似乎是针对特定情况的 - 当 Spring 找不到处理程序时。有问题的情况是 Spring 可以 找到处理程序,但用户出于其他原因想要返回 404。 当我的处理程序方法的 ulr 映射是动态的时,我正在使用它。当基于@PathVariable 的实体不存在时,从我的角度来看,没有请求处理。您认为使用自己的带有@ResponseStatus(value = HttpStatus.NOT_FOUND) 注释的异常更好/更清洁吗? 在你的情况下听起来不错,但我不知道我会推荐你​​提供的链接中发现的异常来处理所有需要异常的情况 - 有时你应该自己做. 嗯,Spring 提供了一个异常,一个仅用于 404。他们应该将其命名为 404Exception 或创建一个。但就像现在一样,我认为只要你需要 404 就可以抛出这个。 好吧,从技术上讲没关系 - 您将发送 404 状态标头。但是自动错误消息 - 响应内容 - 是“没有名称的请求处理方法......”这可能不是您想要向用户显示的内容。【参考方案5】:

从 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 比@ResponseBody 注释更灵活——参见another question)

【讨论】:

当然很灵活,但会破坏声明式编程的好处 如果您在 PROD 中使用 Sentry 或类似工具,并且不想向它发送非实际错误的垃圾邮件,那么与使用异常处理此非异常的解决方案相比,此解决方案要好得多情况。 不要忘记如何填充主体(使用您的实际对象)。通用“对象”示例:对象 returnItemBody = new Object(); return ResponseEntity.status(HttpStatus.OK).body(returnItemBody);【参考方案6】:

您可以使用@ControllerAdvice 来处理您的异常, @ControllerAdvice 注释类的默认行为将协助所有已知的控制器。

所以当你有任何控制器抛出 404 错误时它会被调用。

如下:

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

并在您的 web.xml 中映射此 404 响应错误,如下所示:

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

希望有所帮助。

【讨论】:

您已将 Exception 类型的异常(和子类)映射为 404 状态代码。你有没有想过有内部服务器错误?您打算如何处理 GlobalControllerExceptionHandler 中的那些? 这不适用于 REST 控制器,返回一个空响应。【参考方案7】:

虽然标记的答案是正确的,但有一种方法可以毫无例外地实现这一点。服务返回搜索对象的Optional&lt;T&gt;,如果找到则映射到HttpStatus.OK,如果为空则映射到404。

@Controller
public class SomeController 

    @RequestMapping.....
    public ResponseEntity<Object> handleCall(@PathVariable String param) 
        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); 
    
    

【讨论】:

我总体上喜欢这种方法,但使用 Optionals 有时会成为一种反模式。并且在返回集合时变得复杂。【参考方案8】:

如果您的控制器方法是用于文件处理之类的,那么ResponseEntity 非常方便:

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

【讨论】:

【参考方案9】:

我建议抛出 HttpClientErrorException,像这样

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

您必须记住,这只能在将任何内容写入 servlet 输出流之前完成。

【讨论】:

Spring HTTP 客户端抛出该异常。 Spring MVC 似乎无法识别该异常。你用的是什么Spring版本?除了这个例外,您是否得到 404? 这会导致 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 这是 HTTP 客户端的例外,而不是控制器。所以在指定的上下文中使用它是不合适的。【参考方案10】:

这有点晚了,但是如果您使用的是Spring Data REST,那么已经有org.springframework.data.rest.webmvc.ResourceNotFoundException 它还使用@ResponseStatus 注释。不再需要创建自定义运行时异常。

【讨论】:

【参考方案11】:

此外,如果您想从控制器返回 404 状态,您只需要这样做

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

这样做会收到 404 错误,以防您想从控制器返回 404。

【讨论】:

【参考方案12】:

使用设置配置 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;
        

【讨论】:

【参考方案13】:

因为至少有十种方法来做同样的事情总是好的:

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

【讨论】:

【参考方案14】:

只需使用 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,它只会在发生时处理它

以上是关于在 Spring-MVC 控制器中触发 404?的主要内容,如果未能解决你的问题,请参考以下文章

HTTP 状态 404 - /spring-mvc/login

Spring-MVC:如何从控制器流式传输 mp3 文件

在 Spring-MVC 控制器中支持多种内容类型

Symfony:事件订阅者。当路由要求用户必须经过身份验证时触发 404 页面(取决于 .env var)

如何在 Spring-MVC 中将数据从视图传递到控制器?

在Observer中输入404页面