简体   繁体   English

Spring 中的 404 错误(java 配置/无 web.xml)

[英]404 error in Spring (java config / no web.xml)

Trying to provide a custom 404 error page in a web application that, to the best of my knowledge, uses Java Config (thus no web.xml).尝试在 Web 应用程序中提供自定义 404 错误页面,据我所知,该应用程序使用 Java Config(因此没有 web.xml)。

We have the following versions of the related libraries: spring ("5.1.2.RELEASE"), spring-security ("5.1.1.RELEASE").我们有以下版本的相关库:spring(“5.1.2.RELEASE”)、spring-security(“5.1.1.RELEASE”)。

Disclaimer免责声明

I have checked different approaches here in StackOverflow.我在 StackOverflow 中检查了不同的方法。 Please don't suggest results for web.xml, Thymeleaf or Spring Boot.请不要建议 web.xml、Thymeleaf 或 Spring Boot 的结果。 This is not applicable.这不适用。

Among others;其中; I tried with the following approaches:我尝试了以下方法:

  • @Controller annotation ( here and here ) @Controller注释( 这里这里
  • adding a web.xml添加一个 web.xml

None produced the expected result (that is, still getting the default webserver layout and error).没有产生预期的结果(即仍然得到默认的网络服务器布局和错误)。

Controller annotation approach控制器注解方式

  • exception package异常包

    package ...; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.NoHandlerFoundException; @ControllerAdvice public class GlobalExceptionHandler { // Option A (used as an alternative to option B) //@ExceptionHandler(Exception.class) //public String handle(Exception ex) { // return "redirect:/404"; //} @RequestMapping(value = {"/404"}, method = RequestMethod.GET) public String NotFoundPage() { return "404"; } // Option B (used as an alternative to option A) @ExceptionHandler(Exception.class) public ResponseEntity<String> handleNoHandlerFoundException(GlobalExceptionHandler ex) { ResponseEntity responseEntity = new ResponseEntity<>(new RestClientException("Testing exception"), HttpStatus.NOT_FOUND); return responseEntity; } }
  • init class初始化类

    package ...; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.http.ResponseEntity; import org.springframework.http.HttpStatus; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; @Configuration @ComponentScan("...") @EnableWebMvc @EnableTransactionManagement @PropertySource("classpath:application.properties") public class WebAppConfig extends WebMvcConfigurerAdapter { @ExceptionHandler({ Exception.class }) public ResponseEntity<RestClientException> handle(NoHandlerFoundException e) { return new ResponseEntity<>(new RestClientException("Testing exception"), HttpStatus.NOT_FOUND); } ... @Override public void addViewControllers(ViewControllerRegistry registry) { super.addViewControllers(registry); registry.addViewController("/404.jsp").setViewName("404"); } }

There is also an Initializer class ( public class Initializer implements WebApplicationInitializer ), which seems to conflict with some suggested options (defined here and here );还有一个Initializer类( public class Initializer implements WebApplicationInitializer ),它似乎与一些建议的选项(在此处此处定义)相冲突; so the webapp-init class is not modified.所以 webapp-init 类没有被修改。

web.xml approach web.xml 方法

<?xml version="1.0" encoding="UTF-8"?>
<web-app id="ROOT" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
    version="2.4">

<error-page>
  <error-code>404</error-code>
  <location>/error</location>
</error-page>
<error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/error</location>
</error-page>

</web-app>

The 404.jsp or 404.html files are placed (currently for testing purposes at all the following locations):放置了404.jsp404.html文件(目前在以下所有位置用于测试目的):

    src/main/resources
    ├── ...
    ├── error
    │   └── 404.html
    ├── public
    │   ├── 404.html
    │   └── error
    │       └── 404.html
    ├── templates
    │   └── 404.html
    └── ...

    src/main/webapp/WEB-INF/
    ├── error.jsp
    ├── tags
    │   └── ...
    └── views
        ├── 404.html
        ├── 404.jsp
        ├── error.jsp
        └── ...

Any idea on what is missing or wrong?关于缺少什么或错误的任何想法?

Although not as clear as I would like, this is a sort of working version to at least provide some customisation to error pages.虽然不像我想的那么清楚,但这是一种工作版本,至少可以为错误页面提供一些自定义。 It is a first approach but hopefully can help others.这是第一种方法,但希望可以帮助其他人。

The list of handled exceptions is not extensive, but mainly addressing 404 errors ( NoHandlerFoundException ) and other typical errors like InternalServerErrorException and NullPointerException , to try to catch them all in the end with a generic error for everything else that is an Exception ).处理的异常列表并不广泛,但主要解决 404 错误 ( NoHandlerFoundException ) 和其他典型错误,如InternalServerErrorExceptionNullPointerException ,以尝试最终将它们全部捕获,并为其他所有Exception错误捕获通用错误)。

Note that this is not covering other exceptions related to eg bad syntax in a JSTL template ( org.apache.jasper.* ; exceptions that cannot apparently be caught here).请注意,这不包括与例如 JSTL 模板中的错误语法相关的其他异常( org.apache.jasper.* ;此处显然无法捕获的异常)。

These are the related changes and additions to the source base:这些是对源代码库的相关更改和添加:

CustomSimpleMappingExceptionResolver.java (provide generic exceptions, yet logs details) CustomSimpleMappingExceptionResolver.java (提供通用异常,但记录详细信息)

package ...;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import javax.ws.rs.InternalServerErrorException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.http.HttpStatus;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;

public class CustomSimpleMappingExceptionResolver extends SimpleMappingExceptionResolver {

    public CustomSimpleMappingExceptionResolver() {
        // Turn logging on by default
        setWarnLogCategory(getClass().getName());
    }

    @Override
    public String buildLogMessage(Exception e, HttpServletRequest req) {
        return "MVC exception: " + e.getLocalizedMessage();
    }

    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,
                                              Object handler, Exception ex) {

        // Log exception
        ex.printStackTrace();
        String exceptionCause = ex.toString();
        String exceptionType = ex.getClass().getCanonicalName();

        // Get the ModelAndView to use
        ModelAndView mav = super.doResolveException(request, response, handler, ex);

        // Make more information available to the view - note that SimpleMappingExceptionResolver adds the exception already
        mav.addObject("url", request.getRequestURL());
        mav.addObject("timestamp", new Date());

        ArrayList<String> exceptions404 = new ArrayList<String>(
                Arrays.asList(
                        NoHandlerFoundException.class.getName()
                        )
        );
        ArrayList<String> exceptions500 = new ArrayList<String>(
                Arrays.asList(
                        InternalServerErrorException.class.getName(),
                        NullPointerException.class.getName()
                        )
        );

        String userExceptionDetail = ex.toString();
        String errorHuman = "";
        String errorTech = "";

        if (exceptions404.contains(exceptionType)) {
            errorHuman = "We cannot find the page you are looking for";
            errorTech = "Page not found";
            userExceptionDetail = String.format("The page %s cannot be found", request.getRequestURL());
            mav.setViewName("/error/404");
            mav.addObject("status", HttpStatus.NOT_FOUND.value());
        } else if (exceptions500.contains(exceptionType)) {
            errorHuman = "We cannot currently serve the page you request";
            errorTech = "Internal error";
            userExceptionDetail = "The current page refuses to load due to an internal error";
            mav.setViewName("/error/500");
            mav.addObject("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
        } else {
            errorHuman = "We cannot serve the current page";
            errorTech = "General error";
            userExceptionDetail = "A generic error prevents from serving the page";
            mav.setViewName("/error/generic");
            mav.addObject("status", response.getStatus());
        }

        Exception userException = new Exception(userExceptionDetail);
        mav.addObject("error_human", errorHuman);
        mav.addObject("error_tech", errorTech);
        mav.addObject("exception", userException);
        return mav;
    }
}

WebAppConfig.java (registers custom exception resolver as an exception handler) WebAppConfig.java (将自定义异常解析器注册为异常处理程序)

package ...;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.env.Environment;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

import java.lang.ClassNotFoundException;
import java.lang.NullPointerException;
import javax.annotation.Resource;
import javax.ws.rs.InternalServerErrorException;
import java.util.Properties;

@Configuration
@ComponentScan("...")
@EnableWebMvc
@EnableTransactionManagement
@PropertySource("classpath:application.properties")
public class WebAppConfig extends WebMvcConfigurerAdapter {

    @Resource
    private Environment env;

    // ...

    @Bean
    HandlerExceptionResolver customExceptionResolver () {
        CustomSimpleMappingExceptionResolver resolver = new CustomSimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        // Mapping Spring internal error NoHandlerFoundException to a view name
        mappings.setProperty(NoHandlerFoundException.class.getName(), "/error/404");
        mappings.setProperty(InternalServerErrorException.class.getName(), "/error/500");
        mappings.setProperty(NullPointerException.class.getName(), "/error/500");
        mappings.setProperty(ClassNotFoundException.class.getName(), "/error/500");
        mappings.setProperty(Exception.class.getName(), "/error/generic");
        resolver.setExceptionMappings(mappings);
        // Set specific HTTP codes
        resolver.addStatusCode("404", HttpStatus.NOT_FOUND.value());
        resolver.addStatusCode("500", HttpStatus.INTERNAL_SERVER_ERROR.value());
        resolver.setDefaultErrorView("/error/generic");
        resolver.setDefaultStatusCode(200);
        // This resolver will be processed before the default ones
        resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
        resolver.setExceptionAttribute("exception");
        return resolver;
    }

