1.问题描述

由于业务需求需要在application.properties中配置一个带有中文字符串的参数,注入到业务类中,但是发现注入的中文是乱码的。大概情况如下所示:

@SpringBootTest(classes = Application.class)
@RunWith(SpringRunner.class)
public class UnitTest {
    @Value("${name}")
    private String name;

    @Test
    public void test1() throws UnsupportedEncodingException {
        System.out.println("中文内容:" + name);
    }
}

打印输出结果:

2.问题解决

2.1网络上已有的解决办法

看到这个问题,第一时间去网上博客搜索。网络上的解决办法基本一致,概括为以下三种。

2.1.1修改IDEA编码

在IDEA中将所有的编码设置为UTF-8同时勾上一个叫Transparent native-to-ascii conversion的选项,然后重新创建application.properties的文件

运行结果

但是这个配置文件几乎无法再使用记事本修改了。。。用记事本打开编辑时,发现内容被修改成了unicode编码,在线上部署时几乎是绝望的。。。所以此方式我不做推荐。

 

2.1.2将application.properties改为application.yml

就是将application.properties的文件修改为application.yml的结构,重启项目。

运行效果

证明是可行的。这种方式可以根据自己需求选择,但是当配置文件的内容层级较深时也不推荐,容易看错行配置。

2.1.3在读取properties文件的时候设置编码

@Configuration
@PropertySource(value = "classpath:application.properties", encoding = "utf-8")
public class MyConfig {
    @Value("${name}")
    private String name;

    @PostConstruct
    public void init() {
        System.out.println("name is :" + name);
    }
}

亲测发现这种方式针对application.properties是不行的。

但是针对其他名称的properties文件是可以的

@Configuration
@PropertySource(value = "classpath:test.properties", encoding = "utf-8")
public class MyConfig2 {
    @Value("${name2}")
    private String name;
    @PostConstruct
    public void init() {
        System.out.println("name2 is :" + name);
    }
}

运行结果

2.2编码层面解决办法(个人研究的)

2.2.1研究过程(不喜欢的直接看2.2.2解决办法)

以上网络上的解决办法,都不太适合个人的话,请留意这一种终极解决办法,本人研究得出的,如有问题望各位大佬评论指证。通过阅读源码发现在ConfigFileApplicationListener中有如下代码

private PropertySource<?> doLoadIntoGroup(String identifier, String location,
				Profile profile) throws IOException {
			Resource resource = this.resourceLoader.getResource(location);
			PropertySource<?> propertySource = null;
			StringBuilder msg = new StringBuilder();
			if (resource != null && resource.exists()) {
				String name = "applicationConfig: [" + location + "]";
				String group = "applicationConfig: [" + identifier + "]";
                //重点就在这里,可以在这里打个断点,发现在这里就已经把application.properties的内容读取到了
				propertySource = this.propertiesLoader.load(resource, group, name,
						(profile == null ? null : profile.getName()));
				...
		}

好了,那么进入这个方法内部看

进入PropertiesPropertySourceLoader查看load方法

	public PropertySource<?> load(String name, Resource resource, String profile)
			throws IOException {
		if (profile == null) {
            //好了 我们发现properties实在这里加载的
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			if (!properties.isEmpty()) {
				return new PropertiesPropertySource(name, properties);
			}
		}
		return null;
	}

断点查看执行效果

那么可以发现问题就是出在这个PropertiesLoaderUtils.loadProperties(resource)内部了,进入查看内部调用过程是PropertiesLoaderUtils.loadProperties(resource)->fillProperties()
public static void fillProperties(Properties props, Resource resource) throws IOException {
        InputStream is = resource.getInputStream();

        try {
            String filename = resource.getFilename();
            if (filename != null && filename.endsWith(".xml")) {
                props.loadFromXML(is);
            } else {
                //问题就在这里,这里使用的是字节流,没有指定字符编码 那么读取时将会以ISO-8859-1来进行编码
                props.load(is);
            }
        } finally {
            is.close();
        }
    }

从流程可以看出 application是没有指定编码的,那么是不是可以尝试修改fillProperties的源码解决,但这样改变了源代码,还得重新打包。让我们回到PropertySourcesLoader这个类,刚才发现这个类加载了两个类来进行具体的配置文件解析,PropertiesPropertySourceLoader和YamlPropertySourceLoader那么能不能让这个类拿到我们自定义的Properties的解析文件的类呢?那么重点就在于PropertySourcesLoader.loaders的构造了。找遍整个类发现只有一个地方对该属性赋值了

public PropertySourcesLoader(MutablePropertySources propertySources) {
		Assert.notNull(propertySources, "PropertySources must not be null");
		this.propertySources = propertySources;
        //唯一对loaders赋值的地方
		this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
				getClass().getClassLoader());
	}

打个断点观察先

果然就是在这里注入了两类文件解析的配置,进入方法内部发现另一个关键点

继续进入loadFactoryNames方法内部查看

发现首先会去找classpath下面的META-INF/spring.factories中找org.springframework.boot.env.PropertySourceLoader的实现类,而且这个默认配置在spring-boot-version.jar中存在,那么把配置文件拿过来在本地的配置中重写一下是不是可以呢?

原本的配置文件

 

2.2.2解决办法

1.创建一个类继承PropertiesPropertySourceLoader

public class SelfPropertySourceLoader extends PropertiesPropertySourceLoader {
    @Override
    public PropertySource<?> load(String name, Resource resource, String profile) throws IOException {
        if (profile == null) {
//            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            Properties properties = loadUseUtf8(resource);
            if (!properties.isEmpty()) {
                return new PropertiesPropertySource(name, properties);
            }
        }
        return null;
    }
    private Properties loadUseUtf8(Resource resource) throws IOException {
        Properties props = new Properties();
        InputStream is = resource.getInputStream();
        try {
            String filename = resource.getFilename();
            if (filename != null && filename.endsWith(".xml")) {
                props.loadFromXML(is);
            } else {
                props.load(new InputStreamReader(is, "utf-8"));
            }
        } finally {
            is.close();
        }
        return props;
    }
}

 

2.在resource目录下创建目录META-INF,在META-INF目录下创建文件spring.factories

内容

org.springframework.boot.env.PropertySourceLoader=org.example.SelfPropertySourceLoader

重新运行

大功告成。。。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