栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

自定义错误处理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

自定义错误处理

文章目录

自定义错误页面@ControllerAdvice和@ExceptionHandler处理全局异常@ResponseStatus和自定义异常自定义HandlerExceptionResolver
要自定义错误处理,可以

    自定义错误页面。@ControllerAdvice和@ExceptionHandler处理全局异常。@ResponseStatus和自定义异常。自定义异常处理解析器:实现HandlerExceptionResolver。
自定义错误页面

关于自定义错误页面,SpringBoot的错误处理和错误处理原理(自动配置解析篇)都介绍过。
在error目录下创建错误视图文件,如/error/404.html、/error/4xx.html、/error/5xx.html。
当响应异常时,如果有与响应状态码精确匹配的错误视图页面,则返回视图页面;
如果没有精确匹配,就找/error/4xx.html或/error/5xx.html,并返回可模糊匹配的视图页面;
如果还找不到,则返回whitelabel页面。

看个具体的例子。
新建Spring项目:demo10,添加依赖:Spring Web、Spring Configuration Processor、Lombok和Thymeleaf。
com.example.boot下新建控制器controller.IndexControlller。

package com.example.boot.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class IndexController {

    @GetMapping("/add")
    public String add(@RequestParam("a") int a,@RequestParam("b") int b){
        int c = a + b;
        return "success";
    }

    @GetMapping("/divide")
    public String divide(){
        int i = 10/0;
        return "success";
    }
}

resources.templates下新建视图文件:index.html和succes.html。




    
    Index


    加法:400
减法:404
除法:500



    
    success


成功

resources.templates下新建目录error,error下新建404.html和5xx.html。




  
  404


I'm 404.html

【status】

【error】




  
  5xx


I'm 5xx.html

【status】

【error】

启用应用,测试效果。

@ControllerAdvice和@ExceptionHandler处理全局异常

com.example.boot下新建exception.GlobalExceptionHandler,如下。

package com.example.boot.exception;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({ArithmeticException.class,NullPointerException.class})
    public String handleArithAndNullException(Exception e){
        log.error("出现异常,如下:",e);
        return "exception";
    }
}

用@ControllerAdvice注解,表明GlobalExceptionHandler用来处理web controller异常。
用@ExceptionHandler注解,表示handleArithAndNullException方法处理指定异常:数学运算异常(ArithmeticException)和空指针异常(NullPointerException)。

另外,slf4j打印异常时,异常是不需要占位符的。即,
正确:log.error("出现异常,如下:",e);
错误:log.error("出现异常,如下:{}",e);

resources.templates下新建视图文件exception.html,如下。




    
    success


成功

重启应用,测试效果。

通过调试源码来理解异常处理原理。

    DispatcherServlet#doDispatch
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	try {
		try {
			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
}
    DispatcherServlet#processDispatchResult
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
		@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
		@Nullable Exception exception) throws Exception {
	mv = processHandlerException(request, response, handler, exception);
}
    DispatcherServlet#processHandlerException
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {
	// Check registered HandlerExceptionResolvers...
	ModelAndView exMv = null;
	if (this.handlerExceptionResolvers != null) {
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			exMv = resolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
	}
}
    HandlerExceptionResolverComposite#resolveException
    找到能够处理该控制器异常的解析器,找到了ExceptionHandlerExceptionResolver
public ModelAndView resolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

	if (this.resolvers != null) {
		for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
			ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
			if (mav != null) {
				return mav;
			}
		}
	}
	return null;
}
    AbstractHandlerExceptionResolver#resolveException
public ModelAndView resolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	ModelAndView result = doResolveException(request, response, handler, ex);
}
    AbstractHandlerMethodExceptionResolver#doResolveException
protected final ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
	return doResolveHandlerMethodException(request, response, handlerMethod, ex);
}
    ExceptionHandlerExceptionResolver#doResolveHandlerMethodException
    找到了处理异常的控制器方法后,后面的过程和普通控制器方法的执行过程一样。
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
		HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
	ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
	exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
}


但,处理异常的控制器方法是怎么找到的呢?一步步来。

a)ExceptionHandlerExceptionResolver#getExceptionHandlerMethod

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
		@Nullable HandlerMethod handlerMethod, Exception exception) {
	for (Map.Entry entry : this.exceptionHandlerAdviceCache.entrySet()) {
		ControllerAdviceBean advice = entry.getKey();
		if (advice.isApplicableToBeanType(handlerType)) {
			ExceptionHandlerMethodResolver resolver = entry.getValue();
			Method method = resolver.resolveMethod(exception);
		}
	}
}


b)ExceptionHandlerMethodResolver#resolveMethod

public Method resolveMethod(Exception exception) {
	return resolveMethodByThrowable(exception);
}

c)ExceptionHandlerMethodResolver#resolveMethodByThrowable

public Method resolveMethodByThrowable(Throwable exception) {
	Method method = resolveMethodByExceptionType(exception.getClass());
	return method;
}

d)ExceptionHandlerMethodResolver#resolveMethodByExceptionType

public Method resolveMethodByExceptionType(Class exceptionType) {
	Method method = this.exceptionLookupCache.get(exceptionType);
	if (method == null) {
		method = getMappedMethod(exceptionType);
		this.exceptionLookupCache.put(exceptionType, method);
	}
	return (method != NO_MATCHING_EXCEPTION_HANDLER_METHOD ? method : null);
}

e)ExceptionHandlerMethodResolver#getMappedMethod

private Method getMappedMethod(Class exceptionType) {
	List> matches = new ArrayList<>();
	for (Class mappedException : this.mappedMethods.keySet()) {
		if (mappedException.isAssignableFrom(exceptionType)) {
			matches.add(mappedException);
		}
	}
	if (!matches.isEmpty()) {
		if (matches.size() > 1) {
			matches.sort(new ExceptionDepthComparator(exceptionType));
		}
		return this.mappedMethods.get(matches.get(0));
	}
	else {
		return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
	}
}

以上步骤,重点关注ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache属性、ExceptionHandlerMethodResolver的mappedMethods属性。这两个属性的值,在应用启动时就已经确定了。

启动应用调试一下就清楚了,以下是部分有关源码。

//ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache
private void initExceptionHandlerAdviceCache() {
	List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
	for (ControllerAdviceBean adviceBean : adviceBeans) {
		Class beanType = adviceBean.getBeanType();
		ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
		if (resolver.hasExceptionMappings()) {
			this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
		}
	}
}

//ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver
public ExceptionHandlerMethodResolver(Class handlerType) {
	for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
		for (Class exceptionType : detectExceptionMappings(method)) {
			addExceptionMapping(exceptionType, method);
		}
	}
}

private void addExceptionMapping(Class exceptionType, Method method) {
	Method oldMethod = this.mappedMethods.put(exceptionType, method);
	if (oldMethod != null && !oldMethod.equals(method)) {
		throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
				exceptionType + "]: {" + oldMethod + ", " + method + "}");
	}
}

    ServletInvocableHandlerMethod#invokeAndHandle
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
this.returnValueHandlers.handleReturnValue(
		returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
@ResponseStatus和自定义异常

在com.example.boot.exception下定义自定义异常:UserTooManyException,如下。
一旦UserTooManyException被抛出且捕获,将返回状态码403(HttpStatus.FORBIDDEN),异常原因是”用户数量太多“。

package com.example.boot.exception;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户数量大多")
public class UserTooManyException extends RuntimeException{
    public UserTooManyException(){

    }

    public UserTooManyException(String message){
        super(message);
    }
}

resources.templates.error下新建错误视图文件:4xx.html,如下。




  
  4xx


I'm 4xx.html

【status】

【error】

com.example.boot.controller.IndexController中添加一个新接口:/user,访问该接口时将抛出自定义异常UserTooManyException,如下。

@GetMapping("/user")
public String user(){
    String[] users = {"zhangsan","lisi","wangwu","zhaoliu"};
    if(users.length>3){
        throw new UserTooManyException();
    }
    return "success";
}

重启应用,访问接口/user。

通过调试源码来理解是怎么处理自定义异常的。

    DispatcherServlet#doDispatch,同上。DispatcherServlet#processDispatchResult,同上。DispatcherServlet#processHandlerException,同上。HandlerExceptionResolverComposite#resolveException,同上。
    遍历找到能处理该控制器异常的解析器,找到了ResponseStatusExceptionResolver。AbstractHandlerExceptionResolver#resolveException,同上。ResponseStatusExceptionResolver#doResolveException
    发现@ResponseStatus注解,将获取@ResponseStatus中的信息。
protected ModelAndView doResolveException(
		HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
		ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
	}
		if (status != null) {
			return resolveResponseStatus(status, request, response, handler, ex);
		}
}
    ResponseStatusExceptionResolver#resolveResponseStatus
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,
		HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {

	int statusCode = responseStatus.code().value();
	String reason = responseStatus.reason();
	return applyStatusAndReason(statusCode, reason, response);
}
    ResponseStatusExceptionResolver#applyStatusAndReason
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
		throws IOException {
	String resolvedReason = (this.messageSource != null ?
			this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
			reason);
	response.sendError(statusCode, resolvedReason);
	return new ModelAndView();
}

获取@ResponseStatus注解的信息后,调用response.sendError(statusCode, resolvedReason),底层将发送/error请求

自定义HandlerExceptionResolver

自定义异常解析器(HandlerExceptionResolver),实现HandlerExceptionResolver接口即可。
在com.example.boot.exception下新建CustomHandlerExceptionResolver,如下:

package com.example.boot.exception;

import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
public class CustomExceptionHandlerResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            response.sendError(555,"其他异常");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

@Component,将自定义的异常解析器CustomHandlerExceptionResolver放入容器。
@Order(value = Ordered.HIGHEST_PRECEDENCE),将自定义的异常解析器CustomHandlerExceptionResolver优先级置为最高,所以它将优先处理异常。

启动应用,访问接口:/user,响应如下:

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/712092.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号