16. Spring boot 錯誤頁面


 

默認效果:
1)、瀏覽器,返回一個默認的錯誤頁面

  1.1 請求頭

  1.2返回結果

 

 

2)、如果是其他客戶端,默認響應一個json數據

2.1請求頭

 

2.2返回結果

{
    "timestamp": "2018-11-25T08:22:36.343+0000",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/golden"
}

 

步驟:
  1)系統出現4xx或者5xx之類的錯誤;ErrorPageCustomizer就會生效(定制錯誤的響應規則);

  2) 根據相應規則來到/error請求;被BasicErrorController處理;

  3)響應頁面;被Controller處理后去哪個頁面是由DefaultErrorViewResolver解析得到的;

 

源碼解析

public class ErrorMvcAutoConfiguration {
   // 系統出現錯誤以后來到error請求進行處理;(相當於web.xml注冊錯誤頁面規則) @Bean
public ErrorPageCustomizer errorPageCustomizer() { return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath); } /** * {@link WebServerFactoryCustomizer} that configures the server's error pages. */ private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { private final ServerProperties properties; private final DispatcherServletPath dispatcherServletPath; protected ErrorPageCustomizer(ServerProperties properties, DispatcherServletPath dispatcherServletPath) { this.properties = properties; this.dispatcherServletPath = dispatcherServletPath; } @Override public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath .getRelativePath(this.properties.getError().getPath())); errorPageRegistry.addErrorPages(errorPage); } @Override public int getOrder() { return 0; } } } public class ErrorProperties { /** * Path of the error controller. */ @Value("${error.path:/error}") private String path = "/error"; }

 

 

public class ErrorMvcAutoConfiguration {
   // 系統出現錯誤以后來到error請求進行處理;(相當於web.xml注冊錯誤頁面規則) 
    @Bean
    public ErrorPageCustomizer errorPageCustomizer() {
        return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);
    }
    
    /**
     * {@link WebServerFactoryCustomizer} that configures the server's error pages.
     */
    private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered {

        private final ServerProperties properties;

        private final DispatcherServletPath dispatcherServletPath;

        protected ErrorPageCustomizer(ServerProperties properties,
                DispatcherServletPath dispatcherServletPath) {
            this.properties = properties;
            this.dispatcherServletPath = dispatcherServletPath;
        }

        @Override
        public void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
            ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath
                    .getRelativePath(this.properties.getError().getPath()));
            errorPageRegistry.addErrorPages(errorPage);
        }

        @Override
        public int getOrder() {
            return 0;
        }

    }
    
    @Configuration
    @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true)
    @Conditional(ErrorTemplateMissingCondition.class)
    protected static class WhitelabelErrorViewConfiguration {
        //默認的SpringBoot錯誤頁面
        private final SpelView defaultErrorView = new SpelView(
                "<html><body><h1>Whitelabel Error Page</h1>"
                        + "<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>"
                        + "<div id='created'>${timestamp}</div>"
                        + "<div>There was an unexpected error (type=${error}, status=${status}).</div>"
                        + "<div>${message}</div></body></html>"); 
        @Bean(name = "error")
        @ConditionalOnMissingBean(name = "error")
        public View defaultErrorView() {
            return this.defaultErrorView;
        }

        // If the user adds @EnableWebMvc then the bean name view resolver from
        // WebMvcAutoConfiguration disappears, so add it back in to avoid disappointment.
        @Bean
        @ConditionalOnMissingBean
        public BeanNameViewResolver beanNameViewResolver() {
            BeanNameViewResolver resolver = new BeanNameViewResolver();
            resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
            return resolver;
        }

    }
}

public class ErrorProperties {
    /**
     * Path of the error controller.
     */
    @Value("${error.path:/error}")
    private String path = "/error";
    
}
    

 

 

public class ErrorMvcAutoConfiguration {
    @Bean
    @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
    public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
        return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                this.errorViewResolvers);
    }

}


public abstract class AbstractErrorController implements ErrorController {

    private final ErrorAttributes errorAttributes;

    private final List<ErrorViewResolver> errorViewResolvers;

    public AbstractErrorController(ErrorAttributes errorAttributes) {
        this(errorAttributes, null);
    }
    
    //解析錯誤頁面
    protected ModelAndView resolveErrorView(HttpServletRequest request,
            HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        for (ErrorViewResolver resolver : this.errorViewResolvers) {
            ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
            if (modelAndView != null) {
                return modelAndView;
            }
        }
        return null;
    }
    
}

/**取出配置項:server.error.path中的值。如果沒有,則取error.path的值,如果還沒有,則默認為/error路徑*/
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    

    @RequestMapping(produces = "text/html")//產生html類型的數據;瀏覽器發送的請求來到這個方法處理
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        
        //去哪個頁面作為錯誤頁面;包含頁面地址和頁面內容
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
//如果為空,返回error視圖(在ErrorMvcConfiguration中配置的@Bean)
return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); } //產生json數據,其他客戶端來到這個方法處理; @RequestMapping @ResponseBody public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); } } public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { private static final Map<Series, String> SERIES_VIEWS;

   private final ResourceProperties resourceProperties;
static { Map<Series, String> views = new EnumMap<>(Series.class); views.put(Series.CLIENT_ERROR, "4xx"); views.put(Series.SERVER_ERROR, "5xx"); SERIES_VIEWS = Collections.unmodifiableMap(views); } @Override public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = resolve(String.valueOf(status), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) {
//默認SpringBoot可以去找到一個頁面? error/404 String errorViewName
= "error/" + viewName;
//模板引擎可以解析這個頁面地址就用模板引擎解析 TemplateAvailabilityProvider provider
= this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) {
       //模板引擎可用的情況下返回到errorViewName指定的視圖地址
return new ModelAndView(errorViewName, model); }
     //模板引擎不可用
return resolveResource(errorViewName, model); }   // private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
//從靜態資源文件夾下解析對應的頁面 error/404.html
for (String location : this.resourceProperties.getStaticLocations()) { try { Resource resource = this.applicationContext.getResource(location); resource = resource.createRelative(viewName + ".html"); if (resource.exists()) { return new ModelAndView(new HtmlResourceView(resource), model); } } catch (Exception ex) { } } return null; } }

 

 靜態資源文件夾路徑

@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties {

    private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
            "classpath:/META-INF/resources/", "classpath:/resources/",
            "classpath:/static/", "classpath:/public/" };

    /**
     * Locations of static resources. Defaults to classpath:[/META-INF/resources/,
     * /resources/, /static/, /public/].
     */
    private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;

}

 

2)、如果定制錯誤響應

1、如何定制錯誤的頁面
  1)、有模板引擎的情況下;error/狀態碼; 【將錯誤頁面命名為 錯誤狀態碼.html 放在模板引擎文件夾里面的error文件夾下】,發生此狀態碼的錯誤就會來到 對應的頁面;
      我們可以使用4xx和5xx作為錯誤頁面的文件名來匹配這種類型的所有錯誤,精確優先(優先尋找精確的狀態碼.html);
      頁面能獲取的信息:
      timestamp:時間戳
      status:狀態碼
      error:錯誤提示
      exception:異常對象
      message:異常消息
      errors:JSR303數據校驗的錯誤都在這里
  2)、沒有模板引擎(模板引擎找不到這個錯誤頁面),靜態資源文件夾下找
  3)、以上都沒有錯誤頁面,就是默認來到SpringBoot默認的錯誤提示頁面

2、如何定制錯誤的json數據;
  1)、自定義異常處理&返回定制json數據;

 

 

一、頁面請求出錯


 

1. 自定義異常:

public class UserNotExistException extends RuntimeException {
    private static final long serialVersionUID = -7200824453209817228L;
    public UserNotExistException() {
        super("用戶不存在");
    }
}

2. Controller調用

@Controller
public class HelloController {
    @RequestMapping("/hello")
    @ResponseBody
    public String createInvoice(@RequestParam("user") String user) {
        if(user.equals("aaa")) {
            throw new UserNotExistException();
        }
        return "hello world";
    }
}

 

3. 5xx.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>5xx.html</title>
</head>
<body>
    timestamp:[[${timestamp}]]    <br>
    status:[[${status}]]    <br>
    error:[[${error}]]    <br>
    exception:[[${exception}]]    <br>
    errors:[[${errors}]]    <br>
    message:[[${message}]]    <br>
</body>
</html>

4. 正確結果

 5. 錯誤結果

 

 二、json錯誤定制


 

1.編寫異常處理器

@ControllerAdvice
public class MyExceptionHandler {
    //1、瀏覽器客戶端返回的都是json
    @ResponseBody
    @ExceptionHandler(UserNotExistException.class)
    public Map<String,Object> handleException(Exception e){
        Map<String,Object> map = new HashMap<>();
        map.put("code","user.notexist");
        map.put("message",e.getMessage());
        return map;
    }
}

請求:

 

 頁面也顯示json數據

 

 

但若想讓瀏覽器返回錯誤頁面,客戶端返回json數據,且是自適應的,怎么辦呢?

源碼解析

@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
    
    @RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
    public ModelAndView errorHtml(HttpServletRequest request,
            HttpServletResponse response) {
        HttpStatus status = getStatus(request); //status就是端口號
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
    }
    
}


public abstract class AbstractErrorController implements ErrorController {
protected HttpStatus getStatus(HttpServletRequest request) { Integer statusCode = (Integer) request .getAttribute("javax.servlet.error.status_code"); if (statusCode == null) { return HttpStatus.INTERNAL_SERVER_ERROR; } try { return HttpStatus.valueOf(statusCode); } catch (Exception ex) { return HttpStatus.INTERNAL_SERVER_ERROR; } } }

 

 

 

 

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(UserNotExistException.class)
    public String handleException(Exception e, HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //傳入我們自己的錯誤狀態碼  4xx 5xx
        /**
         * Integer statusCode = (Integer) request
         .getAttribute("javax.servlet.error.status_code");
         */
        request.setAttribute("javax.servlet.error.status_code",500);
        map.put("code","user.notexist");
        map.put("message","用戶出錯啦");

        request.setAttribute("ext",map);  //下面自定義異常返回結果時,將獲取到此request作用域中的數據
        //轉發到/error
        return "forward:/error";
    }
}

 但是這種方式無法返回map對象里自定義的數據到頁面

 

Spring Boot 默認的實現原理如下:

public class BasicErrorController extends AbstractErrorController {
   //SpringBoot 默認的返回錯誤html頁面代碼 @RequestMapping(produces
= MediaType.TEXT_HTML_VALUE) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes( request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); ModelAndView modelAndView = resolveErrorView(request, response, status, model); return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); }
  //Spring Boot 默認的返回json數據請求 @RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL)); HttpStatus status = getStatus(request); return new ResponseEntity<>(body, status); } }
public abstract class AbstractErrorController implements ErrorController {
    
    private final ErrorAttributes errorAttributes;
    
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request,
            boolean includeStackTrace) {
        WebRequest webRequest = new ServletWebRequest(request);
        return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
    }
}


@Order(Ordered.HIGHEST_PRECEDENCE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
        
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest,
            boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put("timestamp", new Date());
        addStatus(errorAttributes, webRequest);
        addErrorDetails(errorAttributes, webRequest, includeStackTrace);
        addPath(errorAttributes, webRequest);
        return errorAttributes; 
    }
}

 

 

3)、將我們的定制數據攜帶出去
出現錯誤以后,會來到/error請求,會被BasicErrorController處理,響應出去可以獲取的數據是由
getErrorAttributes得到的(是AbstractErrorController(ErrorController)規定的方法);
  1、完全來編寫一個ErrorController的實現類【或者是編寫AbstractErrorController的子類】,放在容器中;
  2、頁面上能用的數據,或者是json返回能用的數據都是通過errorAttributes.getErrorAttributes得到;
容器中DefaultErrorAttributes.getErrorAttributes();默認進行數據處理的;

 

@AutoConfigureBefore(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class,WebMvcProperties.class })
public class ErrorMvcAutoConfiguration {

    private final ServerProperties serverProperties;
    
    //當容器中沒有ErrorAttributes類型的bean時,才走默認的,所以我們自定義ErrorAttributes的實現類就可以實現定制化
    @Bean
    @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
    public DefaultErrorAttributes errorAttributes() {
        return new DefaultErrorAttributes(
                this.serverProperties.getError().isIncludeException());
    }
}

 

 

Spring Boot1.5.10版本

//給容器中加入我們自己定義的ErrorAttributes,用其實現類,避免重寫多個無需改造的方法
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
  @Override
  public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,boolean includeStackTrace) {
    Map<String, Object> map = super.getErrorAttributes(requestAttributes,includeStackTrace);
    map.put("company","everjiankang"); //自定義屬性
    map.put("name","超軼絕塵");     
return map;   } }

 

Spring Boot 2.0 版本

import java.util.Map;

//
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;  servlet的可以成功執行 //import org.springframework.boot.web.reactive.error.DefaultErrorAttributes; reactive 也有這個類,但是執行不了 import org.springframework.stereotype.Component; import org.springframework.web.context.request.WebRequest; @Component public class MyErrorAttributes extends DefaultErrorAttributes{ @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
    //獲取到上文中異常處理類中設置到request作用域中的“ext”屬性 requeste.setAtrribute("exgt",map);
     Map<String,Object> extMap = (Map<String, Object) webRequest.getAttribute("ext", RequestAttributes.SCOPE_REQUEST);
 Map<String,Object> map =  super.getErrorAttributes(webRequest, includeStackTrace); map.put("name", "xiaochao");
     map.put("ext", extMap); //將異常處理類中的自定義信息返回給頁面
      
return map; } }

public interface WebRequest extends RequestAttributes {

}

 public interface RequestAttributes {

    int SCOPE_REQUEST = 0;

    int SCOPE_SESSION = 1;

 }

 

 


 

Spring Webflux 版本,執行未成功

在Spring boot 2.1.0版本中,第一個參數變成了org.springframework.web.reactive.function.server.ServerRequest類型,需要引入jar包

但是此段代碼不能適用。如何自定義詳情查看Spring Boot官方文檔

Spring 5 之 Spring Webflux 開發 Reactive 應用

<!-- 此依賴 會依賴於Netty -->
<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-webflux</artifactId>    <!--【改】增加“flux”四個字符-->
</dependency>
//import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;   reactive 也有這個類,但是執行不了







免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM