您的位置:首页 > 编程语言 > Java开发

异常解析家族应用点源码讲解:SpringMVC全局异常捕获及SpringMVC文件上传报错的优雅解决方式以及@Exception拦截异常注意点

2017-05-12 14:54 911 查看

全局异常处理器基本使用

思路:

系统遇到异常,在程序中手动抛出,dao抛给service、service给controller、controller抛给前端控制器,前端控制器调用全局异常处理器。


全局异常处理器处理思路:

解析出异常类型
如果该 异常类型是系统 自定义的异常,直接取出异常信息,在错误页面展示
如果该 异常类型不是系统 自定义的异常,构造一个自定义的异常类型(信息为“未知错误”)


springmvc提供一个HandlerExceptionResolver接口

首先自定义一个异常类型:

public class CustomException extends Exception{
public CustomException(String message) {
super(message);
}

public CustomException(String message, Throwable cause) {
super(message, cause);
}

public CustomException(Throwable cause) {
super(cause);
}

public CustomException() {
super();
}
}


然后一个类去实现HandlerExceptionResolver接口:

public class CustomExceptionResolver implements HandlerExceptionResolver{

@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler, Exception e) {
CustomException customException = null;
//如果抛出的是系统自定义异常及其子类则直接转换
if(e instanceof  CustomException){
System.out.println("########################");
System.out.println("正在转换异常信息!!!!!!!!!!");
System.out.println("########################");
customException = (CustomException) e;
}else{
//如果抛出的不是系统自定义异常及其子类则重新构造一个未知错误异常。
e.printStackTrace();
customException = new CustomException("发生未知错误,请与系统管理员联系!!!");
}
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("errorMessage",customException.getMessage());
modelAndView.setViewName("/WEB-INF/content/error.jsp");

return modelAndView;
}
}


在springmvc.xml配置全局异常处理器

<!-- 配置异常处理器 -->
<!-- 只要实现HandlerExceptionResolver接口就是全局异常处理器 -->
<bean id="customExceptionResolver" class="cn.domarvel.exception.CustomExceptionResolver" />


错误页面

error.jsp

<%--
Created by IntelliJ IDEA.
User: FireLang
Date: 2017/5/1
Time: 20:51
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>错误页面</title>
</head>
<body>
错误页面:
${requestScope.errorMessage}
</body>
</html>


异常测试

在controller、service、dao中任意一处需要手动抛出异常。

如果是程序中手动抛出的异常,在错误页面中显示自定义的异常信息,如果不是手动抛出异常说明是一个运行时异常,在错误页面只显示“未知错误”。

在商品修改的controller方法中抛出异常 .



在service接口中抛出异常:



如果与业务功能相关的异常,建议在service中抛出异常。

与业务功能没有关系的异常,建议在controller中抛出。

上边的功能,建议在service中抛出异常。

我们的全局异常处理在这里执行:







该结果是视图。。。

文件上传过大而报错

(一):

在SpringMVC的文件上传中。通过CommonsMultipartResolver实现文件上传时,如果上传的文件超过我们规定的最大值后,出现的MaxUploadSizeExceededException异常通常是我们捕获不到的,我们不能够通过拦截器或者全局异常处理器捕获该异常。

为什么说捕获不到??这种异常一般会报两次,如果没有配置拦截器拦截异常,就会报两次,第一次在通过Adapter执行Handler获得mv那里,第二次是在最终finally那里。而第一次异常虽然能够捕获到,但是第二次是怎么也不能够捕获到的,这是必然的,除非你不在配置文件里面配置上传文件的大小限制。请向下看!!!

因为:





虽然说我们通过全局异常处理器拦截了拦截器抛出的文件过大的异常,也经过processDispatchResult()方法经过了页面渲染,但是serivce方法没有执行结束,也就不可能会马上显示在浏览器页面。

分析

其实出现这种异常也是一种很好的处理方式。

为什么这么说??通常我们会在客户端通过js验证文件大小是否满足,所以能够干掉99%的正常用户(某些浏览器可能禁用了js),对那些恶意用户,如果出现这种连接断开的信息,也算是警示他们吧!!!

所以下次如果上传文件大小超过规定限制,出现浏览器崩溃现象,不要气馁,通过js校验不久行了。

(二)

网上有很多人为了能够不报MaxUploadSizeExceededException异常,把SpringMVC文件上传大小限制给取消掉了。其实这是一种不可取的方式,下面我就来说说原因!!!!



同时配置了拦截器来判断文件是否过大,如果过大就抛出异常:

@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object handler) throws Exception {

if(httpServletRequest!=null && ServletFileUpload.isMultipartContent(httpServletRequest)) {
ServletRequestContext ctx = new ServletRequestContext(httpServletRequest);
long requestSize = ctx.contentLength();
if(requestSize>maxSize){
throw  new CustomException("文件不能大于"+(maxSize/1024.0/1024)+"M确认!!!!!!!!!!!!!!!!!!");
}
}
return true;
}


这样的做法是不对的,有弊端的,导致的结果就是服务器会一直读取你上传的文件,就算我们有拦截器拦截,根据了文件过大而抛出异常,但是事实就是下面的服务器端代码会一直接收浏览器发来的文件(当然如果我们配置了文件大小限制,超过限制大小时,它就会断开与浏览器的连接,终止文件的上传,但是我们这里是把文件大小限制给删掉了,它就不会终止。),直到服务器接收浏览器文件完毕为止,因为存储文件的逻辑是在Handler中,而这里读取文件的代码是在前端控制器的finally中,所以这个时候服务器端读到内存中的文件是不会持久化保存的。我们之前也说过,
"通过全局异常处理器拦截了拦截器抛出的文件过大的异常,也经过processDispatchResult()方法经过了页面渲染,但是serivce方法没有执行结束,也就不可能会马上显示在页面。"
,因为在全局异常捕获后没有再发生过其它异常,所以就不会中断客户端的连接,最终还是会显示全局异常捕获逻辑处理指定的错误页面。



结论:

去掉服务器端SpringMVC文件大小限制方式不合理的根本原因就是服务器要读取完浏览器上传的文件, 导致服务器加载过多的浏览器上传的文件到内存中,而耗干服务器的资源,这是不可取的!!!!!

解决办法就是,通常采用js校验文件大小!!!防止普通用户报错就行了!!!!!恶意用户报错就报错吧,反正也没什么损失。

<%--
Created by IntelliJ IDEA.
User: FireLang
Date: 2017/5/10
Time: 14:24
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>上传文件</title>
</head>
<body>
<form action="item/uploadFile" method="post" enctype="multipart/form-data">
<input type="file" name="items_pic" onchange="checkFileSize(this)">
<input id="upload" type="submit" value="确认上传">
</form>
<script>
var submitButton = document.getElementById("upload");
function checkFileSize(targetFile) {
if (targetFile.files && targetFile.files[0]) {
var fileSize = targetFile.files[0].size/1024.0/1024;
if(fileSize>20){
alert("上传的文件不能大于20MB!!!");
submitButton.disabled=true;
}else{
submitButton.disabled=false;
}
}
}
</script>
</body>
</html>


@ExceptionHandler

注意,当你用@ExceptionHandler来捕获MaxUploadSizeExceededException异常时,必须把设置resolveLazily等于true才能够捕获异常!!!

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件最大上传大小为10MB -->
<property name="resolveLazily" value="true"/>
<property name="maxUploadSize" value="20971520"/>
</bean>


在Handler里面写上该方法:

@ExceptionHandler(MaxUploadSizeExceededException.class)//里面是要捕获的异常。
public void handleException(Exception ex,HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
System.out.println("*********************************");
System.out.println("*********************************");
System.out.println("*********************************");
System.out.println("*********************************");
System.out.println("*********************************");
request.setAttribute("errorMessage","上传文件太大。。。。。。。");
request.getRequestDispatcher("/WEB-INF/content/error.jsp").forward(request,response);
System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
}


但是,你在上面这个方法中跳转的页面是永远无法达到的,因为我们限制了上传文件的大小,所以在finally里面是会报错的,所以会导致
"连接断开"
,导致页面显示崩溃!!!

为什么resolveLazily要设置为true才能够捕获异常???

因为在该方法执行之初:





进入该方法看看怎么抛出异常的:



如果传输文件比我们限制的最大空间大就抛出异常:



就算抛出了异常和我们的延迟加载true和false有什么关系呢??

当然后咯:



抛出异常后,进入这里处理异常:





进入该方法找到合适的异常解析器:



初始化的异常解析器有下面几个:



我们是通过@ExceptionHandler处理异常的,所以会用第一个异常解析器解析异常:

在解析异常的过程中,如果我们的mappedHandler为空,那么就不会让该异常解析器解析:



结论:就是因为@ExceptionHandler处理的异常必须满足条件是mappedHandler不能为空,所以我们不能够过早的读取浏览器上传的文件,从而引发文件过大的异常!!!所以我们要配置延迟加载文件为true。

通常SimpleExceptionResolver这个异常解析类一般不用,所以我在这里就不在说了!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: