【Java Web开发学习】Spring MVC异常统一处理
【Java Web开发学习】Spring MVC异常统一处理
文采有限,若有错误,欢迎留言指正。
转载:https://www.cnblogs.com/yangchongxing/p/9271900.html
目录
1、使用@ControllerAdvice和@ExceptionHandler注解统一处理异常
2、在控制器中使用@ExceptionHandler统一处理异常
3、使用SimpleMappingExceptionResolver统一处理异常
正文
异常处理是每一个系统必须面对的,对于Web系统异常必须统一处理,否者业务代码会被无穷无尽的异常处理包围。对于Spring MVC来说有以下几种异常处理方式。
1、使用@ControllerAdvice和@ExceptionHandler注解统一处理异常(推荐)
我们自定义一个全局异常处理类GlobalExceptionHandler打印异常信息到日志并且跳转到异常页面,看代码
package cn.ycx.web.exception; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/**
* 全局异常处理
* @author 杨崇兴 2018-07-05
*/
@ControllerAdvice //已经包含@Component注解,能被自动扫描
public class GlobalExceptionHandler {
public Logger logger = Logger.getLogger(getClass());
/**
* 所有异常处理,返回名为error的视图
* @param e
* @return
*/
@ExceptionHandler(value={Exception.class})
public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
printStackTrace(ex);
ModelAndView mav = new ModelAndView();
mav.setViewName("error");
return mav;
}
/**
* 打印异常堆栈信息
* @param ex
*/
private void printStackTrace(Exception ex) {
StringBuilder errors = new StringBuilder();
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区域。
/**
* 所有异常处理
* @param e
* @return
*/
@ExceptionHandler(value={Exception.class})
@ResponseBody
public Map<String, String> exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
printStackTrace(ex);
Map<String, String> data = new HashMap<String, String>();
data.put("status", "failure");
return data;
}
@ControllerAdvice注解已经包含@Component注解故能被自动扫描 ,看代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice
@ExceptionHandler(value={Exception.class})注解指定要被处理的异常有哪些,value是一个类数组可以指定多个异常类型,这里处理了所有的异常。
业务代码使用很简单,直接抛出异常就行。
@RequestMapping(value={"/", "/login"})
public String index() {
User user = null;
if (user == null) throw new ObjectNotFoundException();
return "login";
}
假如你请求一个不存在的地址:/abc123,这时异常统一处理却没有工作。(前提是没有配置静态资源默认处理servelt,即java配置重写configureDefaultServletHandling方法设置configurer.enable() 或者 xml配置添加<mvc:default-servlet-handler/>,若配置了静态资源处理servlet,在url没有匹配时会被当做静态资源处理,从而导致异常统一处理没有工作。)
为什么呢?看DispatcherServlet源码的doDispatch方法,红色加粗部分
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
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) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(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(推荐)
package cn.ycx.web.config;
import javax.servlet.ServletRegistration;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
// 将一个或多个路径映射到DispatcherServlet上
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
// 返回的带有@Configuration注解的类将会用来配置ContextLoaderListener创建的应用上下文中的bean
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] {RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {ServletConfig.class};
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
boolean done = registration.setInitParameter("throwExceptionIfNoHandlerFound", "true");
if(!done) throw new RuntimeException();
}
}
方式二、重写AbstractDispatcherServletInitializer类的protected void registerDispatcherServlet(ServletContext servletContext)方法,给throwExceptionIfNoHandlerFound赋值true
protected void registerDispatcherServlet(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
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>throwExceptionIfNoHandlerFound</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:ycxcode-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
第二步、去掉静态资源处理Servlet,若不去掉会被静态资源处理匹配没有的请求。
code-base配置方式,若重载了下面的方法则去掉,(该方法在WebMvcConfigurerAdapter的扩展类中)
/**
* 配置静态文件处理
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
xml配置方式,若追加了下面的配置则去掉,(在springmvc配置文件中)
<!-- 静态资源默认servlet配置 -->
<mvc:default-servlet-handler />
以上我们对异常统一处理就完成了。去掉静态资源默认处理后,静态资源处理如下:
去掉静态资源处理servlet后,静态资源的请求也会被当成错误的 请求地址异常 拦截,那怎么办呢?自定义Filter在DispatchServlet之前拦截所有的资源然后直接返回给浏览器。
假设js,css,image都在static目录下放着,定义一个StaticFilter静态资源过滤器,直接返回静态资源。
package cn.ycx.web.filter;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
/**
* 资源访问
* @author 杨崇兴 2018-07-05
*/public class StaticFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, 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 = new FileInputStream(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
package cn.ycx.initializer; import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration; import org.springframework.web.WebApplicationInitializer; import cn.ycx.filter.MyFilter;
import cn.ycx.filter.StaticFilter;
import cn.ycx.listener.MyServletRequestAttributeListener;
import cn.ycx.listener.MyServletRequestListener;
import cn.ycx.servlet.MyServlet; public class MyInitializer implements WebApplicationInitializer { @Override
public void onStartup(ServletContext servletContext) throws ServletException {
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("*")会匹配剩下没有匹配成功的请求地址,相当于所有请求地址都是有的,只是我们把其他的处理到错误界面了。看代码
package cn.ycx.web.controller; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView; /**
* 控制器基类
* @author 杨崇兴 2018-07-05
*/
public class BaseController {
public Logger logger = Logger.getLogger(getClass());
/**
* 所有异常处理
* @param e
* @return
*/
@ExceptionHandler(value={Exception.class})
public ModelAndView exceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception ex) {
ModelAndView mav = new ModelAndView();
mav.setViewName("error");
return mav;
}
/**
* 未知请求处理
* @return
*/
@RequestMapping("*")
public String notFount() {
return "error";
}
}
3、使用SimpleMappingExceptionResolver统一处理异常
/**
* 异常处理
* @return
*/
@Bean
public SimpleMappingExceptionResolver exceptionResolver() {
Properties exceptionMappings = new Properties();
exceptionMappings.put("cn.ycx.web.exception.ObjectNotFoundException", "error");
Properties statusCodes = new Properties();
statusCodes.put("error", "");
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
exceptionResolver.setDefaultErrorView("error");
exceptionResolver.setExceptionMappings(exceptionMappings);
exceptionResolver.setStatusCodes(statusCodes);
return exceptionResolver;
}
以上的方式是无法处理Spring MVC抛出的404,500等需要配合下面的处理,看代码
/**
* 未知请求处理
* @return
*/
@RequestMapping("*")
public String notFount() {
return "error";
}
4、将异常映射为HTTP状态码
这个比较简单,就是抛出对应异常时,会转换为对应的状态码。看代码
package cn.ycx.web.exception; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus; /**
* 对象没有找到异常
* @author 杨崇兴 2018-07-05
*/
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="对象没有找到")
public class ObjectNotFoundException extends RuntimeException {
private static final long serialVersionUID = 2874051947922252271L;
}
业务代码直接抛出异常就行
throw new ObjectNotFoundException();
续写中...
【Java Web开发学习】Spring MVC异常统一处理的更多相关文章
- 【Java Web开发学习】Spring MVC文件上传
[Java Web开发学习]Spring MVC文件上传 转载:https://www.cnblogs.com/yangchongxing/p/9290489.html 文件上传有两种实现方式,都比较 ...
- 【Java Web开发学习】Spring MVC 使用HTTP信息转换器
[Java Web开发学习]Spring MVC 使用HTTP信息转换器 转载:https://www.cnblogs.com/yangchongxing/p/10186429.html @Respo ...
- 【Java Web开发学习】Spring MVC添加自定义Servlet、Filter、Listener
[Java Web开发学习]Spring MVC添加自定义Servlet.Filter.Listener 转载:https://www.cnblogs.com/yangchongxing/p/9968 ...
- 【Java Web开发学习】Spring MVC 拦截器HandlerInterceptor
[Java Web开发学习]Spring MVC 拦截器HandlerInterceptor 转载:https://www.cnblogs.com/yangchongxing/p/9324119.ht ...
- 【Java Web开发学习】Spring JPA
[Java Web开发学习]Spring JPA 转载:https://www.cnblogs.com/yangchongxing/p/10082864.html 1.使用容器管理类型的JPA JND ...
- 【Java Web开发学习】Spring加载外部properties配置文件
[Java Web开发学习]Spring加载外部properties配置文件 转载:https://www.cnblogs.com/yangchongxing/p/9136505.html 1.声明属 ...
- 【Java Web开发学习】Spring环境profile
[Java Web开发学习]Spring 环境profile 转载:http://www.cnblogs.com/yangchongxing/p/8890702.html 开发.测试.生产环境往往是不 ...
- 【Java Web开发学习】Spring4整合thymeleaf视图解析
[Java Web开发学习]Spring4整合thymeleaf视图解析 目录 1.简单介绍2.简单例子 转载:https://www.cnblogs.com/yangchongxing/p/9111 ...
- 【Java Web开发学习】Spring4条件化的bean
[Java Web开发学习]Spring4条件化的bean 转载:https://www.cnblogs.com/yangchongxing/p/9071960.html Spring4引入了@Con ...
随机推荐
- python 抓取youtube教程
前言: 相信大家很多人都看过youtube网站上的视频,网站上有很多的优质视频,清晰度也非常的高,看到喜欢的想要下载到本地,虽然也有很多方法,但是肯定没有python 来的快, 废话不多说,上代码: ...
- Project Euler 62: Cubic permutations
立方数\(41063625 (345^3)\)的各位数重新排列形成另外两个立方数\(6623104 (384^3)\)和\(66430125 (405^3)\).事实上,\(41063625\)是满足 ...
- mysql的属性zerofill
一.字段中zerofill属性的类似定义方式 SQL语句:字段名 int(M) zerofill 二.zerofill属性的作用 1.插入数据时,当该字段的值的长度小于定义的长度时,会在该值的前面补上 ...
- windows下搭建dubbo 环境(dubbo-admin和服务提供者消费者)
---恢复内容开始--- 一. dubbo-admin管理控制台 从 https://github.com/apache/dubbo-admin clone项目到本地. 修改dubbo-admin- ...
- ecryptfs
ecryptfs是一种加密文件系统.该文件系统的内容在传输和储存时以密文形式存在.只有在mount时用密钥解密才能得到明文.利用这个特性,我们可以用他来对软件镜像中的部分敏感文件系统进行加密,然后打包 ...
- HDFS之DataNode
DataNode工作机制 1)一个数据块在datanode上以文件形式存储在磁盘上,包括两个文件,一个是数据本身,一个是元数据包括数据块的长度,块数据的校验和,以及时间戳. 2)DataNode启动后 ...
- Deep attention tracking via Reciprocative Learning
文章:Deep attention tracking via Reciprocative Learning 出自NIPS2018 文章链接:https://arxiv.org/pdf/1810.038 ...
- 推荐几个不错的console调试技巧
在我们的日常前端开发中,使用最频繁的莫过于使用console.log在浏览器的控制台中打印出我们需要调试的信息,但是大部分人可能跟之前的我一样,没有意识到其实console除了log方法以外,还有很多 ...
- Java架构师必知:什么是单点登录,主要会应用于哪些场景?
单点登录在大型网站里使用得非常频繁,例如,阿里旗下有淘宝.天猫.支付宝,阿里巴巴,阿里妈妈,阿里妹妹等网站,还有背后的成百上千的子系统,用户一次操作或交易可能涉及到几十个子系统的协作,如果每个子系统都 ...
- Wordpress未授权查看私密内容漏洞 分析(CVE-2019-17671)
目录 0x00 前言 0x01 分析 0x02 思考 0x03 总结 0x04 参考 0x00 前言 没有 0x01 分析 这个漏洞被描述为"匿名用户可访问私密page",由此推断 ...