一、Java中的异常体系:(图片来源于网络)
1、在这些异常中,RuntimeException
及其子孙类异常,在Java语法中并不必须处理,主要原因有:这些异常出现的频率可能非常高,如果一定要处理,例如:try...catch
,则几乎所有的代码都需要放在try代码块
中,并且,这些异常是可以杜绝的异常,通过严谨的编程,可以保证这些异常绝不会出现!
2、处理异常有两种方式:使用try...catch
处理异常,或者使用throw
抛出异常对象,并且在方法的声明中使用throws
语法声明抛出!
3、通常,异常都是必须处理的,如果没有处理异常,会导致异常不断向上抛,最终java EE中的异常会由Tomcat
来处理,会把跟踪日志显示在页面中,这些页面会显得非常的不友好,而且还有被分析出项目的内容的风险,对于后续解决问题也没有任何帮助,所以从原则上来说,必须处理异常!
4、 非RuntimeException
是从语法上限制要求处理的,所以每次调用了抛出异常的方法,就必须try…catch或继续声明抛出!(这个就不多说了)
5、RuntimeException
及其子孙类异常并不从语法上强制要求处理,但是,一旦出现,会对项目的安全、用户体验等各方面都会产生负面影响。
二、下面我们来说一下SpringMVC中处理异常的方式:
1、处理异常-SimpleMappingExceptionResolver:
SpringMVC提供了统一处理异常的方式:使用SimpleMappingExceptionResolver
类,该类通过private Properties exceptionMappings;
属性来配置异常种类于转发到的页面的对应关系,即每一种异常都可以有一个与之对应的页面,一旦项目中出现配置过的异常,就会自动转发到对应的页面来提示错误信息!
注:这种方式处理异常非常简单,但是,也有一个较大的问题:无法提示更详细的信息!例如:出现ArrayIndexOutOfBoundsException时,其实,异常还封装了详细的错误信息,即:越界值是多少等。
<!-- 配置统一处理异常 -->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="java.lang.RuntimeException">runtime</prop>
<prop key="java.lang.NullPointerException">null</prop>
<prop key="java.lang.ArrayIndexOutOfBoundsException">index</prop>
</props>
</property>
</bean>
通过上面的配置,一旦出现了NullPointerException则会跳转到null.jsp页面。
2、处理异常-@ExceptionHandler注解:
可以在控制器类中定义一个处理异常的方法,在方法之前添加@ExceptionHandler
,然后,凡是约定范围内的异常,都可以由该方法来决定如何处理:
@ExceptionHandler
public String handleException(Exception e) {
System.out.println(
"ExceptionController.handleException()");
if (e instanceof NullPointerException) {
return "null";
} else if (e instanceof ArrayIndexOutOfBoundsException) {
return "index";
}
return "runtime";
}
在处理异常时,如果需要转发数据,可以将返回值修改为ModelAndView
,或者,在处理异常的方法参数列表中添加HttpServletRequest
,但是,却不可以使用ModelMap
来封装转发的数据:
@ExceptionHandler
public String handleException(Exception e,
HttpServletRequest request) {
System.out.println(
"ExceptionController.handleException()");
if (e instanceof NullPointerException) {
return "null";
} else if (e instanceof ArrayIndexOutOfBoundsException) {
request.setAttribute("msg", e.getMessage());
return "index";
}
return "runtime";
}
例:下面我们举个小例子就明白了:
比如我们想要写一个UserController,在这个Controller中处理用户提交的用户名被占用的时候,抛出该异常。
step1、首先创建一个控制器的基类BaseController。
public abstract class BaseController {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public ResponseResult<Void> handleException(Exception e){
//判断类型,并进行处理
if(e instanceof UsernameConflictException){
//用户名冲突异常
return new ResponseResult<Void>(401,e);
}
}
}
step2、然后我们创建我们处理业务逻辑的UserController,让UserController继承我们的BaseController。
@Controller
@RequestMapping("/user")
public class UserController extends BaseController{
@Autowired
private IUserService userService;
//调用业务层进行注册(reg为接口)
userService.reg(user);
//执行返回
return new ResponseResult<Void>();
}
step3、reg接口代码:下面我只举一个异常的例子,实际开发中肯定不止这一种异常。
public interface IUserService {
User reg(User user)throws UsernameConflictException;
}
看下具体的业务层实现类:抛出了异常,并定义了异常信息:throw new UsernameConflictException("");
@Service("userService")
public class UserServiceImpl implements IUserService{
@Autowired
private UserMapper userMapper;
public User reg(User user) throws UsernameConflictException,InsertDataException{
// 根据尝试注册的用户名查询用户数据
User data=findUserByUsername(user.getUsername());
// 判断是否查询到数据
if(data!=null){
// 是:查询到数据,即用户名被占用,则抛出UsernameConflictException异常
throw new UsernameConflictException("用户名("+user.getUsername()+")已被占用");
}else{
// 否:没有查询到数据,即用户名没有被占用,则执行插入用户数据,获取返回值
User result=insert(user);
// 执行返回
return result;
}
}
step4、与页面中的ajax请求相对应:如果返回的状态码是401,则会提示错误信息。
<script type="text/javascript">
$("#btn-reg").click(function(){
var url="../user/handle_reg.do";
var data=$("#reg-form").serialize();
$.ajax({
"url":url,
"data":data,
"type":"POST",
"dataType":"json",
"success":function(json){
if(json.state==401){
//用户名已被占用
alert("注册失败"+json.message);
}
}
});
});
</script>
三、思考:
1、是否可以在BaseController类中添加3个处理异常的方法,分别单独处理NullPointerException
和ArrayIndexOutOfBoundsException
和RuntimeException
异常?
答案:可以!允许使用多个不同的方法来处理异常!
2、假设处理异常的方法在A控制器中,而B控制器中处理请求时出现异常,会被处理吗?
答案:不可以!通常,可以创建一个BaseController,作为当前项目的控制器类的基类,然后,把处理异常的代码编写在这个类中即可!
3、关于@ExceptionHandler
,还可以用于限制其对应的方法处理的异常的种类!
例如:@ExceptionHandler(IndexOutOfBoundsException.class);
以上代码表示接下来的方法只处理IndexOutOfBoundsException
及其子孙类异常,而其它的异常并不处理!
四、总结:
1、处理异常的本质并不可以撤销
异常,无法让已经出现的问题还原到没有问题,所以,处理异常的本质应该是尽量的补救已出现的问题,并且,尝试通过提示信息等方式告之使用者,尽量执行正确的操作,避免后续再次出现同样的问题!
2、对于开发者而言,应该处理每一个可能出现的异常,因为,如果没有处理,就会继续抛出,最终被Tomcat捕获,而Tomcat处理的方式是把异常的跟踪信息显示在页面上,是极为不合适的!
3、在SpringMVC中,处理异常有2中方式,第一种是通过SimpleMappingExceptionResolver
设置异常与转发目标的对应关系,第2种是使用@ExceptionHandler
为处理异常的方法进行注解,推荐使用第2种方式。
4、通常,会把处理异常的方法写在项目的控制器类的基类中,即写在BaseController中,在使用@ExceptionHandler
时,可以明确的指定所处理的异常类型,例如:@ExceptionHandler(NullPointerException)
,且,在控制器类中,可以编写多个处理异常的方法。
5、关于处理异常的方法,应该是public
方法,返回值类型与处理请求的方式相同,可以是String
或 ModelAndView
或其他允许类型,方法的名称可以自定义,参数列表中必须包含Exception
类型的参数,还允许HttpServletRequest
、HttpServletResponse
,不允许使用ModelMap
。
上面就是Spring MVC处理异常的方式,如果文章对你有帮助,请点个赞支持一下,谢谢。