一、Resource
在Java程序中,我们经常会读取配置文件、资源文件等。使用Spring容器时,我们也可以把“文件”注入进来,方便程序读取。
Spring提供了一个org.springframework.core.io.Resource
(注意不是javax.annotation.Resource
),它可以像String
、int
一样使用@Value
注入:
@Component public 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
。
还可以把注入的注解写到方法参数中:
@Bean ZoneId createZoneId(@Value("${app.zone:Z}") String zoneId) { return ZoneId.of(zoneId); }
可见,先使用@PropertySource
读取配置文件,然后通过@Value
以${key:defaultValue}
的形式注入,可以极大地简化读取配置的麻烦。
另一种注入配置的方式是先通过一个简单的JavaBean持有所有的配置,例如,一个SmtpConfig
:
@Component public 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}
注入:
@Component public 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>