2020:0629--07--SpringBoot错误处理机制

812 阅读3分钟

1. SpringBoot默认的错误处理机制

1.1 默认效果

默认效果:
    1). 返回一个默认的错误页面

    2). 其他客户端访问,默认响应JSON数据
        
        浏览器响应数据
        其他客户端响应JSON数据
    
        但是实际情况,不只是电脑会发送这个请求。可能有手机,平板等。
        模拟一下其他客户端工具。    Postman
        
        发现其他客户端访问时,响应的是json数据:
        {
            "timestamp": "2020-06-30T12:17:14.545+00:00",
            "status": 404,
            "error": "Not Found",
            "message": "",
            "path": "/crud/aaa"
        }

1.2 原理

    参照:
        org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration
        org.springframework.boot.autoconfigure.web.ErrorProperties
    错误处理的自动配置
    
    1). 如何定制错误响应:
        1.  如何定制错误页面
        
        2.  如何定制错误的JSON(针对其他的客户端)
        
    2). ErrorMvcAutoConfiguration添加的组件
    
        ErrorMvcAutoConfiguration给容器中添加了这几个重要的组件:
        
        1. DefaultErrorAttributes
        
            帮我们在页面共享信息,它给页面获取这几个属性:
            
            timestamp:时间戳
            status:状态码
            erro:错误提示
            exception:异常对象
            message:异常消息
            errors:(BingdingResult)JSR数据校验有关
            
        2. ErrorPageCustomizer  :   定制的错误页面
        3. BasicErrorController
        4. DefaultErrorViewResolver
        
    
    3). 步骤
        
        1.  ErrorPageCustomizer  :   定制的错误页面
        一旦系统出现4XX或者5XX之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则)。
        
        追踪源码:
            ErrorPageCustomizer构造方法中会注册一个错误页面
            
            注册的错误页面的路径是:"/error";
            	@Value("${error.path:/error}")
            	private String path = "/error";
            所以出现错误页面后ErrorPageCustomizer就会生效,定制错误的响应规则,就会发送/error请求。
            
        2.  BasicErrorController:基本的错误处理器。
            @Controller
            @RequestMapping("${server.error.path:${error.path:/error}}")
            public class BasicErrorController extends AbstractErrorController {
            ${server.error.path:${error.path:/error}}:映射${server.error.path}的值,如果没有
            就映射${error.path:/error}。
            映射到后,BasicErrorController就来处理。
            
           BasicErrorController处理默认的/error请求。
           
           
        3.  BasicErrorController的处理方式
    //响应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, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		
		//去哪个页面作为错误页面:ModelAndView中包含页面地址和页面内容
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

    //响应JSON数据的:其他客户端发送的请求来这里处理
	@RequestMapping
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}
            所以浏览器响应的是HTML页面,其他端响应的是JSON数据,
            
        4.  怎么知道是浏览器还是其他客户端呢?
            
            浏览器发送请求的请求头中有识别标志:Accept: text/html

            其他客户端请求的请求头中有识别标志:Accept: "*/*"

        5.  响应页面,怎么来到这个个页面?
	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;
	}

            去哪个页面是由DefaultErrorViewResolver解析得到的。
        
        6.  DefaultErrorViewResolver
	@Override
	public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
		ModelAndView modelAndView = resolve(String.valueOf(status.value()), 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+错误状态码
		String errorViewName = "error/" + viewName;
		
		//如果模版引擎可以解析这个页面地址:就用模版引擎
		TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName,
				this.applicationContext);
		if (provider != null) {
		    //模版引擎可用的情况下返回到errorViewName指定的视图地址
			return new ModelAndView(errorViewName, model);
		}
		
		//模版引擎不可用,就在静态资源文件夹下找errorViewName对应的页面。 
		//error+404.html
		return resolveResource(errorViewName, model);
	}
	
	private ModelAndView resolveResource(String viewName, Map<String, Object> model) {
	for (String location : this.resourceProperties.getStaticLocations()) {
		try {
			Resource resource = this.applicationContext.getResource(location);
			
			//模版引擎不可用,就在静态资源文件夹下找errorViewName对应的页面。 
			resource = resource.createRelative(viewName + ".html");
			
			if (resource.exists()) {
				return new ModelAndView(new HtmlResourceView(resource), model);
			}
		}
		catch (Exception ex) {
		}
	}
	return null;
}
            7. DefaultErrorAttributes
        
            帮我们在页面设置信息,它给页面获取这几个属性:
            
            timestamp:时间戳
            status:状态码
            erro:错误提示
            exception:异常对象
            message:异常消息
            errors:(BingdingResult)JSR数据校验有关 

1.3 如何定制错误页面

1.3.1 没有模版引擎的情况下

    1). 有模版引擎的情况下:error/状态码
        
        Thymeleaf默认取到classpath:/templates/error路径下解析。
        
        小结:有模版引擎的情况下,将错误页面命名为错误状态码.html,然后放在
        classpath:/templates/error文件夹下。发生此状态码的错误就会来到该页面。
        
        但是错误类型繁多,所以用量一种写法。
    
    2). 模糊匹配 4XX.html和5XX.html
        
        精确优先。
        没有精确的匹配的话,就匹配4XX.html
    
    3). 定制错误页面    
        根据DefaultErrorAttributes获取的信息,我们在自己的错误页面上定制
        
        timestamp:时间戳
        status:状态码
        erro:错误提示
        exception:异常对象
        message:异常消息
        errors:(BingdingResult)JSR数据校验有关  
        
        1. 在定制4XX.html页面
			<h1>status:[[${status}]]</h1>
			<h2>timestamp:[[${timestamp}]]</h2>
			<h2>error:[[${error}]]</h2>
        2. 效果

1.3.2 不在模版引擎的目录下--或者说模版引擎找不到这个错误页面

    或者说模版引擎找不到这个错误页面,会去到静态资源文件夹下找错误页面。
    
    能找到,但是由于不在模版引擎解析的目录下,就无法解析想要定制的信息,

1.3.3 静态资源目录和模版引擎都没有时

    就是默认来到SpringBoot默认的空白页面。

1.4 如何定制错误的JSON数据

1.4.1 自定义一个异常

    SpringBoot做了一个自适应的效果,浏览器访问会响应错误页面,其他客户端会响应错误JSON数据。
    
    
    1.  自定义一个异常类:UserNotExistException
        public class UserNotExistException extends RuntimeException{
        
            public UserNotExistException(){
                super("用户不存在");
            }
        }
    2.  controller
            @ResponseBody
            @RequestMapping("/hello")
            public String hello(@RequestParam("user") String user){
                if(user.equals("aaa")){
                    //抛一个自定义异常
                    throw new UserNotExistException();
                }
                return "Hello World";
            }
        如果请求的参数中有user=aaa,那么就会抛出自定义异常。
        
    3.  自定义5XX.html错误页面
		<h1>status:[[${status}]]</h1>
		<h2>timestamp:[[${timestamp}]]</h2>
		<h2>error:[[${error}]]</h2>
		<h2>exception:[[${exception}]]</h2>
		<h2>message:[[${message}]]</h2>
    4.  效果展示:http://localhost:8080/crud/hello?user=aaa

    5.  开启配置
        # 开启获取自定义异常类exception。
        server.error.include-exception=true
        # 开启获取自定义异常信息message
        server.error.include-message=always
    6.  效果展示

    7.  其他端效果展示
        {
            "timestamp": "2020-06-30T14:03:05.992+00:00",
            "status": 500,
            "error": "Internal Server Error",
            "exception": "com.atguigu.springboot.exception.UserNotExistException",
            "message": "用户不存在",
            "path": "/crud/hello"
        }

1.4.2 自定义一个异常处理器

    1. 写一个自己的异常处理器:MyExceptionHandler
        @ControllerAdvice
        public class MyExceptionHandler {
        
            /**
             * 指明要处理的异常:UserNotExistException.class
             * 所有异常:Exception.class
             * @return
             */
            @ResponseBody //将map以json数据形式返回
            @ExceptionHandler(UserNotExistException.class)
            public Map<String, Object> handlerException(Exception e){
                //捕获该异常后,将异常对象传进来Exception e
        
                Map<String, Object> map = new HashMap<>();
        
                map.put("code", "user.notexist");
                map.put("message", e.getMessage());
        
                return map;
        
            }
        }
    自定义处理器的几个注意:
    
        1.  Spring中异常处理器要标注@ControllerAdvice
        
        2.  @ExceptionHandler(UserNotExistException.class)标注在handlerException方法上
            SpirngMVC的注解
            捕获要处理的异常:UserNotExistException.class
            所有异常:Exception.class
            
        3.  public Map<String, Object> handlerException(Exception e){}
            方法参数是:捕获的异常对象
            
        4.  @ResponseBody 将定义的错误的信息存在map并且以json数据形式返回
        
2.  效果展示:没有自适应效果。

    1.  其他端

    2.  浏览器客户端

    发现浏览器也响应的是JSON数据。
    
    3.  原因就是我们在自定义异常处理器中,定义的处理规则返回JSON形式的map。
    
        浏览器和其他客户端被响应的都是数据

1.4.3 自适应的自定义异常处理器

    因为SpringBoot默认是由BasicErrorController这个基本的错误处理器来处理/error请求,
    而且BasicErrorController处理/error请求是自适应的。
    
    1.  所以我们的自定义异常在捕获的异常时,定义好JSON数据后,请求转发到/error请求。
        那么BasicErrorController就会自适应的处理/error请求。
        @ExceptionHandler(UserNotExistException.class)
        public String handlerException(Exception e){
            //捕获该异常后,将异常对象传进来Exception e
    
            Map<String, Object> map = new HashMap<>();
    
            map.put("code", "user.notexist");
            map.put("message", e.getMessage());
    
            //因为BasicErrorController处理/error请求是自适应的。
            return "forward:/error";
    
        }
    2. 出现问题:
        
        没有来到自定义的错误页面,也没有显示自定义的错误信息。

        发现页面的状态码是:200。
        
    3.  传入我们自己的错误状态码:不传的话默认是200
    
        看一下SpringBoot中咋么获取状态码:
        1.AbstractErrorController : BasicErrorController的父类

Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);

        2.RequestDispatcher

String ERROR_STATUS_CODE = "javax.servlet.error.status_code";

    4.  自定义异常处理器代码改进
    @ExceptionHandler(UserNotExistException.class)
    public String handlerException(Exception e, HttpServletRequest request){
        Map<String, Object> map = new HashMap<>();

        //传入我们自己的错误状态码
        /**
         *  Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
         *  String ERROR_STATUS_CODE = "javax.servlet.error.status_code";
         */
        request.setAttribute("javax.servlet.error.status_code", 400);

        map.put("code", "user.notexist");
        map.put("message", e.getMessage());

        //因为BasicErrorController处理/error请求是自适应的。
        return "forward:/error";

    }
    5.  效果展示

1.4.4 将我们定制的数据携带出去。

    1. 注意上面的做的结果还有一个问题:
    
        自适应效果有了,但是显示的message是自定义类中的message,而不是我们在异常处理器中自定义的。

    2.  怎么将我们在自定义异常处理器中存在map中的数据携带出去呢?
    
        出现异常以后,会来到/error请求,会被BasicErrorController处理。
        
        响应出去的数据都是由:
            getErrorAttributes()方法得到的。
        
        BasicErrorController处理结果:
            方法1:如果是页面返回ModelAndView
            方法2:若是其他端也是以JSON形式返回一个Map<String,Object> body
        
        响应出去的数据都是由:
            getErrorAttributes()方法得到的。
            AbstractErrorController的方法, 它是BasicErrorController的父类。
            ErrorController是AbstractErrorController的接口
            
    3.  ErrorMvcAutoConfiguration添加的BasicErrorController组件时的条件
	@Bean
	@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
	public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
			ObjectProvider<ErrorViewResolver> errorViewResolvers) {
		return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
				errorViewResolvers.orderedStream().collect(Collectors.toList()));
	}

@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)

        没有ErrorController组件时,会添加一个BasicErrorController组件。
        
        继承关系:
        BasicErrorController extends AbstractErrorController implements ErrorController
        
        所以BasicErrorController实现了ErrorController
        
    4.  解决方法1.
    
        我们来另外编写一个ErrorController的实现类(或者AbstractErrorController的子类),
        放到容器中。那么SpringBoot中的异常相关的自动配置类ErrorMvcAutoConfiguration
        就不会再添加BasicErrorController组件了。
        
        用我们自己的异常处理器,来完成我们要求的异常处理规则。
        
        我们来写一个AbstractErrorController的子类,重写返回ModelAndView的方法和返回JSON的方法。
        
        
        但是太复杂,用另一种方法

1.4.5 解决方法2:原理分析

    1.  给页面携带的数据:ModelAndView
        响应给JSON的数据:Map<String,Object>
    
    2.  而且这俩个响应的数据都是通过getErrorAttributes()方法得到的。

        getErrorAttributes()方法里面是:
            return this.errorAttributes.getErrorAttributes
            响应到页面上的数据和响应的JSON,都是通过这个方法得到的
        
    3.  errorAttributes是什么:
        private final ErrorAttributes errorAttributes;
        
    4.  ErrorMvcAutoConfiguration在进行配置时:如果没有ErrorAttributes会自动配置一个
        DefaultErrorAttributes
        相当于DefaultErrorAttributes默认进行数据处理的。
        
    5.  DefaultErrorAttributes中有哪些数据:
            其中有一个map中加入了很多属性。

	@Bean
	@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
	public DefaultErrorAttributes errorAttributes() {
		return new DefaultErrorAttributes();
	}
    6.  所以我们可以自己设置这些信息,然后加到容器中。
    
    7.  小结:
        return this.errorAttributes.getErrorAttributes。响应到页面上的数据和响应的JSON,
        都是通过这个方法得到的。
        
        errorAttributes对象是DefaultErrorAttributes类
        DefaultErrorAttributes中实现ErrorAttributes接口方法getErrorAttributes()
        
        所以我们自定义一个类继承DefaultErrorAttributes然后重写其中的getErrorAttributes()方法,
        在方法里面重新定义属性信息等。
        
        也就是将我们这个重写的继承DefaultErrorAttributes注入到IOC中,那么当BasicErrorController
        来处理/error时,就从this.errorAttributes.getErrorAttributes()得到返回的数据。
        注意:重点来了。
            this.errorAttributes.getErrorAttributes():
            errorAttributes是DefaultErrorAttributes类的实例化对象
            而此时你自定义了一个类继承了DefaultErrorAttributes重写了其中的getErrorAttributes()方法
            所以BasicErrorController再处理/error时,也会调用我们自定义的类中getErrorAttributes()
            方法,获取我们自定以的值。

1.4.6. 解决方法

   1.   代码 
    //给容器中加入我们自己定义的错误属性。
    @Component
    public class MyErrorAttributes extends DefaultErrorAttributes {
    
        @Override
        public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
            Map<String, Object> map = getErrorAttributes(webRequest, options);
    
            map.put("company", "陶大哥");
    
            return map;
        }
    }
    2.  效果展示
    
        响应的JSON数据:

    3. BasicErrorController 源码
        
        自适应响应数据的两个方法:都从getErrorAttributes()方法中取数据。
	@RequestMapping(produces = MediaType.TEXT_HTML_VALUE) //响应HTML
	public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
		HttpStatus status = getStatus(request);
		Map<String, Object> model = Collections
				.unmodifiableMap(getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
		response.setStatus(status.value());
		ModelAndView modelAndView = resolveErrorView(request, response, status, model);
		return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
	}

	@RequestMapping     //响应JSON
	public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
		HttpStatus status = getStatus(request);
		if (status == HttpStatus.NO_CONTENT) {
			return new ResponseEntity<>(status);
		}
		Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
		return new ResponseEntity<>(body, status);
	}

1.5 将我们自定义异常处理类中的信息页携带过去。

    1. 将自定义异常处理类中的信息存入request中
    @ExceptionHandler(UserNotExistException.class)
    public String handlerException(Exception e, HttpServletRequest request){
        //捕获该异常后,将异常对象传进来Exception e

        Map<String, Object> map = new HashMap<>();

        //传入我们自己的错误状态码
        /**
         *  Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
         *  String ERROR_STATUS_CODE = "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);

        //因为BasicErrorController处理/error请求是自适应的。
        return "forward:/error";

    }
    2.  在重写的错误属性类中,将自定义异常处理类中的信息存入map
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {


    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {

        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);

        errorAttributes.put("company", "atguigu");

        /**
         * 0:request
         * 1:session
         * 2:global_session
         *
         * webRequest是一个封装了的Request对象
         * 得到异常处理器携带的数据
         */
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);

        errorAttributes.put("ext", ext);

        return errorAttributes;
    }
}

        注意:
            webRequest是一个封装了的Request对象可以获得request中的参数
            webRequest.getAttribute("ext", 0) 第二个参数代表域:
                0:request
                1:session
                2:global_session
                
                
    3. 效果演示:
        
        3.1 响应JSON

        3.2 响应HTML

几个关键点:
        
        1.  出现异常以后,会来到/error请求,会被BasicErrorController自适应的处理。
        
        2.  BasicErrorController处理结果:
            方法1:如果是页面返回ModelAndView
            方法2:若是其他端也是以JSON形式返回一个Map<String,Object> body
            
        3.  响应出去的数据都是由:
            getErrorAttributes()方法得到的。
            getErrorAttributes()方法里面是:
            return this.errorAttributes.getErrorAttributes
            
            响应到页面上的数据和响应的JSON,都是通过这个方法得到的
            
        6.  ErrorMvcAutoConfiguration在进行配置时:如果没有ErrorAttributes会自动配置一个
            DefaultErrorAttributes
            相当于DefaultErrorAttributes默认进行数据处理的。
            
            DefaultErrorAttributes中的方法getErrorAttributes()
            
        4.  继承关系:
            BasicErrorController extends AbstractErrorController implements ErrorController
        
        5.  没有ErrorController组件时,会添加一个BasicErrorController组件。	
            @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
            

            
    6.  小结:
        return this.errorAttributes.getErrorAttributes。响应到页面上的数据和响应的JSON,
        都是通过这个方法得到的。
        
        errorAttributes对象是DefaultErrorAttributes类
        DefaultErrorAttributes中实现ErrorAttributes接口方法getErrorAttributes()
        
        所以我们自定义一个类继承DefaultErrorAttributes然后重写其中的getErrorAttributes()方法,
        在方法里面重新定义属性信息等。
        
        也就是将我们这个重写的继承DefaultErrorAttributes注入到IOC中,那么当BasicErrorController
        来处理/error时,就从this.errorAttributes.getErrorAttributes()得到返回的数据。
        注意:重点来了。
            this.errorAttributes.getErrorAttributes():
            errorAttributes是DefaultErrorAttributes类的实例化对象
            而此时你自定义了一个类继承了DefaultErrorAttributes重写了其中的getErrorAttributes()方法
            所以BasicErrorController再处理/error时,也会调用我们自定义的类中getErrorAttributes()
            方法,获取我们自定以的值。                  

最终的效果:响应是自适应的,可以通过定制getErrorAttributes改变需要返回的内容。


### 2. 完整代码:

    1.  自定义异常类 UserNotExistException
public class UserNotExistException extends RuntimeException{

    public UserNotExistException(){
        super("用户不存在");
    }
}
    2.  自定义异常处理类 MyExceptionHandler
        
        是为了自定义错误的JSON数据,但是还是没有将在自定义异常处理类定义的异常信息响应过去。
@ControllerAdvice
public class MyExceptionHandler {

    @ExceptionHandler(UserNotExistException.class)
    public String handlerException(Exception e, HttpServletRequest request){
        //捕获该异常后,将异常对象传进来Exception e

        Map<String, Object> map = new HashMap<>();

        //传入我们自己的错误状态码
        /**
         *  Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
         *  String ERROR_STATUS_CODE = "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);

        //因为BasicErrorController处理/error请求是自适应的。
        return "forward:/error";

    }
}
    3.  抛自定义异常的异常Controller: HelloWorld
@Controller
public class HelloWorld {

    @ResponseBody
    @RequestMapping("/hello")
    public String hello(@RequestParam("user") String user){
        if(user.equals("aaa")){
            //抛一个自定义异常
            throw new UserNotExistException();
        }
        return "Hello World";
    }
}
    4.  定制错误页面:有模版引擎的话就去classpath:/templates/error路径下解析
    
    400.html
        <h1>status:[[${status}]]</h1>
        <h2>timestamp:[[${timestamp}]]</h2>
        <h2>error:[[${error}]]</h2>
    4XX.html
        <h1>status:[[${status}]]</h1>
        <h2>timestamp:[[${timestamp}]]</h2>
        <h2>error:[[${error}]]</h2>
    5XX.html
	<h1>status:[[${status}]]</h1>
	<h2>timestamp:[[${timestamp}]]</h2>
	<h2>error:[[${error}]]</h2>
	<h2>exception:[[${exception}]]</h2>
	<h2>message:[[${message}]]</h2>
	<h2>company:[[${company}]]</h2>
	<h2>ext:[[${ext.code}]]</h2>
	<h2>ext:[[${ext.message}]]</h2>
    5.  定义一个类继承了DefaultErrorAttributes重写了其中的getErrorAttributes()方法
        所以BasicErrorController再处理/error时,也会调用我们自定义的类中getErrorAttributes()
        方法,获取我们自定以的值。 
        
        同时将自定的异常处理类的异常信息加进来。
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {


    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {

        Map<String, Object> errorAttributes = super.getErrorAttributes(webRequest, options);

        errorAttributes.put("company", "atguigu");

        /**
         * 0:request
         * 1:session
         * 2:global_session
         *
         * webRequest是一个封装了的Request对象
         * 得到异常处理器携带的数据
         */
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);

        errorAttributes.put("ext", ext);

        return errorAttributes;
    }
}