【问题标题】:404 error redirect in Spring with Java config使用 Java 配置在 Spring 中重定向 404 错误
【发布时间】:2014-06-27 19:10:40
【问题描述】:

如你所知,在 XML 中,配置它的方式是:

<error-page>
    <error-code>404</error-code>
    <location>/my-custom-page-not-found.html</location>
</error-page>

但我还没有找到在 Java 配置中执行此操作的方法。我尝试的第一种方法是:

@RequestMapping(value = "/**")
public String Error(){
    return "error";
}

它似乎可以工作,但是在检索资源时会发生冲突。

有办法吗?

【问题讨论】:

  • 你想用那个处理方法做什么?
  • 如果用户输入的网址无效,我会尝试重定向到自定义错误页面
  • 请求处理程序用于处理请求。 /** 匹配所有 URI。
  • 还可以在这里查看我的帖子以获得正确的错误处理:stackoverflow.com/questions/41607802/…

标签: java spring spring-mvc


【解决方案1】:

在 Spring Framework 中,有多种处理异常的方法(尤其是 404 错误)。这是documentation link

  • 首先,您仍然可以在web.xml中使用error-page标签,并自定义错误页面。这是example
  • 其次,您可以为所有控制器使用一个@ExceptionHandler,如下所示:

    @ControllerAdvice
    public class ControllerAdvisor {
    
         @ExceptionHandler(NoHandlerFoundException.class)
         public String handle(Exception ex) {
    
            return "404";//this is view name
        }
    }
    

    为此,请将 web.xml 中 DispatcherServletthrowExceptionIfNoHandlerFound 属性设置为 true:

    <init-param>
        <param-name>throwExceptionIfNoHandlerFound</param-name>
        <param-value>true</param-value>
    </init-param>
    

    您还可以将一些对象传递给错误视图,请参阅javadoc

【讨论】:

  • 我使用的是 java 配置,没有 xml。如何添加 throwExceptionIfNoHandlerFound 参数?
  • 您仍然在使用应用程序描述符 (web.xml)。
  • 在 web.xml 中,您将 spring org.springframework.web.servlet.DispatcherServlet 声明为标准 servlet。如上所述,将 init 参数添加到此 servlet。
  • @Luiggi OP 专门要求基于 Java 配置的解决方案
  • @Luiggi: 扩展 WebApplicationInitializer 而不是 web.xml
【解决方案2】:

自 spring 4.2 RC3 以来最干净的解决方案是在扩展 AbstractDispatcherServletInitializer(或间接通过扩展 AbstractAnnotationConfigDispatcherServletInitializer)的类中使用新的 createDispatcherServlet 挂钩,如下所示:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    /* ... */

    @Override
    protected DispatcherServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
        final DispatcherServlet dispatcherServlet = super.createDispatcherServlet(servletAppContext);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
        return dispatcherServlet;
    }
}

然后您可以使用全局@ControllerAdvice(一个用@ControllerAdvice 注释的类),如in the reference docs 所述。在建议中,您可以使用@ExceptionHandler 处理NoHandlerFoundException,如here 所述。

这可能看起来像这样:

@ControllerAdvice
public class NoHandlerFoundControllerAdvice {

    @ExceptionHandler(NoHandlerFoundException.class)
    public ResponseEntity<String> handleNoHandlerFoundException(NoHandlerFoundException ex) {
        // prepare responseEntity
        return responseEntity;
    }

}

【讨论】:

  • 这看起来是当前的最佳做法。一个小提示:从 Spring 4.2.3 开始,AbstractDispatcherServletInitializer.createDispatcherServlet 返回 FrameworkServlet,因此需要对 DispatcherServlet 进行额外的强制转换。在this answer 中可以找到实现此方法的一些其他注意事项。
  • getServletConfigClasses也可以用在WebAppInitializer中吗?
  • 可以。它是在我上面答案的完整代码中完成的,但为简洁起见被剥离了。
【解决方案3】:

按照in the doc 的描述使用基于代码的 Servlet 容器初始化并覆盖 registerDispatcherServlet 方法以将 throwExceptionIfNoHandlerFound 属性设置为 true:

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { WebConfig.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected void registerDispatcherServlet(ServletContext servletContext) {
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() may not return empty or null");

        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext,
            "createServletApplicationContext() did not return an application " +
                    "context for servlet [" + servletName + "]");

        DispatcherServlet dispatcherServlet = new DispatcherServlet(servletAppContext);

        // throw NoHandlerFoundException to Controller
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);

        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        Assert.notNull(registration,
            "Failed to register servlet with name '" + servletName + "'." +
                    "Check if there is another servlet registered under the same name.");

        registration.setLoadOnStartup(1);
        registration.addMapping(getServletMappings());
        registration.setAsyncSupported(isAsyncSupported());

        Filter[] filters = getServletFilters();
        if (!ObjectUtils.isEmpty(filters)) {
            for (Filter filter : filters) {
                registerServletFilter(servletContext, filter);
            }
        }

        customizeRegistration(registration);
    }
}    

然后创建一个异常处理程序:

@ControllerAdvice
public class ExceptionHandlerController {
    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        return "404";// view name for 404 error
    }   
}

不要忘记在 Spring configuration file 上使用 @EnableWebMvc 注释:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages= {"org.project.etc"})
public class WebConfig extends WebMvcConfigurerAdapter {
    ...
}

【讨论】:

  • 您选择此方法而不是 customizeRegistration 方法是否有原因?似乎除了@Override protected void customizeRegistration(ServletRegistration.Dynamic registration){ registration.setInitParameter("throwExceptionIfNoHandlerFound","true"); } 还需要更多的东西
【解决方案4】:

100% 免费 xml 的简单答案:

  1. DispatcherServlet设置属性

    public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
        @Override
        protected Class<?>[] getRootConfigClasses() {
            return new Class[] { RootConfig.class  };
        }
    
        @Override
        protected Class<?>[] getServletConfigClasses() {
            return new Class[] {AppConfig.class  };
        }
    
        @Override
        protected String[] getServletMappings() {
            return new String[] { "/" };
        }
    
        @Override
        protected void customizeRegistration(ServletRegistration.Dynamic registration) {
            boolean done = registration.setInitParameter("throwExceptionIfNoHandlerFound", "true"); // -> true
            if(!done) throw new RuntimeException();
        }
    
    }
    
  2. 创建@ControllerAdvice:

    @ControllerAdvice
    public class AdviceController {
    
        @ExceptionHandler(NoHandlerFoundException.class)
        public String handle(Exception ex) {
            return "redirect:/404";
        }
    
        @RequestMapping(value = {"/404"}, method = RequestMethod.GET)
        public String NotFoudPage() {
            return "404";
    
        }
    }
    

【讨论】:

  • 这对我不起作用,我的代码永远不会进入 customizeRegistration 并失败并出现模棱两可的错误消息
  • 这个问题的最佳答案,@Carlos Lopèz 请验证它
  • RootConfig.class 来自哪里?
【解决方案5】:

在您的网络配置类中,

@Configuration
public class WebConfig extends WebMvcConfigurerAdapter 

如下声明一个bean,

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {

  return new EmbeddedServletContainerCustomizer() {
    @Override
    public void customize(ConfigurableEmbeddedServletContainer container)       
    {
      ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/401.html");
      ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/404.html");
      ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html");

      container.addErrorPages(error401Page,error404Page,error500Page);
    }
  };
}

将提到的html文件(401.html.etc)添加到/src/main/resources/static/文件夹中。

希望对你有帮助

【讨论】:

  • 这只适用于 Spring Boot 吗?我希望它能够正常工作,因为您还可以指定非 404 错误页面。
  • 它适用于spring mvc。不管它是否是弹簧启动......相同的配置。您也可以为其他 http 错误代码添加错误页面。
  • 也许我遗漏了什么,但在 Spring Framework 4 中找不到 EmbeddedServletContainerCustomizerConfigurableEmbeddedServletContainerErrorPagedocs.spring.io/spring/docs/current/javadoc-api
  • 澄清一下,EmbeddedServletContainerCustomizer 是 Spring Boot 的一部分,所以这只有在您使用 Spring Boot 时才有效。
【解决方案6】:

对于 Java 配置,DispatcherServlet 中有一个方法 setThrowExceptionIfNoHandlerFound(boolean throwExceptionIfNoHandlerFound)。通过将其设置为true 我猜你正在做同样的事情

<init-param>
    <param-name>throwExceptionIfNoHandlerFound</param-name>
    <param-value>true</param-value>
</init-param>

那么您可以在控制器建议中使用NoHandlerFoundException.class,如上述答案所述

它会像什么东西

public class WebXml implements WebApplicationInitializer{

    public void onStartup(ServletContext servletContext) throws ServletException {
        WebApplicationContext context = getContext();
        servletContext.addListener(new ContextLoaderListener(context));


        DispatcherServlet dp =  new DispatcherServlet(context);
        dp.setThrowExceptionIfNoHandlerFound(true);

        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", dp);
        dispatcher.setLoadOnStartup(1);
        dispatcher.addMapping(MAPPING_URL);
    }
}

【讨论】:

    【解决方案7】:

    in comments above 提出的解决方案确实有效:

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration)
    {
      registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
    }
    

    【讨论】:

      【解决方案8】:

      Spring 5 和 Thymeleaf 3 的解决方案。

      MyWebInitializer 中,使用setThrowExceptionIfNoHandlerFound() 启用异常抛出。我们需要对DispatcherServlet进行强制转换。

      @Configuration
      public class MyWebInitializer extends
              AbstractAnnotationConfigDispatcherServletInitializer {
      
              ...
      
              @Override
              protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
                  var dispatcher = (DispatcherServlet) super.createDispatcherServlet(servletAppContext);
                  dispatcher.setThrowExceptionIfNoHandlerFound(true);
                  return dispatcher;
              }
          }
      

      使用@ControllerAdvice 创建控制器建议并将错误消息添加到ModealAndView

      @ControllerAdvice
      public class ControllerAdvisor {
      
          @ExceptionHandler(NoHandlerFoundException.class)
          public ModelAndView handle(Exception ex) {
      
              var mv = new ModelAndView();
              mv.addObject("message", ex.getMessage());
              mv.setViewName("error/404");
      
              return mv;
          }
      }
      

      创建 404 错误模板,显示错误消息。根据我的配置,文件是src/main/resources/templates/error/404.html

      <!doctype html>
      <html xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport"
                content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
          <title>Resource not found</title>
      </head>
      <body>
      
      <h2>404 - resource not found</h2>
      
      <p>
          <span th:text="${message}" th:remove="tag"></span>
      </p>
      
      </body>
      </html>
      

      为了完整起见,我添加了 Thymeleaf 解析器配置。我们将 Thymeleaf 模板配置在类路径上的 templates 目录中。

      @Configuration
      @EnableWebMvc
      @ComponentScan(basePackages = {"com.zetcode"})
      public class WebConfig implements WebMvcConfigurer {
      
          @Autowired
          private ApplicationContext applicationContext;
      
          ...
      
          @Bean
          public SpringResourceTemplateResolver templateResolver() {
      
              var templateResolver = new SpringResourceTemplateResolver();
      
              templateResolver.setApplicationContext(applicationContext);
              templateResolver.setPrefix("classpath:/templates/");
              templateResolver.setSuffix(".html");
      
              return templateResolver;
          }
          ...
      }
      

      【讨论】:

        【解决方案9】:

        在 springboot 中它更简单。由于 Spring 自动配置的东西,spring 创建了一个 bean org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties。这个类用@ConfigurationProperties(prefix = "spring.mvc")注解,因此它会寻找带有spring.mvc前缀的属性。

        部分来自 javadoc:

        Annotation for externalized configuration. Add this to a class definition or a
        * @Bean method in a @Configuration class if you want to bind and validate
        * some external Properties (e.g. from a .properties file).
        

        您只需将以下属性添加到您的即application.properties 文件中:

         spring.mvc.throwExceptionIfNoHandlerFound=true
         spring.resources.add-mappings=false //this is for spring so it won't return default handler for resources that not exist
        

        并添加异常解析器如下:

        @ControllerAdvice
        public class ExceptionResponseStatusHandler {
            @ExceptionHandler(NoHandlerFoundException.class)
            public ModelAndView handle404() {
                var out = new ModelAndView();
                out.setViewName("404");//you must have view named i.e. 404.html
                return out;
            }
        }
        

        【讨论】:

          猜你喜欢
          • 2016-08-12
          • 2017-05-10
          • 2015-03-24
          • 1970-01-01
          • 2017-01-24
          • 1970-01-01
          • 1970-01-01
          • 2011-01-19
          • 1970-01-01
          相关资源
          最近更新 更多