目录
一、 java 常见异常原理简介:类型与异常处理机制
1. error 和 exception 的区别
2. 几个常见的错误(error)和RuntimeException(unchecked异常)
二、 java中异常抛出的常见方式
1. 系统自动抛异常
2. throw
3. throws
throw与throws的区别
三、 java中异常处理的常见方式
1. 使用try/catch块
关于try{}catch(){}的用法举例说明和对应的六种错误类型
2. 实现HandlerExceptionResolver接口
3. SpringMVC的简单异常处理器
4. @ControllerAdvice+@ExceptionHandler进行异常处理
Java 对异常进行了分类,不同类型的异常分别用不同的 Java 类表示,所有异常的根类为 java.lang.Throwable,Throwable 下面又派生了两个子类: Error 和 Exception,
Error 表示应用程序本身无法克服和恢复的一种严重问题。
Exception 表示程序还能够克服和恢复的问题。
按照异常需要处理的时机分为编译时异常(CheckedException,强制性异常,普通异常)和运行时异常(RuntimeException,非强制性异常,系统异常)。
编译时异常:
编译时异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。编译器强制普通异常必须 try.catch 处理或用 throws 声明继续抛给上层调用方法处理,所以普通异常也称为 checked 异常,只有 java 语言提供了CheckedException, Java 认为CheckedException都是可以被处理的异常,所以 Java 程序必须显式处理CheckedException。如果程序没有处理CheckedException,该程序在编译时就会发生错误无法编译。这体现了 Java 的设计哲学:没有完善错误处理的代码根本没有机会被执行。所以,“所以异常都是在运行时出错”这种说法是不正确的。
java 为系统异常和普通异常提供了不同的解决方案,
CheckedException处理方法有两种:
1、当前方法知道如何处理该异常,则用 try.catch 块来捕获处理该异常。
2、当前方法不知道如何处理,则在定义该方法是用 throws 字句声明抛出该异常,交给它的父类处理,否则编译不会通过。
运行时异常:
运行时异常(如ArithmaticException,IllegalArgumentException)只有当代码在运行时才发行的异常,编译时不需要 try.catch。 Runtime如除数是 0 和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);而系统异常可以处理也可以不处理,所以,编译器不强制用 try.catch 处理或用 throws 声明,所以系统异常也称为 unchecked 异常。
如下图,粉色的为checked 异常。青色的为unchecked异常。
SQLexception、OutOfMemoryError是什么异常?
try{
console.log('a');
console.log(b);
console.log('c');
}catch(e){
console.log(e.name + " :" + e.message );
//打印出错误类型和错误信息
}
console.log('照常运行');
相同点:父类都是 Throwable 类(Throwable是所有异常的根)
不同点:Error 类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误的导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。
Exception 类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
Error | 都是 Throwable 的子类,都用于表示程序出现了不正常的情况 | 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况。 |
Exception | 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。 |
error的六种错误类型:
名称 | 说明(原因) |
EvalError |
eval()的使用与定义不一致 |
RangeError | 数值越界 |
ReferenceError | 非法或者不能识别的引用数值 |
SyntaxError | 发生语法解析错误 |
TypeError | 操作数类型错误 |
URIError | URL处理函数使用不当 |
RuntimeException(unchecked异常):
异常 | 名称 | 原因 |
java.lang.NullPointerException | 空指针异常 | 调用了未经初始化的对象或者是不存在的对象 |
java.lang.ClassNotFoundException | 指定的类找不到 | 类的名称和路径加载错误;通常都是程序试图通过字符串来加载某个类时可能引发异常 |
java.lang.NumberFormatException | 字符串转换为数字异常 | 字符型数据中包含非数字型字符 |
java.lang.IndexOutOfBoundsException | 数组角标越界异常 | 常见于操作数组对象时发生 |
java.lang.IllegalArgumentException | 方法传递参数错误 | |
java.lang.ClassCastException | 数据类型转换异常 | |
java.lang.NoClassDefFoundException | 未找到类定义错误 | |
java.lang.InstantiationException | 实例化异常 | |
java.lang.NoSuchMethodException | 方法不存在异常 | |
SQLException SQL | 常见于操作数据库时的 SQL 语句错误 | |
在java中,抛出异常有三种方式,系统自动抛异常、throw与throws。
当程序语句出现一些逻辑错误、主义错误或类型转换错误时,系统会自动抛出异常:
public static void main(String[] args) {
int a = 5, b =0;
System.out.println(5/b);
//function();
}
系统会自动抛出ArithmeticException异常。如果对其处理(比如使用try、catch块)则输出异常信息,程序继续执行;如果不处理,则向上抛出直到被处理,若一直未处理则程序结束,打印异常信息栈。
throw是语句抛出一个异常,一般是在代码块的内部,当程序出现某种逻辑错误时由程序员根据程序逻辑来决定手动抛出何种异常。
public static void main(String[] args) {
String s = "abc";
if(s.equals("abc")) {
throw new NumberFormatException();
} else {
System.out.println(s);
}
//function();
}
运行时,系统会抛出异常:
Exception in thread "main" java.lang.NumberFormatException at......
throws是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常)
public void function() throws Exception{......}
当某个方法可能会抛出某种异常时用throws 声明可能抛出的异常,向上抛出交给上层调用它的方法去处理该异常。
使用抛出异常的情景主要有:
异常必须由容器来处理,异常时容器做出不同处理的依据和触发;
例如:有事务处理的方法中,事务相关的逻辑必须抛出异常,而不能捕获异常,否则会导致事务不回滚。
本地方法不知道如何处理,只有调用方才可能知道如何处理异常;
例如:一些底层的方法,其可能出现多种异常,且调用方可能根据不同的异常做出不同的处理,只能抛出异常,而且必须是具体的异常类型,而不能是笼统的Exception类型。
throw | throws | |
位置 | 用在方法体内 | 用在方法声明后面(方法函数头) |
表示 | 抛出异常 | 抛出异常(throws表示出现异常的一种可能性) |
由..去处理异常 | 由方法体内的语句处理 | 由该方法的调用者处理 |
备注 | 具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw 一定是抛出了某种异常。 | throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。throws 表示出现异常的一种可能性,并不一定会发生这种异常。 |
两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。
上面是异常的抛出方式,表示在运行程序中的某些位置遇到了某种类型的异常导致程序不能按照期望的功能运行。有了异常就要进行处理,要么直接忽略掉这个异常,要么就打印出相应的信息供编程人员查看以解决异常。所以对异常的处理是非常有必要的,java的异常处理有很多种方式,选择哪种方式,取决于异常应该怎么处理。下面是异常的常见处理方式。
通常是在xxxController类的方法中进行异常的抛出,try里面是正常执行的业务处理,如果业务处理能够正常执行,那么就不会运行catch里面的代码,如果在业务执行的过程中有异常,那么就执行catch里面的代码。
示例代码:
try{
//调用相应的Handler进行业务逻辑处理
}catch(Exception ex) {
//执行相应方法
}
try/catch捕获异常的情景主要有:一般适用于需要忽略掉可能发生异常的情况下,或者程序块中语句可能的异常不能引起其他逻辑中断;缓存逻辑不能影响正常的逻辑运行,故缓存逻辑应该放在try/catch块中。比如:必须对异常进行处理,否则会影响程序运行,异常到了Controller层,若不处理则会返回404或500错误页面,因此,必须使用try/catch捕捉异常自己处理。
弊端:这种方式并不会影响代码执行的性能,但是试想如果每一个控制层(Controller类)的方法都有一个try、catch块,那么就会造成代码的冗余和重复。
当里面有错误时不抛出错误,而且运行catch里面的语句,try里面错误语句的后续代码不再运行,但是不影响后续代码运行
public int getNum(){
try {
int a = 1/0;
return 1;
} catch (Exception e) {
return 2;
}finally{
return 3;
}
代码运行到int a = 1/0;时遇到了一个 MathException,这时return 1;就不会执行了,代码直接跳转到 catch语句中,走到return 2;的时候,异常机制有这么一个原则:如果在 catch 中遇到了 return 或者异常等能使该函数终止的话,那么有 finally 就必须先执行完 finally 代码块里面的代码然后再返回值。因此代码又跳到return 3;,此时方法运行结束,返回值是3,因此第 6 行的返回结果就无法被真正返回。如果 finally 仅仅是处理了一个释放资源的操作,那么该道题最终返回的结果就是 2。
catch子句的排列顺序是子类在先,父类在后。
1. HandlerExceptionResolver接口的源码:
public interface HandlerExceptionResolver {
/**
* Try to resolve the given exception that got thrown during handler execution,
* returning a {@link ModelAndView} that represents a specific error page if appropriate.
* The returned {@code ModelAndView} may be {@linkplain ModelAndView#isEmpty() empty}
* to indicate that the exception has been resolved successfully but that no view
* should be rendered, for instance by setting a status code.
* @param request current HTTP request
* @param response current HTTP response
* @param handler the executed handler, or {@code null} if none chosen at the
* time of the exception (for example, if multipart resolution failed)
* @param ex the exception that got thrown during handler execution
* @return a corresponding {@code ModelAndView} to forward to,
* or {@code null} for default processing in the resolution chain
*/
@Nullable
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
这个接口定义传入4个参数:HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex
2.实现接口:
需要自己写一个实现类去实现HandlerExceptionResolver接口,这个接口里面只有一个resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex)方法需要重写,在重写的方法体里面加入判断就可以按照不同的异常类型进行相应的操作:
@Configuration
public class GlobalException implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler,Exception ex) {
ModelAndView mv = new ModelAndView();
// 判断不同异常类型,做不同操作
if(ex instanceof ArithmeticException) {
//相应操作
}
if(ex instanceof NullPointerException) {
//相应操作
}
mv.addObject("error", ex.toString());
return mv;
}
}
使用实现HandlerExceptionResolver接口的异常处理器进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,同时,在异常处理时能获取导致出现异常的对象,有利于提供更详细的异常处理信息。
使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver,重点看里面的doResolveException方法:
SimpleMappingExceptionResolver中的doResolveException方法:
@Override
@Nullable
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
// Expose ModelAndView for chosen error view.
String viewName = determineViewName(ex, request);
if (viewName != null) {
// Apply HTTP status code for error views, if specified.
// Only apply it if we're processing a top-level request.
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) {
applyStatusCodeIfPossible(request, response, statusCode);
}
return getModelAndView(viewName, ex, request);
}
else {
return null;
}
}
需要在spring配置文件中添加以下配置:
自己写一个全局处理器xxxExceptionResolver去继承内部异常处理类SimpleMappingExceptionResolver,必须要重写doResolverException方法:
public class BusinessExceptionResolver extends SimpleMappingExceptionResolver {
@Override
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
return super.doResolveException(request, response, handler, ex);
}
}
在重写的doResolverException中重写的方法可以自己编写异常的处理,根据异常的类型执行相应的操作,这种方法和第二种方法是极其相似的,只不过一个是继承,一个是实现。但通过继承关系来看,根本原因在于第二种方式中自定义的GlobalException类和本方式中的SimpleMappingExceptionResolver都实现了HandlerExceptionResolver接口,下面是SimpleMappingExceptionResolver继承实现关系:
优点:进行异常处理,具有集成简单、有良好的扩展性、对已有代码没有入侵性等优点,但该方法仅能获取到异常信息,若在出现异常时,对需要获取除异常以外的数据的情况不适用。
放在类(Exception.class)上边,和@ExceptionResolver(xxxException.class)放在方法(xxxHandler)上边,表示处理这个注解上的类就用这个xxxHandler方法。
在程序执行过程中,会自动扫描@ControllerAdvice和@ExceptionResolver注解以实现对某些注解下的类的异常处理,因此这种方式需要在自定义的类上面加上@ControllerAdvice和@ExceptionHandler注解,以Slf4j+Logback记录日志。
在全局异常处理的类上边写这样的注解:
@ControllerAdvice(annotations = {RestController.class, Controller.class, HandlingExceptions.class})
annotations ="xxx.class"表示凡是带有这些xxx注解的类遇到的异常都会进入@ControllerAdvice注解所在的类做相应的处理
可以简单地将所有的Exception类都使用这个方法:
@ExceptionHandler(Exception.class)
@ResponseBody
public xxx errorHandler(Exception ex) {
logger.error("request url: {},detail message is {}", request.getRequestURI(), ex.getMessage(), ex);
setDetailMessage(response, ex);
return response;
}
只像上面这样写也是可以的,还可以更加细化地处理这些异常,具体示例代码:
@ExceptionHandler(IllegalArgumentException.class)
@ResponseBody
public BaseResponse handleIllegalArgumentException(IllegalArgumentException ex) {
logger.error("request url: {},detail message is {}", request.getRequestURI(), ex.getMessage(), ex);
//省略
}
@ExceptionHandler(IllegalArgumentException.class)表明在遇到“向方法返回了一个不合法或者不正确的参数”类型的异常时,就会运行这个注解下面相应的方法。这样就实现了对于不同异常类型的不同方式的处理。