詳解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
中是無法預判異常環(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-09Spring實戰(zhàn)之使用Resource作為屬性操作示例
這篇文章主要介紹了Spring實戰(zhàn)之使用Resource作為屬性,結(jié)合實例形式分析了spring載人Resource作為屬性相關(guān)配置與使用技巧,需要的朋友可以參考下2020-01-01Java?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