注入属性文件的值
按照以往的方式,我们总是直接把具体的字面量值填入代码进行字面量值的注入。如下所示:
1 @Component 2 public class Music { 3 private String musicName = null; 4 private Date publishTime = null; 5 6 @Value("Dream") 7 public void setMusicName(String musicName) { 8 this.musicName = musicName; 9 } 10 11 @Value("2022/12/22") 12 public void setPublishTime(Date publishTime) { 13 this.publishTime = publishTime; 14 } 15 16 // 省略getter方法 17 }
可以看到Music定义了三个属性。其值直接填在代码里,由@Value注解注入。直觉告诉我们,这种硬编码的注入方式并不友好。假如Bean的属性值由于某种原因需做变动,我们还得修改代码。非常麻烦,非常不好维护!要是能从某个属性文件里读取属性值之后作为字面量值进行注入,该有多好!这样,我们无需修改代码,只需修改属性文件就能达到修改Bean的属性值的目的。非常灵活,非常简单,非常方便,非常易于修改,非常易于维护!幸运的是,Spring确实提供了这样的支持。而这支持,得从Environment接口谈起。
Environment是Spring定义的一个接口,代表着当前正在运行的应用程序的环境,主要提供两种功能:一种是设置Profile;一种是能让开发人员比较方便地访问属性文件。至于何为Profile,我们将在其它章节另行介绍。现在重点关注的是与属性文件访问相关的内容。
具体而言,Spring实现了两种Environment接口:一种是StandardEnvironment,用于与Web无关的应用程序;一种是StandardServletEnvironment,用于与Web相关的应用程序。创建Spring容器时,Spring容器将会创建Environment,而Environment则会加载属性文件。因此,我们需要提供配置信息告诉Environment属性文件在哪。而这,可以通过Spring提供的@PropertySource注解指定。如下所示:
1 @Configuration 2 @ComponentScan("com.dream.controller") 3 @PropertySource("classpath:com/dream/app.properties") 4 public class ServletConfig { 5 }
我们指定的属性文件是 classpath:com/dream/app.properties ,位于com.dream包里。属性文件的内容如下:
music.name=Dream music.publishtime=2022/12/22
于是,Environment能从@PropertySource注解指定的位置加载属性文件。之后,我们需把Environment注入我们的Bean里,以使我们的Bean能用Environment获取属性文件的属性值,按照属性名的匹配情况把相应的属性值注入我们的Bean里,如下所示:
1 @Component 2 public class Music { 3 private Environment environment = null; 4 private String musicName = null; 5 private Date publishTime = null; 6 7 @Autowired 8 public Music(Environment environment) { 9 this.environment = environment; 10 this.musicName = environment.getProperty("music.name"); 11 this.publishTime = environment.getProperty("music.publishtime", Date.class); 12 } 13 14 // 省略getter方法 15 }
通过@Autowired注解,我们告诉Spring容器把Environment注入Music的构造函数里。构造函数拿到Environment之后,以属性文件的属性名作为参数调用getProperty()方法获取属性值,把属性值赋给Bean的属性,完成字面量值的注入。getProperty()方法具有四种重载,签名如下:
1.String getProperty(String key)
2.String getProperty(String key, String defaultValue)
3.T getProperty(String key, Class<T> type)
4.T getProperty(String key, Class<T> type, T defaultValue)
大家一看便知怎么调用,无需详叙。重点在于,我们还可通过往@Value注解里填入属性占位符的方式注入属性文件的值。而这,需要我们创建PropertySourcesPlaceholderConfigurer类型的Bean,让它帮助我们针对这种方式进行处理。如下所示:
1 @Configuration 2 @ComponentScan("com.dream.controller") 3 public class ServletConfig { 4 @Bean 5 public PropertySourcesPlaceholderConfigurer placeholderConfigurer( 6 ResourceLoader resourceLoader) { 7 var resourceUrl = "classpath:com/dream/app.properties"; 8 var resource = resourceLoader.getResource(resourceUrl); 9 var configurer = new PropertySourcesPlaceholderConfigurer(); 10 configurer.setLocations(resource); 11 return configurer; 12 } 13 }
可以看到先前添加的@PropertySource注解已被删除,却定义了一个具有ResourceLoader类型的参数的方法,用于创建PropertySourcesPlaceholderConfigurer类型的Bean。创建过程如下:
1.调用resourceLoader的getResource()方法获取属性文件这种资源。
2.创建PropertySourcesPlaceholderConfigurer类型的实例。
3.调用setLocations()方法,把属性文件这种资源交给PropertySourcesPlaceholderConfigurer加载。
注意:setLocations()方法的参数是 Resource... 类型的,可以指定多个代表属性文件的资源。
于是,Spring容器加载配置文件之后,PropertySourcesPlaceholderConfigurer类型的Bean就存在Spring容器里,帮助我们处理属性占位符。因此,我们还得修改Music如下:
1 @Component 2 public class Music { 3 private String musicName = null; 4 private Date publishTime = null; 5 6 @Value("${music.name}") 7 public void setMusicName(String musicName) { 8 this.musicName = musicName; 9 } 10 11 @Value("${music.publishtime}") 12 public void setPublishTime(Date publishTime) { 13 this.publishTime = publishTime; 14 } 15 16 // 省略getter方法 17 }
可以看到@Value注解注入的是属性占位符。属性占位符是这样构成的: ${属性文件里的属性名} ,也就是用 ${} 把属性文件里的属性名括起来。这样,实现了BeanFactoryPostProcessor接口的PropertySourcesPlaceholderConfigurer就能在Spring容器创建Bean之前做些事情:
1. 把由setLocations()方法指定的属性文件加载到Environment里。
2. 找出Bean的定义里的属性占位符,与Environment里的属性名进行匹配,并把匹配成功的属性占位符改成属性名对应的属性值。
于是,那些含有属性占位符的Bean的定义都被修改了,由属性占位符改成属性文件的值。之后,Spring容器根据修改之后的Bean的定义创建和装配Bean,注入的值自然是属性文件的值。
另外,如果配置文件是XML的话,可以这样配置:
1 <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer"> 2 <property name="locations" value="classpath:com/dream/app.properties"/> 3 </bean> 4 <bean class="com.dream.controller.Music"> 5 <property name="musicName" value="${music.name}" /> 6 <property name="publishTime" value="${music.publishtime}" /> 7 </bean>
为了方便配置,Spring还专门提供了<context:property-placeholder>元素,用于创建PropertySourcesPlaceholderConfigurer类型的Bean。因此,配置信息也能改成这样:
1 <context:property-placeholder locations ="classpath:com/dream/app.properties"/> 2 3 <bean class="com.dream.controller.Music"> 4 <property name="musicName" value="${music.name}" /> 5 <property name="publishTime" value="${music.publishtime}" /> 6 </bean>
还有,Environment除了能够加载属性文件,也能按照优先级从上到下加载这些参数:
1.Servlet初始化参数
2.Servlet上下文初始化参数
3.JNDI环境变量
4.JVM系统属性(也就是JVM的命令行参数)
5.操作系统环境变量
这意味着我们也能通过属性占位符注入这些参数的值。大家可以试试,这里不作过多介绍。
于是,关于怎样通过属性占位符注入属性文件的值,我们已经理清楚了。下章该谈谈Spring表达式语言,看看能用强大的Spring表达式语言注入怎样的字面量值。欢迎大家继续阅读,谢谢大家!