文章目录
今天复习了一天和Java异常体系相关的知识,在这篇文章总进行一个小小的总结,最后再提一提如何在项目中运用异常处理.
1.1 什么是异常(Exception),什么是错误(Error)
长话连篇的概念就不介绍了,简单地讲,异常和错误都属于Java异常体系中的一员,而异常是错误的孪生兄弟;错误一般由Java虚拟机所发现并且抛出,并不是由于开发者的代码逻辑问题而造成, 而异常则一般是由与程序自身出现的错误和遗漏所产生,而造成异常的原因有以下几点:
- 1.用户输入了非法数据
- 2.要打开的文件不存在
- 3.网络通信时连接中断,或者JVM内存溢出
而异常的作用就是标明程序的错误并阻止程序继续往下进行,告知开发者程序无法正确地运行下去,这样才不会带来一系列由于异常的出现而导致的问题
1.2 异常体系一览
在Java中,所有的Exception类及其子类,Error类及其子类都继承自Throwable类, 这是Java异常体系的超类:
对于异常来说,可以大致分为两种异常:
- 1.检查型异常
检查型异常指的是Exception及其之下除了RuntimeException之外的子类,抛出后必须要处理的异常,如果不在当前程序段进行处理,则需要向上声明抛出 - 2.非检查型异常指的是RuntimeException及其子类,它在抛出后无需强制处理
1.3 抛出异常
想要抛出一个异常很简单,只需要使用throw关键字就可以抛出一个异常:
if (args == null) {
throw new NullPointerException("args参数为null");
}
1.4 捕获和处理异常
在异常声明之后需要进行异常的捕获和处理,我们可以使用try{}
来捕获到异常,在之后使用catch(XException e){}
来处理异常,例如下面的程序:
public static void main(String[] args) {
try {
fun(null);
} catch (NullPointerException e) {
e.printStackTrace();
//对异常进行处理
}
}
public static void fun(String args) {
if (args == null) {
throw new NullPointerException("args参数为null");
}
}
当我们在程序中抛出的不是RuntimeException或者及其子类, 如果不立即捕获并且处理的话, 需要在方法中声明异常,向上抛出给调用者进行处理(注意处理者需要能够捕获到该类型的异常):
public static void main(String[] args) {
try {
fun(null);
} catch (Exception e) {
e.printStackTrace();
//对异常进行处理
}
}
public static void fun(String args) throws Exception{
if (args == null) {
throw new Exception("args参数为null");
}
}
在catch(XxException e){}块
中对异常进行处理时可以按异常的类型进行相应的处理:
public static void main(String[] args) {
try {
fun(null);
} catch (Xx1Exception e) {
e.printStackTrace();
//对异常类型1进行处理
} catch (Xx2Exception e) {
e.printStackTrace();
//对异常类型2进行处理
} catch (Xx3Exception e) {
e.printStackTrace();
//对异常类型4进行处理
}
}
这段程序会捕获fun方法产生的异常,并且按照异常的类型向下依次匹配,若抛出的异常是catch中的异常及其子类,则将会被相应的catch进行处理并且不会再往下匹配
当上面的catch块捕获的范围很大,而导致下面的catch块永远无法进去时,编译器报错:
1.5 finally块
finally块位于catch块之后,无论是否产生异常,finally块中的代码都会执行,并且能够无视try块和catch块中的程序结束关键字,例如break,continue或return:
public static void main(String[] args) {
try {
fun(null);
return;
} catch (Exception e) {
e.printStackTrace();
return;
//处理
} finally {
System.out.println("Finally块执行");
}
}
public static void fun(String args) throws Exception{
if (args == null) {
throw new Exception("args参数为null");
}
}
//打印:
java.lang.Exception: args参数为null
at thinking_in_java.exception.ExceptionCatchTest.fun(ExceptionCatchTest.java:24)
at thinking_in_java.exception.ExceptionCatchTest.main(ExceptionCatchTest.java:12)
Finally块执行
finally块一般用于以下几个方面:
- 1.释放数据库连接
- 2.关闭文件系统
- 3.关闭其他第三方库的源,避免持续地使用内存
1.6 异常链
在一个异常中抛出另一个异常被称为异常链,而在JDK 4之前,异常链需要自己去维护,不然只会把异常栈堆打印到最新的异常,而不会去显示之前异常的栈堆日志;在JDK 4中,超类Throwable在构造方法中引入了cause
,即它自身,这样的话在一个异常中抛出另一个异常就无需自己去处理了,直接把当前异常传入到新异常中即可:
public static void main(String[] args) {
try {
fun2(null);
} catch (Exception e) {
e.printStackTrace();
System.out.println("处理异常");
}
}
public static void fun2(String[] args) throws Exception {
try {
fun1(null);
} catch (Exception e) {
//再次抛出异常
throw new Exception(e);
}
}
public static void fun1(String args){
if (args == null) {
throw new NullPointerException("args参数为null");
}
}
打印异常链:NullPointerException -> Exception
1.7 项目中如何进行异常处理
下面结合一个模板消息模块简单地讲讲如何运用异常处理,这是SpringBoot/SSM的一个项目,处理的思路是在service层进行异常的抛出,并且统一在controller层去进行异常捕获和处理:
- ArgsLoseException
/**
* @Auther: ARong
* @Description: 参数缺失异常
**/
public class ArgsLoseException extends Exception {
public ArgsLoseException(){}
public ArgsLoseException(String message){super(message);}
}
- ArgsErrorException
/**
* @Auther: ARong
* @Description: 参数错误异常
**/
public class ArgsErrorException extends Exception {
public ArgsErrorException() {}
public ArgsErrorException(String messasge) {super(messasge);}
}
- TemplateMessageSerivceImp
/**
* @Auther: ARong
* @Description: 发送模板消息
**/
@Service
@Transactional
@Slf4j
public class TemplateMessageSerivceImp implements TemplateMessageSerivce {
@Autowired
private MessageUtil messageUtil;
/**
* @param messageEntity
* @param opeType
* @auther: Arong
* @description: 注册成功后发送
* @param: [messageEntity]
* @return: AjaxResult
*/
@Override
public AjaxResult sendMessage(MessageEntity messageEntity, String opeType) throws ArgsLoseException, ArgsErrorException {
Map map = MessageUtil.MessageMap(
messageEntity.getOpenId(),
messageEntity.getPage(),
messageEntity.getFormId()
);
if (opeType == null || "".equals(opeType.trim())) {
throw new ArgsLoseException("opeType参数缺失");
}
if (!"YES".equals(opeType)) {
throw new ArgsErrorException("opeType参数错误");
}
//...业务逻辑
return null;
}
}
此时重点在于,controller层应该如何去处理这些异常呢?
首先我们需要注意的是,不应该让异常直接暴露在用户眼中,而是要将异常转化为前端的一种状态码,让前端去做相应的处理,例如根据状态码去提示用户哪些操作是错误的,或者说是网络延迟等等,总之重点在于前后端约定状态码以及将异常转化为用户眼中的一种状态:
- TemplateMessageController
@Controller
@CrossOrigin
@RequestMapping(value = "/message")
@ResponseBody
@Slf4j
public class TemplateMessageController {
@Autowired
private TemplateMessageSerivce templateMessageSerivce;
/**
* @auther: Arong
* @description: 注册成功后发送
* @param: [messageEntity]
* @return: AjaxResult
*/
@PostMapping(value = "/sendMessage")
public AjaxResult sendMessage(
@ModelAttribute MessageEntity messageEntity,
@RequestParam(name = "opeType") String opeType //操作类型
) {
AjaxResult ajaxResult = null;
try {
ajaxResult = templateMessageSerivce.sendMessage(messageEntity, opeType);
} catch (Exception e) {
//打印异常栈堆路径
e.printStackTrace();
//分不同异常给前端返回不同的状态码(不要将异常暴露给前端,而是通过状态码让前端去反应)
if (e instanceof ArgsLoseException) {
//回传状态码501
return new AjaxResult(501, "传入opeType为空");
} else if (e instanceof ArgsErrorException) {
//回传状态码502s
return new AjaxResult(502, "参数错误");
}
}
return ajaxResult;
}
}
或者也可以这么去捕获异常,但是catch块会有很多:
try {
ajaxResult = templateMessageSerivce.sendMessage(messageEntity, opeType);
} catch (ArgsLoseException e) {
//..
} catch (ArgsErrorException e) {
//..
}
这样就可以让开发者知道哪出错了,而对于前端来说,只是转变了个状态码,跳转了其他页面
今天的异常体系就复习到这里吧,其实本来还想接着写一些源码分析,后来顺着执行过程看了下发现不过是不断地在构造函数中调用父类构造方法,直到调用到超类的构造方法中执行了一些方法,感觉没什么可以分析的了,今天就这样吧~(゜-゜)つロ (干杯)