詳解Java如何優(yōu)雅的實現(xiàn)異常捕獲
在一個優(yōu)秀的項目中一定少不了對程序流程良好的異常捕獲與日志打印,通過二者結(jié)合可實現(xiàn)異常程序的快速定位,本片文章將詳細介紹如何優(yōu)雅的實現(xiàn)異常捕獲與日志打印輸出。
話不多說,下面我們就直奔主題開始介紹相關(guān)知識吧。
一、異常捕獲
1. 處理方式
程序異常是開發(fā)時不可避免的,很多時候我們需要針對不同異常進行不同的處理,最常用的就是try{} catch(){}語句,這里主要說明一下兩種常見異常處理的異同。
堆棧打印
當用 printStackTrace() 處理異常時,當程序出現(xiàn)異常將會在控制臺打印異常信息,然后繼續(xù)執(zhí)行之后的代碼。
public void Exception1Demo() {
try {
Integer.parseInt("abc");
} catch (Exception e) {
e.printStackTrace()
}
// 打印正常輸出
System.out.println("This is will show.");
}異常拋出
通過 throw new xxxException() 則會將異常信息根據(jù)調(diào)用層級逐層向上拋出,程序?qū)⒃诋惓L幹袛?,不會繼續(xù)執(zhí)行后續(xù)代碼。
public void Exception2Demo() {
try {
Integer.parseInt("abc");
} catch (Exception e) {
throw new IllegalArgumentException();
}
// 打印不會被輸出
System.out.println("This is will not show.");
}2. 捕獲示例
在上一點中介紹了兩種捕獲異常的處理方式,那在實際的程序開發(fā)中應(yīng)該如何進行選擇呢?
最常見的一種規(guī)范即底層異常永遠向上拋出,由最頂層統(tǒng)一處理。如下示例中 demo() 調(diào)用了 task() 方法,相對而言 task() 更為底層因此其捕獲異常時通過 throw 關(guān)鍵字向上拋出,而 demo() 為最頂層則可以通過 printStackTrace() 打印異常堆棧,當然也可以選擇繼續(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() 時將無法捕獲到程序異常,從而導致代碼的執(zhí)行順序可能將與我們設(shè)想的有所偏差。
當然還有一種情景是需要執(zhí)行批量操作,但是我們又想每一批之間可以互不影響,此時底層模塊也可以選擇不向上拋出異常。
稍微修改一下上述的 task() 方法為如下,這里通過 continue 跳過,替換為 e.printStackTrace() 效果一致。
private void task() {
for(int i = 0; i < 5; i++) {
try {
Integer.parseInt("abc");
} catch (Exception e) {
// 不拋出異常,繼續(xù)下一循環(huán)
continue;
}
}
}3. 自定義異常
自定義異常類十分簡單,只需繼承 RuntimeException ,并編寫相應(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 中引入斷言語法更簡潔的實現(xiàn)異常拋出,在 try catch 中是無法預(yù)判異常環(huán)節(jié)從而用其實現(xiàn)捕獲,斷言則更多的用于條件判斷。
即通過斷言用于判斷是否滿足先決條件,若否則在該處拋出異常,若是則正常執(zhí)行后續(xù)代碼,下面通過示例說明。
assert
通過 assert 可實現(xiàn)更便捷的數(shù)據(jù)合法性驗證,其基本語法如下,當表達式 <expression> 返回值為 false 時將拋出一個異常,通過 <message> 定義異常信息提示。
assert <expression> : <message>;
下面通過一個具體示例演示效果,兩個示例的作用效果相同,除了拋出的異常類型不同。
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
更簡潔的語法規(guī)則,效果同上,當捕獲到異常后將中斷程序,不會繼續(xù)執(zhí)行后續(xù)內(nèi)容。
public void Assert2Demo() {
String msg = "";
// 打印異常,效果等同 printStackTrace()
Assert.hasLength(msg, "不允許為空");
System.out.println("1111");
}二、日志監(jiān)控
在開發(fā)時如果需要查看某一處代碼信息時我們可以直接使用 println() 進行打印輸出,但生成環(huán)境下控制臺信息顯然變得沒有意義,此時我們就需要通過日志進行信息打印。
1. 日志打印
Java 提供原生日志工具類,導入包即可使用。
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 是當下較為流行的日志插件,在項目工程中引入下列依賴,其中 slf4j 指的是日志規(guī)范。
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.7</version>
</dependency>其提供了一下兩種初始化方式,區(qū)別在于使用 getClass() 定義的實例對象其子類仍可使用。
// 只能當前類可用 Logger logger = LoggerFactory.getLogger(LogTest.class); // 當前類與其子類都可用 Logger logger = LoggerFactory.getLogger(getClass());
Slf4j 規(guī)范針對不同級別的日志提供不同的接口方法如: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. 項目配置
在工程的 application.yml 添加 logging.config 用于指定日志配置文件路徑。
# 日志配置文件 logging: config: classpath:logback-spring.xml
2. 日志級別
這里單獨介紹一下 logger 標簽的作用,它可以用于控制指定包路徑下的日志是否輸出。
如配置了 <logger name="xyz.ibudai.slf4j.error" level="ERROR"/> 則 xyz.ibudai.slf4j.error 包路徑下的 info, debug, warn 將不會出現(xiàn)在配置的輸出日志文件中。
logger 標簽中不同的 level 配置輸出的信息如下:
- INFO:輸出
info, warn, error級別日志。 - DEBUG:輸出
info, debug, warn, error級別日志。 - WARN:只輸出
error級別日志。 - ERROR:只輸出
error級別日志。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 指定包路徑的日志輸出級別, 過濾低級別日志 -->
<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 配置文件,常見標簽參考下表。
| 標簽 | 作用 |
|---|---|
| property | 定義全局變量,可通過 ${} 表達式獲取。 |
| appender | 搭配 appender-ref 可為不同級別日志設(shè)置文件輸出配置。 |
| logger | 用于控制指定包下文件的日志輸出。 |
如下配置示例中即分別為 INFO, DEBUG, ERROR 三種日志級別配置了日志內(nèi)容文件輸出,具體作用參考備注信息。
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 設(shè)置日志存儲路徑 -->
<property name="LOG_HOME" value="./logs"/>
<!-- 指定基礎(chǔ)的日志輸出級別 -->
<root level="INFO">
<!-- appender 將會添加到這個 logger -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="INFO"/>
<appender-ref ref="DEBUG"/>
<appender-ref ref="ERROR"/>
</root>
<!-- 控制臺日志輸出 -->
<!-- 設(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: 表示線程名 -->
<!-- (3) %-5level: 日志級別, 從左顯示 4 個字符寬度 -->
<!-- (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>
<!-- 文件滾動模式 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志輸出的文件名 -->
<!-- (1) %i: 表示序號, 當日文件多份時用于區(qū)分 -->
<!-- (1) gz: 文件類型, 開啟文件壓縮 -->
<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>
<!-- 日志級別過濾, 過濾低級別日志 -->
<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)雅的實現(xiàn)異常捕獲的文章就介紹到這了,更多相關(guān)Java異常捕獲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 遞歸查詢部門樹形結(jié)構(gòu)數(shù)據(jù)的實踐
本文主要介紹了Java 遞歸查詢部門樹形結(jié)構(gòu)數(shù)據(jù)的實踐,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09
Spring實戰(zhàn)之使用Resource作為屬性操作示例
這篇文章主要介紹了Spring實戰(zhàn)之使用Resource作為屬性,結(jié)合實例形式分析了spring載人Resource作為屬性相關(guān)配置與使用技巧,需要的朋友可以參考下2020-01-01
Java?8?的異步編程利器?CompletableFuture的實例詳解
這篇文章主要介紹了Java?8?的異步編程利器?CompletableFuture?詳解,本文通過一個例子給大家介紹下Java?8??CompletableFuture異步編程的相關(guān)知識,需要的朋友可以參考下2022-03-03
詳解Spring cloud使用Ribbon進行Restful請求
這篇文章主要介紹了詳解Spring cloud使用Ribbon進行Restful請求,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04

