詳解Java如何優(yōu)雅的實(shí)現(xiàn)異常捕獲
在一個(gè)優(yōu)秀的項(xiàng)目中一定少不了對(duì)程序流程良好的異常捕獲與日志打印,通過(guò)二者結(jié)合可實(shí)現(xiàn)異常程序的快速定位,本片文章將詳細(xì)介紹如何優(yōu)雅的實(shí)現(xiàn)異常捕獲與日志打印輸出。
話(huà)不多說(shuō),下面我們就直奔主題開(kāi)始介紹相關(guān)知識(shí)吧。
一、異常捕獲
1. 處理方式
程序異常是開(kāi)發(fā)時(shí)不可避免的,很多時(shí)候我們需要針對(duì)不同異常進(jìn)行不同的處理,最常用的就是try{} catch(){}語(yǔ)句,這里主要說(shuō)明一下兩種常見(jiàn)異常處理的異同。
堆棧打印
當(dāng)用 printStackTrace() 處理異常時(shí),當(dāng)程序出現(xiàn)異常將會(huì)在控制臺(tái)打印異常信息,然后繼續(xù)執(zhí)行之后的代碼。
public void Exception1Demo() {
try {
Integer.parseInt("abc");
} catch (Exception e) {
e.printStackTrace()
}
// 打印正常輸出
System.out.println("This is will show.");
}異常拋出
通過(guò) throw new xxxException() 則會(huì)將異常信息根據(jù)調(diào)用層級(jí)逐層向上拋出,程序?qū)⒃诋惓L幹袛啵粫?huì)繼續(xù)執(zhí)行后續(xù)代碼。
public void Exception2Demo() {
try {
Integer.parseInt("abc");
} catch (Exception e) {
throw new IllegalArgumentException();
}
// 打印不會(huì)被輸出
System.out.println("This is will not show.");
}2. 捕獲示例
在上一點(diǎn)中介紹了兩種捕獲異常的處理方式,那在實(shí)際的程序開(kāi)發(fā)中應(yīng)該如何進(jìn)行選擇呢?
最常見(jiàn)的一種規(guī)范即底層異常永遠(yuǎn)向上拋出,由最頂層統(tǒng)一處理。如下示例中 demo() 調(diào)用了 task() 方法,相對(duì)而言 task() 更為底層因此其捕獲異常時(shí)通過(guò) throw 關(guān)鍵字向上拋出,而 demo() 為最頂層則可以通過(guò) printStackTrace() 打印異常堆棧,當(dāng)然也可以選擇繼續(xù)拋出由系統(tǒng)處理異常。
@Test
public void demo() {
try {
task();
} catch (Exception e) {
e.printStackTrace();
}
}
private void task() {
try {
Integer.parseInt("abc");
} catch (Exception e) {
throw new RuntimeException(e);
}
}但如果將上述示例的 task() 方法中捕獲異常替換為 e.printStackTrace(); 則 demo() 在調(diào)用 task() 時(shí)將無(wú)法捕獲到程序異常,從而導(dǎo)致代碼的執(zhí)行順序可能將與我們?cè)O(shè)想的有所偏差。
當(dāng)然還有一種情景是需要執(zhí)行批量操作,但是我們又想每一批之間可以互不影響,此時(shí)底層模塊也可以選擇不向上拋出異常。
稍微修改一下上述的 task() 方法為如下,這里通過(guò) continue 跳過(guò),替換為 e.printStackTrace() 效果一致。
private void task() {
for(int i = 0; i < 5; i++) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
// 不拋出異常,繼續(xù)下一循環(huán)
continue;
}
}
}3. 自定義異常
自定義異常類(lèi)十分簡(jiǎn)單,只需繼承 RuntimeException ,并編寫(xiě)相應(yīng)的構(gòu)造方法即可,使用方法同上。
public class BaseException extends RuntimeException {
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}4. 斷言處理
在 JDK 1.4 中引入斷言語(yǔ)法更簡(jiǎn)潔的實(shí)現(xiàn)異常拋出,在 try catch 中是無(wú)法預(yù)判異常環(huán)節(jié)從而用其實(shí)現(xiàn)捕獲,斷言則更多的用于條件判斷。
即通過(guò)斷言用于判斷是否滿(mǎn)足先決條件,若否則在該處拋出異常,若是則正常執(zhí)行后續(xù)代碼,下面通過(guò)示例說(shuō)明。
assert
通過(guò) assert 可實(shí)現(xiàn)更便捷的數(shù)據(jù)合法性驗(yàn)證,其基本語(yǔ)法如下,當(dāng)表達(dá)式 <expression> 返回值為 false 時(shí)將拋出一個(gè)異常,通過(guò) <message> 定義異常信息提示。
assert <expression> : <message>;
下面通過(guò)一個(gè)具體示例演示效果,兩個(gè)示例的作用效果相同,除了拋出的異常類(lèi)型不同。
public void AssertDemo() {
int y = -1;
assert y > 0 : "The value of y is lower then zero";
}
public void AssertDemo() {
int y = -1;
if(y < 0) {
throw new RuntimeException("The value of y is lower then zero");
}
}Assert
更簡(jiǎn)潔的語(yǔ)法規(guī)則,效果同上,當(dāng)捕獲到異常后將中斷程序,不會(huì)繼續(xù)執(zhí)行后續(xù)內(nèi)容。
public void Assert2Demo() {
String msg = "";
// 打印異常,效果等同 printStackTrace()
Assert.hasLength(msg, "不允許為空");
System.out.println("1111");
}二、日志監(jiān)控
在開(kāi)發(fā)時(shí)如果需要查看某一處代碼信息時(shí)我們可以直接使用 println() 進(jìn)行打印輸出,但生成環(huán)境下控制臺(tái)信息顯然變得沒(méi)有意義,此時(shí)我們就需要通過(guò)日志進(jìn)行信息打印。
1. 日志打印
Java 提供原生日志工具類(lèi),導(dǎo)入包即可使用。
import java.util.logging.Logger;
public void LogDemo() {
Logger logger = Logger.getGlobal();
logger.info("start process...");
logger.warning("memory is running out...");
logger.fine("ignored.");
logger.severe("process will be terminated...");
}2. Log4j框架
除了自帶的日志框架,Log4j 是當(dāng)下較為流行的日志插件,在項(xiàng)目工程中引入下列依賴(lài),其中 slf4j 指的是日志規(guī)范。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.7</version>
</dependency>其提供了一下兩種初始化方式,區(qū)別在于使用 getClass() 定義的實(shí)例對(duì)象其子類(lèi)仍可使用。
// 只能當(dāng)前類(lèi)可用 Logger logger = LoggerFactory.getLogger(LogTest.class); // 當(dāng)前類(lèi)與其子類(lèi)都可用 Logger logger = LoggerFactory.getLogger(getClass());
Slf4j 規(guī)范針對(duì)不同級(jí)別的日志提供不同的接口方法如:info()、 warn()、 debug()、 error(), 基本使用示例如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LogTest{
Logger logger = LoggerFactory.getLogger(LogTest.class);
public static void main(String[] args) {
logger.info("info ...");
logger.warn("warn ...");
logger.debug("debug ...");
logger.error("error ...");
}
}三、Logbak配置
1. 項(xiàng)目配置
在工程的 application.yml 添加 logging.config 用于指定日志配置文件路徑。
# 日志配置文件 logging: config: classpath:logback-spring.xml
2. 日志級(jí)別
這里單獨(dú)介紹一下 logger 標(biāo)簽的作用,它可以用于控制指定包路徑下的日志是否輸出。
如配置了 <logger name="xyz.ibudai.slf4j.error" level="ERROR"/> 則 xyz.ibudai.slf4j.error 包路徑下的 info, debug, warn 將不會(huì)出現(xiàn)在配置的輸出日志文件中。
logger 標(biāo)簽中不同的 level 配置輸出的信息如下:
- INFO:輸出
info, warn, error級(jí)別日志。 - DEBUG:輸出
info, debug, warn, error級(jí)別日志。 - WARN:只輸出
error級(jí)別日志。 - ERROR:只輸出
error級(jí)別日志。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 指定包路徑的日志輸出級(jí)別, 過(guò)濾低級(jí)別日志 -->
<logger name="xyz.ibudai.slf4j.info" level="INFO"/>
<logger name="xyz.ibudai.slf4j.debug" level="DEBUG"/>
<logger name="xyz.ibudai.slf4j.warn" level="WARN"/>
<logger name="xyz.ibudai.slf4j.error" level="ERROR"/>
</configuration>3. 配置格式
在 resources 目錄下新建 logback-spring.xml 配置文件,常見(jiàn)標(biāo)簽參考下表。
| 標(biāo)簽 | 作用 |
|---|---|
| property | 定義全局變量,可通過(guò) ${} 表達(dá)式獲取。 |
| appender | 搭配 appender-ref 可為不同級(jí)別日志設(shè)置文件輸出配置。 |
| logger | 用于控制指定包下文件的日志輸出。 |
如下配置示例中即分別為 INFO, DEBUG, ERROR 三種日志級(jí)別配置了日志內(nèi)容文件輸出,具體作用參考備注信息。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 設(shè)置日志存儲(chǔ)路徑 -->
<property name="LOG_HOME" value="./logs"/>
<!-- 指定基礎(chǔ)的日志輸出級(jí)別 -->
<root level="INFO">
<!-- appender 將會(huì)添加到這個(gè) logger -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="ERROR"/>
</root>
<!-- 控制臺(tái)日志輸出 -->
<!-- 設(shè)置彩色輸出: -Dlog4j.skipJansi=false -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!-- 設(shè)置輸出格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!-- 格式化輸出 -->
<!-- (1) %d: 表示日期 -->
<!-- (2) %thread: 表示線(xiàn)程名 -->
<!-- (3) %-5level: 日志級(jí)別, 從左顯示 4 個(gè)字符寬度 -->
<!-- (4) %msg: 表示日志消息 -->
<!-- (5) %n: 表示換行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %highlight(%-4level) %cyan(%logger{50}:%L) - %msg%n</pattern>
<!-- 設(shè)置編碼 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照 INFO 每天生成日志文件 -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 日志名, 指定最新的文件名, 其他文件名使用 FileNamePattern -->
<file>${LOG_HOME}/info.log</file>
<!-- 文件滾動(dòng)模式 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志輸出的文件名 -->
<!-- (1) %i: 表示序號(hào), 當(dāng)日文件多份時(shí)用于區(qū)分 -->
<!-- (1) gz: 文件類(lèi)型, 開(kāi)啟文件壓縮 -->
<FileNamePattern>${LOG_HOME}/bak/info.log.%d{yyyy-MM-dd}.%i.log.gz</FileNamePattern>
<!-- 日志文件保留天數(shù) -->
<MaxHistory>7</MaxHistory>
<!-- 按大小分割同一天的 -->
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>128MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 日志級(jí)別過(guò)濾, 過(guò)濾低級(jí)別日志 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<!-- 日志內(nèi)容輸出格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照 DEBUG 每天生成日志文件 -->
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<File>${LOG_HOME}/debug.log</File>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/bak/info.%d{yyyy-MM-dd}.%i.log.gz</FileNamePattern>
<MaxHistory>7</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>128MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照 ERROR 每天生成日志文件 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<FileNamePattern>${LOG_HOME}/bak/error.log.%d{yyyy-MM-dd}.%i.log.gz</FileNamePattern>
<MaxHistory>7</MaxHistory>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>128MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %thread %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- MyBatis log configure -->
<logger name="com.apache.ibatis" level="INFO"/>
<logger name="java.sql.Connection" level="ERROR"/>
<logger name="java.sql.Statement" level="ERROR"/>
<logger name="java.sql.PreparedStatement" level="ERROR"/>
</configuration>到此這篇關(guān)于詳解Java如何優(yōu)雅的實(shí)現(xiàn)異常捕獲的文章就介紹到這了,更多相關(guān)Java異常捕獲內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java面試必問(wèn)之ThreadLocal終極篇分享
ThreadLocal是什么呢?其實(shí)ThreadLocal并非是一個(gè)線(xiàn)程的本地實(shí)現(xiàn)版本,它并不是一個(gè)Thread,而是thread local variable(線(xiàn)程局部變量),這篇文章主要給大家介紹了關(guān)于Java面試必問(wèn)之ThreadLocal終極篇的相關(guān)資料,需要的朋友可以參考下2021-10-10
Java 遞歸查詢(xún)部門(mén)樹(shù)形結(jié)構(gòu)數(shù)據(jù)的實(shí)踐
本文主要介紹了Java 遞歸查詢(xún)部門(mén)樹(shù)形結(jié)構(gòu)數(shù)據(jù)的實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Spring實(shí)戰(zhàn)之使用Resource作為屬性操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之使用Resource作為屬性,結(jié)合實(shí)例形式分析了spring載人Resource作為屬性相關(guān)配置與使用技巧,需要的朋友可以參考下2020-01-01
Java?8?的異步編程利器?CompletableFuture的實(shí)例詳解
這篇文章主要介紹了Java?8?的異步編程利器?CompletableFuture?詳解,本文通過(guò)一個(gè)例子給大家介紹下Java?8??CompletableFuture異步編程的相關(guān)知識(shí),需要的朋友可以參考下2022-03-03
詳解Spring cloud使用Ribbon進(jìn)行Restful請(qǐng)求
這篇文章主要介紹了詳解Spring cloud使用Ribbon進(jìn)行Restful請(qǐng)求,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Logback配置文件這么寫(xiě),還說(shuō)你不會(huì)整理日志?
logback框架會(huì)默認(rèn)加載classpath下命名為logback-spring.xml或logback.xml的配置文件。這篇文章主要介紹了Logback配置文件寫(xiě)法,需要的朋友可以參考下2020-07-07
Java使用poi實(shí)現(xiàn)excel的導(dǎo)入操作指南
使用Apache Poi是一種流行且廣泛使用的方式,可以幫助開(kāi)發(fā)人員直接從Java代碼中讀取、寫(xiě)入和處理Excel文件,因此在這篇文章我們將著重介紹如何實(shí)現(xiàn)excel的導(dǎo)入,感興趣的朋友可以跟著小編一起來(lái)學(xué)習(xí)2023-06-06

