文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2019-10-14   1.9k

Java基础学习之异常处理

Java的异常

Java标准库中常用的异常:

异常是一种class,它的继承关系如下:

Throwable有两个子类:ErrorException

Error表示严重的错误,例如:

  • OutOfMemoryError:内存耗尽
  • NoClassDefFountError:无法加载某个Class
  • StackOverflowError:栈溢出

Exception表示运行时错误,它可以被捕获并处理。

程序逻辑处理相关:

  • NumberFormatException:数值类型的格式错误
  • FileNotFoundException:未找到文件
  • SocketException:读取网络失败

以及程序逻辑编写错误:

  • NullPointerException:对某个null的对象调用方法或字段
  • IndexOutOfBoundsException:数组索引越界

Java规定:

  • 必须捕获的异常包括:Exception及其子类,除了RuntimeExcetion及其子类。
  • 不需要捕获的异常包括:Error及其子类,RuntimeExcetion及其子类。

捕获异常

捕获异常使用try...catch语句,把可能发生异常的代码放到try {...}中,然后使用catch捕获对应的Exception及其子类 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import java.io.UnsupportedEncodingException;
import java.util.Arrays;

public class Main {
public static void main(String[] args) {
byte[] bs = toGBK("中文");
System.out.println(Arrary.toString(bs));
}

static byte[] toGBK(String s) {
try {
return s.getBytes("GBK");
} catch (UnsupportedEncodingException e) {
System.out.println(e);
return s.getBytes(); //使用默认编码
}
}
}

// 输出
// [-42, -48, -50, -60]

如果我们不捕获UnsupportedEncodingException,则会出现编译失败,这是因为String.getBytes()方法的定义是:

1
2
3
public byte[] getBytes(String charsetName) throws UnsupportedEncodingException {
...
}

所以调用方在调用的时候,必须强制捕获这些异常,否则编译器会报错。

多catch语句

可以使用多个catch语句,每个catch捕获相对应的异常,但是,多个catch语句只有一个能被执行。所以存在多个catch时候,catch的顺序非常重要:子类必须写在前面。例如:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
try {
...
} catch (IOExcetion c) {
System.out.println("IO error");
} catch (UnsupportedEncodingException e) { // 永远捕获不到
System.out.println("Bad encoding");
}

因为UnsupportedEncodingExceptionIOExcetion的子类,所以当抛出UnsupportedEncodingException异常时,会被catch (IOException e) { ... }捕获并执行。

finally语句

无论是否有异常发生,如果我们都希望执行一些语句,可以使用finally语句,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (UnsupportedEncodingException e) {
System.out.println("Bad encoding");
} catch (IOException e) {
System.out.println("IO error");
} finally {
System.out.println("END");
}
}

如果某些异常的处理逻辑相同,但是异常本身不存在继承关系,我们可以使用|来合并:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
try {
process1();
process2();
process3();
} catch (IOException | NumberFormatException e) { // IOException或NumberFormatException
System.out.println("Bad input");
} catch (Exception e) {
System.out.println("Unknown error");
}
}

抛出异常

抛出异常分为两步:

  1. 创建某个Excetion实例;
  2. throw语句抛出。

例如:

1
2
3
4
5
void process(String s) {
if (s == null) {
throw new NullPointerExcetion();
}
}

异常转换

1
2
3
4
5
6
7
8
9
10
11
12
13
void process1(String s) {
try {
process2();
} catch (NullPointerExcetion e) {
throw new IllegalArgumentExcement;
}
}

void process2(String s) {
if (s==null){
throw new NullPonterExcetion();
}
}

process2()抛出NullPointerException后,被process1()捕获,然后抛出IllegalArgumentException()

如果在main()中捕获IllegalArgumentException,我们看看打印的异常栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}

static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}

static void process2() {
throw new NullPointerException();
}
}

异常栈如下:

1
2
3
4
5
6
7
8
9
10
11
java.lang.IllegalArgumentException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:567)
at jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
at jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
at jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)

我们已经看不到原始异常NullPointerException的信息了。 为了能追踪到完整的异常栈,在构造异常的时候,把原始的Exception实例传进去,新的Exception就可以持有原始Exception信息。 即:

1
2
3
4
5
6
7
8
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}

异常栈如下:

1
2
3
4
5
6
7
java.lang.IllegalArgumentException: java.lang.NullPointerException
at Main.process1(Main.java:15)
at Main.main(Main.java:5)
Caused by: java.lang.NullPointerException
at Main.process2(Main.java:20)
at Main.process1(Main.java:13)

自定义异常

一个常见的做法是自定义一个BaseException作为“根异常”,然后,派生出各种业务类型的异常。

BaseException需要从一个适合的Exception派生,通常建议从RuntimeException派生:

1
2
3
public class BaseException extends RuntimeExcetion {
}

其他业务类型的异常就可以从BaseException派生:

1
2
3
4
5
6
7
8
public class UserNotFoundException extends BaseException {
}

public class LoginFailedException extends BaseException {
}

...

使用JDK Logging

直接上🌰

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.logging.Level;
import java.util.logging.Logger;

public class Hello {
public static void main(String[] args) {
Logger logger = Logger.getGlobal();
logger.info("start process...");
logger.warning("memory is running out...");
logger.fine("ignore");
logger.severe("process will be terminated...");
}
}

输出:

1
2
3
4
5
6
7
10月 12, 2019 6:34:55 下午 Hello main
信息: start process...
10月 12, 2019 6:34:55 下午 Hello main
警告: memory is running out...
10月 12, 2019 6:34:55 下午 Hello main
严重: process will be terminated...

logger.fine("ignore");没有打印是因为JDK默认的消息级别是Info

  • SEVERE
  • WARNING
  • INFO
  • CONFIG
  • FINE
  • FINER
  • FINEST

使用Common Logging

使用 Common Logging只需要两个步骤:

  • 通过LogFactory获取Log类的实例
  • 使用Log实例的方法打印日志
1
2
3
4
5
6
7
8
9
10
11
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Main {
public static void main(String[] args) {
Log log = LogFactory.getLog(Main.class);
log.info("start...");
log.warn("end...");
}
}

Commons Logging定义了6个日志级别:

  • FATAL
  • ERROR
  • WARNING
  • INFO
  • DEBUG
  • TRACE

使用Common Logging时,如果在静态方法中引用Log,通常直接定义一个静态类型变量:

1
2
3
4
5
6
7
8
public class Main{
static final Log log = LogFactory.getLog(Main.class);

static void foo(){
log.info("foo");
}
}

在实例方法中引用Log,通过定义一个实例变量:

1
2
3
4
5
6
7
8
public class Person {
protected final Log = LogFactory.getLog(getClass());

void foo(){
log.info("foo");
}
}

这两种定义的方法区别在于:实例变量log获取方式是LogFactory.getLog(getClass),这种做法的好处是,子类可以直接使用log实例,例如:

1
2
3
4
5
6
public class Student extends Person {
void bar() {
log.info("bar");
}
}

此外,Commons Logging的日志方法,例如info(),除了标准的info(String)外,还提供了一个非常有用的重载方法:info(String, Throwable),这使得记录异常更加简单:

1
2
3
4
5
6
try {
...
} catch (Exception e) {
log.error("got Exception", e);
}

使用Log4j

Commons Logging,可以作为“日志接口”来使用,而实现这个接口可以使用Log4j。

我们在使用Log4j框架时,更多时候是通过配置文件来配置它。

以XML配置为例,使用Log4j的时候,我们把一个log4j2.xml的文件放到classpath下就可以让Log4j读取配置文件并按照我们的配置来输出日志。

另外,因为Log4j也是一个第三方库,需要把下面4个jar包放在classpath中:

  • log4j-api-2.x.jar
  • log4j-core-2.x.jar
  • log4j-jcl-2.x.jar
  • commons-logging-1.2.jar

使用SLF4J和Logback

SLF4J类似于Commons Logging,也是一个日志接口,而Logback类似于Log4j,是一个日志的实现。

SLF4J使用方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

class Main {
final Logger = LoggerFactory.getLogger(getClass());

void foo() {
int bar = 100;
String name = "foo";
logger.info("bar = {} name = {}", bar, name);
}
}

SLF4J使用下面三个jar包:

  • slf4j-api-1.7.x.jar
  • logback-classic-1.2.x.jar
  • logback-core-1.2.x.jar

和Log4j类似,我们仍然需要一个Logback的配置文件,把logback.xml放到classpath下。

Copyright © ca01h 2019-2020 | 本站总访问量