1. 異常復(fù)現(xiàn)
工具及組件版本
IDE:IDEA 2023.3.2 (Ultimate Edition)
JDK:11
SpringBoot: 2.7.8
報(bào)錯(cuò)場(chǎng)景復(fù)現(xiàn)
編輯application.yml文件后,SpringBoot項(xiàng)目啟動(dòng)失敗,報(bào)錯(cuò)java.nio.charset.MalformedInputException
錯(cuò)誤信息如下:
org.yaml.snakeyaml.error.YAMLException: java.nio.charset.MalformedInputException: Input length = 2
at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:218)
at org.yaml.snakeyaml.reader.StreamReader.ensureEnoughData(StreamReader.java:176)
at org.yaml.snakeyaml.reader.StreamReader.ensureEnoughData(StreamReader.java:171)
at org.yaml.snakeyaml.reader.StreamReader.peek(StreamReader.java:126)
at org.yaml.snakeyaml.scanner.ScannerImpl.scanToNextToken(ScannerImpl.java:1218)
at org.yaml.snakeyaml.scanner.ScannerImpl.fetchMoreTokens(ScannerImpl.java:329)
at org.yaml.snakeyaml.scanner.ScannerImpl.checkToken(ScannerImpl.java:251)
at org.yaml.snakeyaml.parser.ParserImpl$ParseImplicitDocumentStart.produce(ParserImpl.java:214)
at org.yaml.snakeyaml.parser.ParserImpl.peekEvent(ParserImpl.java:166)
at org.yaml.snakeyaml.parser.ParserImpl.checkEvent(ParserImpl.java:156)
at org.yaml.snakeyaml.composer.Composer.checkNode(Composer.java:93)
at org.yaml.snakeyaml.constructor.BaseConstructor.checkData(BaseConstructor.java:124)
at org.yaml.snakeyaml.Yaml$1.hasNext(Yaml.java:509)
at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:198)
at org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:166)
at org.springframework.boot.env.OriginTrackedYamlLoader.load(OriginTrackedYamlLoader.java:88)
at org.springframework.boot.env.YamlPropertySourceLoader.load(YamlPropertySourceLoader.java:50)
at org.springframework.boot.context.config.StandardConfigDataLoader.load(StandardConfigDataLoader.java:54)
at org.springframework.boot.context.config.StandardConfigDataLoader.load(StandardConfigDataLoader.java:36)
at org.springframework.boot.context.config.ConfigDataLoaders.load(ConfigDataLoaders.java:108)
at org.springframework.boot.context.config.ConfigDataImporter.load(ConfigDataImporter.java:128)
at org.springframework.boot.context.config.ConfigDataImporter.resolveAndLoad(ConfigDataImporter.java:86)
at org.springframework.boot.context.config.ConfigDataEnvironmentContributors.withProcessedImports(ConfigDataEnvironmentContributors.java:116)
at org.springframework.boot.context.config.ConfigDataEnvironment.processWithProfiles(ConfigDataEnvironment.java:311)
at org.springframework.boot.context.config.ConfigDataEnvironment.processAndApply(ConfigDataEnvironment.java:232)
at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:102)
at org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor.postProcessEnvironment(ConfigDataEnvironmentPostProcessor.java:94)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEnvironmentPreparedEvent(EnvironmentPostProcessorApplicationListener.java:102)
at org.springframework.boot.env.EnvironmentPostProcessorApplicationListener.onApplicationEvent(EnvironmentPostProcessorApplicationListener.java:87)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:131)
at org.springframework.boot.context.event.EventPublishingRunListener.environmentPrepared(EventPublishingRunListener.java:85)
at org.springframework.boot.SpringApplicationRunListeners.lambda$environmentPrepared$2(SpringApplicationRunListeners.java:66)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:120)
at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:114)
at org.springframework.boot.SpringApplicationRunListeners.environmentPrepared(SpringApplicationRunListeners.java:65)
at org.springframework.boot.SpringApplication.prepareEnvironment(SpringApplication.java:343)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:301)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1303)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1292)
at com.test.monitor.MonitorApplication.main(Application.java:30)
Caused by: java.nio.charset.MalformedInputException: Input length = 2
at java.base/java.nio.charset.CoderResult.throwException(CoderResult.java:274)
at java.base/sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
at java.base/sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.base/java.io.InputStreamReader.read(InputStreamReader.java:185)
at org.yaml.snakeyaml.reader.UnicodeReader.read(UnicodeReader.java:125)
at org.yaml.snakeyaml.reader.StreamReader.update(StreamReader.java:183)
... 43 common frames omitted
配置文件更改內(nèi)容如下:
spring:
cloud:
nacos:
discovery:
# 測(cè)試服務(wù)器地址
server-addr: 127.0.0.1:8848
2. 問(wèn)題定位
由報(bào)錯(cuò)信息中的的MalformedInputException可知,這里是出現(xiàn)了編碼問(wèn)題,可能的選項(xiàng)有以下幾種:
- 配置文件的格式有問(wèn)題;
- 文件帶了BOM,覆蓋掉了默認(rèn)的配置;
- 輸入了編碼錯(cuò)誤的字符串,優(yōu)先懷疑中文。
逐一排查,發(fā)現(xiàn)文件的格式?jīng)]有問(wèn)題,也沒(méi)有BOM,推測(cè)是中文注釋出了問(wèn)題。
注釋掉中文注釋?zhuān)辉賵?bào)錯(cuò)。
解決臨時(shí)問(wèn)題,接下來(lái)需要找到根本原因。
我們繼續(xù)從異常信息下手,可以看到org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:198)方法調(diào)用,這兒就是yaml文件解析為字符串的方法。該方法源碼如下:
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
int count = 0;
try {
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + resource);
}
// 這里完成了比特流到字符流的轉(zhuǎn)換
try (Reader reader = new UnicodeReader(resource.getInputStream())) {
for (Object object : yaml.loadAll(reader)) {
if (object != null && process(asMap(object), callback)) {
count++;
if (this.resolutionMethod == ResolutionMethod.FIRST_FOUND) {
break;
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " document" + (count > 1 ? "s" : "") +
" from YAML resource: " + resource);
}
}
}
catch (IOException ex) {
handleProcessError(resource, ex);
}
return (count > 0);
}
可以看到代碼中第8行完成了比特流到字符流的轉(zhuǎn)換,debug模式下也正是這里出現(xiàn)了錯(cuò)誤,通過(guò)截取resource.getInputStream()中的內(nèi)容可知,注釋中的中文果然出現(xiàn)了亂碼。測(cè)試可知亂碼的編碼格式是gbk。
那么為什么在編碼格式不正確的時(shí)候,只要去掉英文字符就可以正常使用呢?
是因?yàn)榕渲弥谐酥形淖址猓?strong>配置之中的其他字符都屬于ASCII碼表中的字符,這部分字符在各種編碼中都是固定不變的,所以即使是錯(cuò)誤的編碼也讀取出了正確的信息。
注意:這里講到是常見(jiàn)編碼格式,例如UTF-16,UTF-16等因?yàn)殚L(zhǎng)度原因是不兼容ASCII格式的。
那么進(jìn)一步的,為什么IDE沒(méi)有提示我文件編碼信息錯(cuò)誤呢?
進(jìn)一步檢查IDEA中的文件編碼配置,路徑”Settings-->File Encodings“。然后發(fā)現(xiàn)“Project Encoding”的編碼里寫(xiě)著:<System Default: GBK>, 至此,真相大白。
IDE的項(xiàng)目編碼為系統(tǒng)默認(rèn)的GBK,覆蓋了全局編碼UTF-8格式,導(dǎo)致輸入的字符都為GBK,但是IDE是可以正常識(shí)別該編碼的。
編碼相關(guān)知識(shí)參考博客 ASCII、Unicode、UTF-8、UTF-16、GBK、GB2312、ANSI等編碼方式簡(jiǎn)析
3. 問(wèn)題處理
- 將
Project Encoding頁(yè)面統(tǒng)一的編碼改成UTF-8; - 給文件加上
UTF-8格式的BOM也可以解決問(wèn)題,增加之后再刪除BOM可以避免自己更改編碼錯(cuò)誤的內(nèi)容 - 將系統(tǒng)編碼改為
UTF-8,參考文檔