    // ...

    @Bean
    public InternalResourceViewResolver setupViewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        super.addViewControllers(registry);
    }
}

Initializer.java (adding dispatcherServlet.setThrowExceptionIfNoHandlerFound(true); ; maybe not needed) Initializer.java (添加dispatcherServlet.setThrowExceptionIfNoHandlerFound(true); ;可能不需要)

package ...;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;

public class Initializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(WebAppConfig.class);
        servletContext.addListener(new ContextLoaderListener(ctx));
        ctx.setServletContext(servletContext);
        DispatcherServlet dispatcherServlet = new DispatcherServlet(ctx);
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);

        // Add the dispatcher servlet mapping manually and make it initialize automatically
        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher", dispatcherServlet);
        servlet.addMapping("/");
        servlet.addMapping("*.png");
        servlet.addMapping("*.jpg");
        servlet.addMapping("*.css");
        servlet.addMapping("*.js");
        servlet.addMapping("*.txt");
        servlet.setLoadOnStartup(1);

        // ...

    }
}

Structure of views and tags related to error classes:与错误类别相关的视图和标签的结构:

    src/main/webapp/WEB-INF/
    ├── tags
    │   └── error.tag
    └── views
        ├── error
        │   ├── 404.jsp
        │   ├── 500.jsp
        └────── generic.jsp

src/main/webapp/WEB-INF/tags/error.tag src/main/webapp/WEB-INF/tags/error.tag

<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<!DOCTYPE html>
<head>
    <title>Error page</title>
</head>
<body>
<div class="container">
    <h3><c:out value="${error_human}" /></h3>

    <p><br/><br/></p>

    <div class="panel panel-primary">
        <div class="panel-heading">
            <c:out value="${error_tech}" />
        </div>
        <div class="panel-body">
            <p><c:out value="${exception_message}" /></p>
        </div>
    </div>
</div>
</body>
</html>

src/main/webapp/WEB-INF/views/error/404.jsp src/main/webapp/WEB-INF/views/error/404.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8" isErrorPage="true" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib tagdir="/WEB-INF/tags/" prefix="g" %>

<c:set var = "error_human" scope = "session" value = "We cannot find the page you are looking for"/>
<c:set var = "error_tech" scope = "session" value = "Page not found"/>
<c:set var = "exception_message" scope = "session" value = "The current page cannot be found"/>
<g:error />

src/main/webapp/WEB-INF/views/error/500.jsp src/main/webapp/WEB-INF/views/error/500.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8" isErrorPage="true" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib tagdir="/WEB-INF/tags/" prefix="g" %>

<c:set var = "error_human" scope = "session" value = "We cannot currently serve the page you request"/>
<c:set var = "error_tech" scope = "session" value = "Internal error"/>
<c:set var = "exception_message" scope = "session" value = "The current page refuses to load due to an internal error"/>
<g:error />

src/main/webapp/WEB-INF/views/error/generic.jsp src/main/webapp/WEB-INF/views/error/generic.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8" isErrorPage="true" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib tagdir="/WEB-INF/tags/" prefix="g" %>

<c:set var = "error_human" scope = "session" value = "We cannot serve the current page"/>
<c:set var = "error_tech" scope = "session" value = "General error"/>
<c:set var = "exception_message" scope = "session" value = "A generic error prevents from serving the page"/>
<g:error />

Reading the Spring Boot docs, this works for me:阅读 Spring Boot 文档,这对我有用:

  @Bean
   public ErrorPageRegistrar errorPageRegistrar() {
     return registry -> registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/index.html"));  }

This is equivalente to web.xml.这相当于 web.xml。

Make sure you can access 404 page, and then add these codes.确保您可以访问 404 页面,然后添加这些代码。

@ControllerAdvice
public class GlobalExceptionHandler {

    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ExceptionHandler(NoHandlerFoundException.class)
    public String handle404(Model model, HttpServletRequest req, Exception ex) {
        return "/404";
    }
}

application.yaml应用程序.yaml

spring:
  mvc:
    throwExceptionIfNoHandlerFound: true # if page not found, it will throw error, and then ControllerAdvice will catch the error.

PS: springBoot version=2.4.2; PS:springBoot 版本=2.4.2; Java=15爪哇=15

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM