文章目录
1、默认机制
SpringBoot错误处理的自动配置都在ErrorMvcAutoConfiguration
中,两大核心机制:
2、使用@ExceptionHandler标识一个方法,处理用@Controller标注的该类发生的指定错误
// @ExceptionHandler源码
@Target({ElementType.METHOD}) // 指定了该注解仅能用于方法
@Retention(RetentionPolicy.RUNTIME) //指定了注解会在运行时保留,这允许通过反射在运行时获取对注解的访问
@Documented //注解应该被包含在生成的 JavaDoc 文档中
@Reflective({ExceptionHandlerReflectiveProcessor.class})
public @interface ExceptionHandler {
Class<? extends Throwable>[] value() default {};
//它定义了一个名为 value 的属性,其类型是一个 Class 数组,这个数组的元素必须是 Throwable 类或其子类。通过使用这个注解时,可以为 value 属性提供一个 Throwable 类型的数组
}
1).局部错误处理部分源码
@Controller //适配服务端渲染 前后不分离模式开始
public class WelcomeController {
//来到首页
@GetMapping("/")
public String index(){
int i=10/0; //制造错误
return "index";
}
@ResponseBody
//返回的字符串应该直接作为 HTTP 响应体的内容,而不是作为视图名称解析。通常用于返回 JSON 或纯文本等非HTML内容
@ExceptionHandler(Exception.class) //传递到value数组中
public String handleException(Exception e){
return "Ohho~~~,原因:"+e.getMessage();
}
}
2).测试
3、 创建一个全局错误处理类集中处理错误,使用==@ControllerAdvice==注解标注
//@ControllerAdvice源码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //标记类为一个 Spring 组件
public @interface ControllerAdvice {
@AliasFor(
annotation = Component.class,
attribute = "value"
)
String name() default "";
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] assignableTypes() default {};
Class<? extends Annotation>[] annotations() default {};
}
1).全局错误处理部分源码
@ControllerAdvice //这个类是集中处理所有@Controller发生的错误
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
return "Ohho~~~统一处理所有错误,原因:"+e.getMessage();
}
}
###2).再创建一个类,使用@Controller注解标注
@Controller
public class HelloController {
@GetMapping("/haha")
public String haha(){
int i = 10/0; //制造错误
return "index";
}
}
3).测试
- 1.启动项目,浏览器访问
http://localhost:8080/
- 2.测试结果(成功处理错误)就近原则,执行的是@Controller类中标注了@ExceptionHandler方法的处理:
- 3.浏览器访问
http://localhost:8080/haha
:全局错误处理类进行处理
4、SpringMVC错误处理未能处理(上述处理不存在)
第一阶段的处理未解决,错误转发到/error,执行后续处理(图中第一阶段失效,第二阶段处理),以下测试过程中,
注释掉上文中的全局和局部处理代码
1、SpringBoot中自动配置的错误处理机制,基于ErrorMvcAutoConfiguration
自动配置实现
在ErrorMvcAutoConfiguration
自动装配类中,SpringBoot在底层写好一个 ==BasicErrorController
==的组件,专门处理/error
这个请求,部分源码如下:
@AutoConfiguration(before = WebMvcAutoConfiguration.class) //指定了该自动配置类在 WebMvcAutoConfiguration 之前进行配置
@ConditionalOnWebApplication(type = Type.SERVLET) //只有在当前应用是一个 Servlet Web 应用时,这个自动配置才会生效
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class }) //只有当类路径中存在 Servlet 和 DispatcherServlet 时,这个自动配置才会生效
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class }) //启用指定类的配置属性绑定
public class ErrorMvcAutoConfiguration {
private final ServerProperties serverProperties; //注入了 ServerProperties 实例。这样的构造方法注入是为了获取应用程序的服务器配置
public ErrorMvcAutoConfiguration(ServerProperties serverProperties) {
this.serverProperties = serverProperties; //注入 ServerProperties 实例
}
//容器中不存在 ErrorAttributes 类型的 Bean 时,才会创建并注册当前方法所返回的 Bean
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes();
}
//在容器中不存在 ErrorController 类型的 Bean 时,才会创建并注册当前方法所返回的 Bean
@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().toList());
}
...
}
2、 BasicErrorController
组件
SpringBoot中默认的server.error.path=/error,即该类就是处理/error
请求的,根据不同类型的请求,如果产生 HTML 内容的请求,匹配public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response)
这个方法;否则其他请求类型,匹配public ResponseEntity<Map<String, Object>> error(HttpServletRequest request)
方法。分别进行处理。(只会匹配其中一个,Spring MVC会尝试匹配与请求路径最匹配的RequestMapping)
- 1.部分源码:
@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {
private final ErrorProperties errorProperties;
/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
*/
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
this(errorAttributes, errorProperties, Collections.emptyList());
}
/**
* Create a new {@link BasicErrorController} instance.
* @param errorAttributes the error attributes
* @param errorProperties configuration properties
* @param errorViewResolvers error view resolvers
*/
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,
List<ErrorViewResolver> errorViewResolvers) {
super(errorAttributes, errorViewResolvers);
Assert.notNull(errorProperties, "ErrorProperties must not be null");
this.errorProperties = errorProperties;
}
@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
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、解析错误的自定义视图地址 ModelAndView modelAndView = resolveErrorView(request, response, status, model); //2、如果解析不到错误页面的地址,默认的错误页就是 error return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
-
3.在
ErrorMvcAutoConfiguration
自动装配类中,SpringBoot在底层写好一个 ==DefaultErrorViewResolver
==的组件,注入到了容器中@Configuration(proxyBeanMethods = false) @EnableConfigurationProperties({ WebProperties.class, WebMvcProperties.class }) static class DefaultErrorViewResolverConfiguration { private final ApplicationContext applicationContext; private final Resources resources; DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, WebProperties webProperties) { this.applicationContext = applicationContext; this.resources = webProperties.getResources(); } @Bean @ConditionalOnBean(DispatcherServlet.class) @ConditionalOnMissingBean(ErrorViewResolver.class) DefaultErrorViewResolver conventionErrorViewResolver() { return new DefaultErrorViewResolver(this.applicationContext, this.resources); } }
-
4.在
DefaultErrorViewResolver
中定义了错误页的默认规则,功能如下:- 如果在类路径下,查看是否存在
error/错误码
的页面,存在就返回该视图; - 否则,去4个CLASSPATH_RESOURCE_LOCATIONS路径下,查看是否写了
error/错误码.html
的页面,存在则返回该视图; - 都不存在,则
modelAndView=null
;则判断是否有error/4xx
或者error/5xx
的页面,存在则返回该视图 - 否则,去4个CLASSPATH_RESOURCE_LOCATIONS路径下,查看是否写了
error/4xx.html
或者error/5xx.html
的页面,存在则返回该视图;
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered { ... private static final Map<Series, String> SERIES_VIEWS; 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); } // 查看是否存在 error/错误码 的页面,存在就返回该视图 // 不存在,则判断是否有 error/4xx 或者 error/5xx 的页面,存在则返回该视图 @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; } // 在类路径下,查看是否存在 error/错误码 的模板引擎,存在就返回该视图; // 否则去下面的4个CLASSPATH_RESOURCE_LOCATIONS路径下,查看是否写了 error/错误码.html 的页面,存在则返回该视图 private ModelAndView resolve(String viewName, Map<String, Object> model) { String errorViewName = "error/" + viewName; TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); if (provider != null) { return new ModelAndView(errorViewName, model); } return resolveResource(errorViewName, model); } //去下面的4个CLASSPATH_RESOURCE_LOCATIONS路径下,查看是否写了 error/错误码.html 的页面,存在则返回该视图 //this.resources.getStaticLocations() //private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", // "classpath:/resources/", "classpath:/static/", "classpath:/public/" }; private ModelAndView resolveResource(String viewName, Map<String, Object> model) { for (String location : this.resources.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; } ... }
- 如果在类路径下,查看是否存在
-
5.在
ErrorMvcAutoConfiguration
自动装配类中,向容器中放入了一个默认名为error的视图,提供了默认的白页功能,如果上述都无法处理@Configuration(proxyBeanMethods = false) @ConditionalOnProperty(prefix = "server.error.whitelabel", name = "enabled", matchIfMissing = true) @Conditional(ErrorTemplateMissingCondition.class) protected static class WhitelabelErrorViewConfiguration { private final StaticView defaultErrorView = new StaticView(); // 注入了error视图 @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; } }
默认的白页源码,就在类
ErrorMvcAutoConfiguration
中定义的private static class StaticView implements View { private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8); private static final Log logger = LogFactory.getLog(StaticView.class); @Override public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception { if (response.isCommitted()) { String message = getMessage(model); logger.error(message); return; } response.setContentType(TEXT_HTML_UTF8.toString()); StringBuilder builder = new StringBuilder(); Object timestamp = model.get("timestamp"); Object message = model.get("message"); Object trace = model.get("trace"); if (response.getContentType() == null) { response.setContentType(getContentType()); } builder.append("<html><body><h1>Whitelabel Error Page</h1>") .append("<p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p>") .append("<div id='created'>") .append(timestamp) .append("</div>") .append("<div>There was an unexpected error (type=") .append(htmlEscape(model.get("error"))) .append(", status=") .append(htmlEscape(model.get("status"))) .append(").</div>"); if (message != null) { builder.append("<div>").append(htmlEscape(message)).append("</div>"); } if (trace != null) { builder.append("<div style='white-space:pre-wrap;'>").append(htmlEscape(trace)).append("</div>"); } builder.append("</body></html>"); response.getWriter().append(builder.toString()); } private String htmlEscape(Object input) { return (input != null) ? HtmlUtils.htmlEscape(input.toString()) : null; } private String getMessage(Map<String, ?> model) { Object path = model.get("path"); String message = "Cannot render error page for request [" + path + "]"; if (model.get("message") != null) { message += " and exception [" + model.get("message") + "]"; } message += " as the response has already been committed."; message += " As a result, the response may have the wrong status code."; return message; } @Override public String getContentType() { return "text/html"; } }
-
6.在
ErrorMvcAutoConfiguration
自动装配类中,封装了JSON格式的错误信息(初始化了错误的类型、错误的状态码、路径、栈信息、时间戳等信息)@Bean @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) public DefaultErrorAttributes errorAttributes() { return new DefaultErrorAttributes(); }
规则如下:
-
1.解析一个错误页:
5、自定义错误响应
1) 自定义json响应:使用文章第2和第3部分介绍的,使用注解进行统一的异常处理
2)自定义页面响应:根据第4部分介绍的规则,在对应的项目路径"classpath:/METAINF/resources/","classpath:/resources/","classpath:/static/", "classpath:/public/"
或者模板引擎目录下,定义错误页面即可。
6、测试
1、在项目的resources/templates/目录下创建一个error文件夹,放入4xx.html、5xx.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
4xx.html
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
5xx.html
</body>
</html>
2、在项目的resources/templates/error文件夹中,再放入404.html、500.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
404.html
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
500.html
</body>
</html>
3、浏览器先后测试上述情况
原文地址:https://blog.csdn.net/qq_45929019/article/details/134764187
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_37504.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!