1. 异常复现
工具及组件版本
IDE:IDEA 2023.3.2 (Ultimate Edition)
JDK:11
SpringBoot: 2.7.8
报错场景复现
编辑application.yml
文件后,SpringBoot项目启动失败,报错java.nio.charset.MalformedInputException
错误信息如下:
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
配置文件更改内容如下:
spring:
cloud:
nacos:
discovery:
# 测试服务器地址
server-addr: 127.0.0.1:8848
2. 问题定位
由报错信息中的的MalformedInputException
可知,这里是出现了编码问题,可能的选项有以下几种:
- 配置文件的格式有问题;
- 文件带了BOM,覆盖掉了默认的配置;
- 输入了编码错误的字符串,优先怀疑中文。
逐一排查,发现文件的格式没有问题,也没有BOM,推测是中文注释出了问题。
注释掉中文注释,不再报错。
解决临时问题,接下来需要找到根本原因。
我们继续从异常信息下手,可以看到org.springframework.beans.factory.config.YamlProcessor.process(YamlProcessor.java:198)
方法调用,这儿就是yaml文件解析为字符串的方法。该方法源码如下:
private boolean process(MatchCallback callback, Yaml yaml, Resource resource) {
int count = 0;
try {
if (logger.isDebugEnabled()) {
logger.debug("Loading from YAML: " + resource);
}
// 这里完成了比特流到字符流的转换
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行完成了比特流到字符流的转换,debug模式下也正是这里出现了错误,通过截取resource.getInputStream()
中的内容可知,注释中的中文果然出现了乱码。测试可知乱码的编码格式是gbk
。
那么为什么在编码格式不正确的时候,只要去掉英文字符就可以正常使用呢?
是因为配置中除了中文字符之外,配置之中的其他字符都属于ASCII码表中的字符,这部分字符在各种编码中都是固定不变的,所以即使是错误的编码也读取出了正确的信息。
注意:这里讲到是常见编码格式,例如UTF-16,UTF-16等因为长度原因是不兼容ASCII格式的。
那么进一步的,为什么IDE没有提示我文件编码信息错误呢?
进一步检查IDEA中的文件编码配置,路径”Settings-->File Encodings“。然后发现“Project Encoding”的编码里写着:<System Default: GBK>
, 至此,真相大白。
IDE的项目编码为系统默认的GBK,覆盖了全局编码UTF-8
格式,导致输入的字符都为GBK,但是IDE是可以正常识别该编码的。
编码相关知识参考博客 ASCII、Unicode、UTF-8、UTF-16、GBK、GB2312、ANSI等编码方式简析
3. 问题处理
- 将
Project Encoding
页面统一的编码改成UTF-8
; - 给文件加上
UTF-8
格式的BOM也可以解决问题,增加之后再删除BOM可以避免自己更改编码错误的内容 - 将系统编码改为
UTF-8
,参考文档
4. 参考
[1] ASCII、Unicode、UTF-8、UTF-16、GBK、GB2312、ANSI等编码方式简析
标签:文件,MalformedInputException,java,更改,snakeyaml,boot,springframework,yaml,org From: https://www.cnblogs.com/bloodcolding/p/18308824