java mvc异常捕获_【Java Web开发学习】Spring MVC异常统一处理

【Java Web开发学习】Spring MVC异常统一处理

文采有限,若有错误,欢迎留言指正。

目录

正文

异常处理是每一个系统必须面对的,对于Web系统异常必须统一处理,否者业务代码会被无穷无尽的异常处理包围。对于Spring MVC来说有以下几种异常处理方式。

1、使用@ControllerAdvice和@ExceptionHandler注解统一处理异常(推荐)

我们自定义一个全局异常处理类GlobalExceptionHandler打印异常信息到日志并且跳转到异常页面,看代码

packagecn.ycx.web.exception;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.apache.log4j.Logger;importorg.springframework.web.bind.annotation.ControllerAdvice;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.servlet.ModelAndView;/*** 全局异常处理

*@author杨崇兴 2018-07-05*/@ControllerAdvice//已经包含@Component注解,能被自动扫描

public classGlobalExceptionHandler {public Logger logger =Logger.getLogger(getClass());/*** 所有异常处理,返回名为error的视图

*@parame

*@return

*/@ExceptionHandler(value={Exception.class})publicModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {

printStackTrace(ex);

ModelAndView mav= newModelAndView();

mav.setViewName("error");returnmav;

}/*** 打印异常堆栈信息

*@paramex*/

private voidprintStackTrace(Exception ex) {

StringBuilder errors= newStringBuilder();

errors.append("【异常信息】\r\n");

errors.append(ex.getClass().getName());if (ex.getMessage() != null) {

errors.append(": ");

errors.append(ex.getMessage());

}for(StackTraceElement stackTraceElement : ex.getStackTrace()) {

errors.append("\r\n\tat ");

errors.append(stackTraceElement.toString());

}//打印异常堆栈信息

logger.fatal(errors.toString());

}

}

若异常返回的不是视图而是JSON数据对象怎么办呢?添加@ResponseBody注解,将方法的返回值直接写入到response的body区域。

/*** 所有异常处理

*@parame

*@return

*/@ExceptionHandler(value={Exception.class})

@ResponseBodypublicMap exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {

printStackTrace(ex);

Map data = new HashMap();

data.put("status", "failure");returndata;

}

@ControllerAdvice注解已经包含@Component注解故能被自动扫描 ,看代码

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Componentpublic @interface ControllerAdvice

@ExceptionHandler(value={Exception.class})注解指定要被处理的异常有哪些,value是一个类数组可以指定多个异常类型,这里处理了所有的异常。

业务代码使用很简单,直接抛出异常就行。

@RequestMapping(value={"/", "/login"})publicString index() {

User user= null;if (user == null) throw newObjectNotFoundException();return "login";

}

假如你请求一个不存在的地址:/abc123,这时异常统一处理却没有工作。(前提是没有配置静态资源默认处理servelt,即java配置重写configureDefaultServletHandling方法设置configurer.enable() 或者 xml配置添加,若配置了静态资源处理servlet,在url没有匹配时会被当做静态资源处理,从而导致异常统一处理没有工作。)

为什么呢?看DispatcherServlet源码的doDispatch方法,红色加粗部分

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throwsException {

HttpServletRequest processedRequest=request;

HandlerExecutionChain mappedHandler= null;boolean multipartRequestParsed = false;

WebAsyncManager asyncManager=WebAsyncUtils.getAsyncManager(request);try{

ModelAndView mv= null;

Exception dispatchException= null;try{

processedRequest=checkMultipart(request);

multipartRequestParsed= (processedRequest !=request);//Determine handler for the current request.

mappedHandler =getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {

noHandlerFound(processedRequest, response);return;

}//Determine handler adapter for the current request.

HandlerAdapter ha =getHandlerAdapter(mappedHandler.getHandler());//Process last-modified header, if supported by the handler.

String method =request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified =ha.getLastModified(request, mappedHandler.getHandler());if(logger.isDebugEnabled()) {

logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " +lastModified);

}if (new ServletWebRequest(request, response).checkNotModified(lastModified) &&isGet) {return;

}

}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;

}//Actually invoke the handler.

mv =ha.handle(processedRequest, response, mappedHandler.getHandler());if(asyncManager.isConcurrentHandlingStarted()) {return;

}

applyDefaultViewName(processedRequest, mv);

mappedHandler.applyPostHandle(processedRequest, response, mv);

}catch(Exception ex) {

dispatchException=ex;

}catch(Throwable err) {//As of 4.3, we're processing Errors thrown from handler methods as well,//making them available for @ExceptionHandler methods and other scenarios.

dispatchException = new NestedServletException("Handler dispatch failed", err);

}

processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

}catch(Exception ex) {

triggerAfterCompletion(processedRequest, response, mappedHandler, ex);

}catch(Throwable err) {

triggerAfterCompletion(processedRequest, response, mappedHandler,new NestedServletException("Handler processing failed", err));

}finally{if(asyncManager.isConcurrentHandlingStarted()) {//Instead of postHandle and afterCompletion

if (mappedHandler != null) {

mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);

}

}else{//Clean up any resources used by a multipart request.

if(multipartRequestParsed) {

cleanupMultipart(processedRequest);

}

}

}

}protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throwsException {if(pageNotFoundLogger.isWarnEnabled()) {

pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +

"] in DispatcherServlet with name '" + getServletName() + "'");

}if (this.throwExceptionIfNoHandlerFound) {throw newNoHandlerFoundException(request.getMethod(), getRequestUri(request),newServletServerHttpRequest(request).getHeaders());

}else{

response.sendError(HttpServletResponse.SC_NOT_FOUND);

}

}

noHandlerFound方法的throwExceptionIfNoHandlerFound属性判断为false,所以没有抛出异常,而是直接返回客户端了。

注意!注意!注意。处理Spring MVC抛出的404,500等异常,以及无法匹配到请求地址的异常。

第一步、throwExceptionIfNoHandlerFound赋值为true

我们知道原因是if (this.throwExceptionIfNoHandlerFound)没有进,throwExceptionIfNoHandlerFound属性是false导致的,所以我们把他赋值为true就行。

方式一、重写AbstractDispatcherServletInitializer类的protected void customizeRegistration(ServletRegistration.Dynamic registration)方法,给throwExceptionIfNoHandlerFound赋值true(推荐)

packagecn.ycx.web.config;importjavax.servlet.ServletRegistration;importorg.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class ServletWebApplicationInitializer extendsAbstractAnnotationConfigDispatcherServletInitializer {//将一个或多个路径映射到DispatcherServlet上

@OverrideprotectedString[] getServletMappings() {return new String[] {"/"};

}//返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean

@Overrideprotected Class>[] getRootConfigClasses() {return new Class>[] {RootConfig.class};

}

@Overrideprotected Class>[] getServletConfigClasses() {return new Class>[] {ServletConfig.class};

}

@Overrideprotected void customizeRegistration(ServletRegistration.Dynamic registration) {

boolean done = registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");

if(!done) throw newRuntimeException();

}

}

方式二、重写AbstractDispatcherServletInitializer类的protected void registerDispatcherServlet(ServletContext servletContext)方法,给throwExceptionIfNoHandlerFound赋值true

protected voidregisterDispatcherServlet(ServletContext servletContext) {

String servletName=getServletName();

Assert.hasLength(servletName,"getServletName() must not return empty or null");

WebApplicationContext servletAppContext=createServletApplicationContext();

Assert.notNull(servletAppContext,"createServletApplicationContext() did not return an application " +

"context for servlet [" + servletName + "]");

FrameworkServlet dispatcherServlet=createDispatcherServlet(servletAppContext);

dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);

ServletRegistration.Dynamic registration=servletContext.addServlet(servletName, dispatcherServlet);

Assert.notNull(registration,"Failed to register servlet with name '" + servletName + "'." +

"Check if there is another servlet registered under the same name.");

registration.setLoadOnStartup(1);

registration.addMapping(getServletMappings());

registration.setAsyncSupported(isAsyncSupported());

Filter[] filters=getServletFilters();if (!ObjectUtils.isEmpty(filters)) {for(Filter filter : filters) {

registerServletFilter(servletContext, filter);

}

}

customizeRegistration(registration);

}

方式三、web.xml追加init-param,给throwExceptionIfNoHandlerFound赋值true

dispatcher

org.springframework.web.servlet.DispatcherServlet

throwExceptionIfNoHandlerFound

true

contextConfigLocation

classpath:ycxcode-servlet.xml

1

第二步、去掉静态资源处理Servlet,若不去掉会被静态资源处理匹配没有的请求。

code-base配置方式,若重载了下面的方法则去掉,(该方法在WebMvcConfigurerAdapter的扩展类中)

/*** 配置静态文件处理*/@Overridepublic voidconfigureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {

configurer.enable();

}

xml配置方式,若追加了下面的配置则去掉,(在springmvc配置文件中)

以上我们对异常统一处理就完成了。去掉静态资源默认处理后,静态资源处理如下:

去掉静态资源处理servlet后,静态资源的请求也会被当成错误的 请求地址异常 拦截,那怎么办呢?自定义Filter在DispatchServlet之前拦截所有的资源然后直接返回给浏览器。

假设js,css,image都在static目录下放着,定义一个StaticFilter静态资源过滤器,直接返回静态资源。

packagecn.ycx.web.filter;importjava.io.FileInputStream;importjava.io.IOException;importjavax.servlet.Filter;importjavax.servlet.FilterChain;importjavax.servlet.ServletException;importjavax.servlet.ServletOutputStream;importjavax.servlet.ServletRequest;importjavax.servlet.ServletResponse;importjavax.servlet.annotation.WebFilter;importjavax.servlet.http.HttpServletRequest;/*** 资源访问

*@author杨崇兴 2018-07-05*/public class StaticFilter implementsFilter {

@Overridepublic voiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)throwsIOException, ServletException {

HttpServletRequest httpServletRequest=(HttpServletRequest) request;

String path=httpServletRequest.getServletPath();String realPath=httpServletRequest.getServletContext().getRealPath(path);

System.out.println(realPath);

ServletOutputStream out=response.getOutputStream();

FileInputStream in= newFileInputStream(realPath);byte[] buf = new byte[2048];int len = -1;while((len = in.read(buf)) != -1) {

out.write(buf,0, len);

}

in.close();

out.flush();

out.close();}

}

把定义好的StaticFilter添加到Spring MVC上下文中,如下红色代码部分。如何添加自定义Servelt、Filter、Listener请参考另一片博文:https://www.cnblogs.com/yangchongxing/p/9968483.html

packagecn.ycx.initializer;importjavax.servlet.FilterRegistration;importjavax.servlet.ServletContext;importjavax.servlet.ServletException;importjavax.servlet.ServletRegistration;importorg.springframework.web.WebApplicationInitializer;importcn.ycx.filter.MyFilter;importcn.ycx.filter.StaticFilter;importcn.ycx.listener.MyServletRequestAttributeListener;importcn.ycx.listener.MyServletRequestListener;importcn.ycx.servlet.MyServlet;public class MyInitializer implementsWebApplicationInitializer {

@Overridepublic void onStartup(ServletContext servletContext) throwsServletException {

System.out.println(">>>>>>>>>>>> 自定义 onStartup ...");//自定义Servlet

ServletRegistration.Dynamic myServlet = servletContext.addServlet("myservlet", MyServlet.class);

myServlet.addMapping("/myservlet");//自定义Filter

FilterRegistration.Dynamic staticFilter = servletContext.addFilter("staticfilter", StaticFilter.class);

staticFilter.addMappingForUrlPatterns(null, false, "/static/*");

FilterRegistration.Dynamic myFilter= servletContext.addFilter("myfilter", MyFilter.class);

myFilter.addMappingForUrlPatterns(null, false, "/*");//自定义Listener

servletContext.addListener(MyServletRequestListener.class);

servletContext.addListener(MyServletRequestAttributeListener.class.getName());

}

}

2、在控制器中使用@ExceptionHandler统一处理异常

这种方式可以在每一个控制器中都定义处理方法,也可以写一个BaseController基类,其他控制器继承这个类;

未知请求地址我们也要处理一下,将其跳转到错误页面。这个要利用Spring MVC请求地址的精准匹配,@RequestMapping("*")会匹配剩下没有匹配成功的请求地址,相当于所有请求地址都是有的,只是我们把其他的处理到错误界面了。看代码

packagecn.ycx.web.controller;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importorg.apache.log4j.Logger;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.servlet.ModelAndView;/*** 控制器基类

*@author杨崇兴 2018-07-05*/

public classBaseController {public Logger logger =Logger.getLogger(getClass());/*** 所有异常处理

*@parame

*@return

*/@ExceptionHandler(value={Exception.class})publicModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {

ModelAndView mav= newModelAndView();

mav.setViewName("error");returnmav;

}/*** 未知请求处理

*@return

*/@RequestMapping("*")publicString notFount() {return "error";

}

}

3、使用SimpleMappingExceptionResolver统一处理异常

/**

* 异常处理

* @return*/@BeanpublicSimpleMappingExceptionResolver exceptionResolver() {

Properties exceptionMappings= newProperties();

exceptionMappings.put("cn.ycx.web.exception.ObjectNotFoundException", "error");

Properties statusCodes= newProperties();

statusCodes.put("error", "404");

SimpleMappingExceptionResolver exceptionResolver= newSimpleMappingExceptionResolver();

exceptionResolver.setDefaultErrorView("error");

exceptionResolver.setExceptionMappings(exceptionMappings);

exceptionResolver.setStatusCodes(statusCodes);returnexceptionResolver;

}

以上的方式是无法处理Spring MVC抛出的404,500等需要配合下面的处理,看代码

/*** 未知请求处理

*@return

*/@RequestMapping("*")publicString notFount() {return "error";

}

4、将异常映射为HTTP状态码

这个比较简单,就是抛出对应异常时,会转换为对应的状态码。看代码

packagecn.ycx.web.exception;importorg.springframework.http.HttpStatus;importorg.springframework.web.bind.annotation.ResponseStatus;/*** 对象没有找到异常

*@author杨崇兴 2018-07-05*/@ResponseStatus(value= HttpStatus.NOT_FOUND, reason="对象没有找到")public class ObjectNotFoundException extendsRuntimeException {private static final long serialVersionUID = 2874051947922252271L;

}

业务代码直接抛出异常就行

throw new ObjectNotFoundException();

续写中...

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值