一、Resource
在Java程序中,我们经常会读取配置文件、资源文件等。使用Spring容器时,我们也可以把“文件”注入进来,方便程序读取。
Spring提供了一个org.springframework.core.io.Resource(注意不是javax.annotation.Resource),它可以像String、int一样使用@Value注入:
@Componentpublic class AppService {
@Value("classpath:/logo.txt")
private Resource resource;
private String logo;
@PostConstruct
public void init() throws IOException {
try (var reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
this.logo = reader.lines().collect(Collectors.joining("\n"));
}
}
}
注入Resource最常用的方式是通过classpath,使用Maven的标准目录结构,所有资源文件放入src/main/resources即可,即类似classpath:/logo.txt表示在classpath中搜索logo.txt文件,然后,我们直接调用Resource.getInputStream()就可以获取到输入流,避免了自己搜索文件的代码。
也可以直接指定文件的路径,例如:
@Value("file:/path/to/logo.txt")private Resource resource;
在开发应用程序时,经常需要读取配置文件。最常用的配置方法是以key=value的形式写在.properties文件中。Resource来读取位于classpath下的一个app.properties文件。但是,这样仍然比较繁琐
二、@PropertySource&@Value
Spring容器还提供了一个更简单的@PropertySource来自动读取指定的配置文件。我们只需要在@Configuration配置类上再添加一个注解:
@Configuration@ComponentScan
@PropertySource("app.properties") // 表示读取classpath的app.properties
public class AppConfig {
@Value("${app.zone:Z}")
String zoneId;
@Bean
ZoneId createZoneId() {
return ZoneId.of(zoneId);
}
}
Spring容器看到@PropertySource("app.properties")注解后,自动读取这个配置文件,然后,我们使用@Value正常注入:
@Value("${app.zone:Z}")String zoneId;
注意注入的字符串语法,它的格式如下:
- "${app.zone}"表示读取key为app.zone的value,如果key不存在,启动将报错;
- "${app.zone:Z}"表示读取key为app.zone的value,但如果key不存在,就使用默认值Z。
这样一来,我们就可以根据app.zone的配置来创建ZoneId。
还可以把注入的注解写到方法参数中:
@BeanZoneId createZoneId(@Value("${app.zone:Z}") String zoneId) {
return ZoneId.of(zoneId);
}
可见,先使用@PropertySource读取配置文件,然后通过@Value以${key:defaultValue}的形式注入,可以极大地简化读取配置的麻烦。
另一种注入配置的方式是先通过一个简单的JavaBean持有所有的配置,例如,一个SmtpConfig:
@Componentpublic class SmtpConfig {
@Value("${smtp.host}")
private String host;
@Value("${smtp.port:25}")
private int port;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}
然后,在需要读取的地方,使用#{smtpConfig.host}注入:
@Componentpublic class MailService {
@Value("#{smtpConfig.host}")
private String smtpHost;
@Value("#{smtpConfig.port}")
private int smtpPort;
}
注意观察#{}这种注入语法,它和${key}不同的是,#{}表示从JavaBean读取属性。"#{smtpConfig.host}"的意思是,从名称为smtpConfig的Bean读取host属性,即调用getHost()方法。一个Class名为SmtpConfig的Bean,它在Spring容器中的默认名称就是smtpConfig,除非用@Qualifier指定了名称。
使用一个独立的JavaBean持有所有属性,然后在其他Bean中以#{bean.property}注入的好处是,多个Bean都可以引用同一个Bean的某个属性。例如,如果SmtpConfig决定从数据库中读取相关配置项,那么MailService注入的@Value("#{smtpConfig.host}")仍然可以不修改正常运行。
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.demo1"/>
<!--方法1:使用property-placeholder加载properties文件-->
<context:property-placeholder location="stu.properties"/>
<!-- 方法2:加载属性文件 -->
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="order" value="1"/>
<!-- 忽略不能读取的属性 -->
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="locations">
<list>
<value>classpath:stu.properties</value>
</list>
</property>
</bean>
</beans>