文章目录
软件系统在生命周期的不同阶段(比如
开发调试
、
测试
、
生产
等),应该有着不同的行为模式,软件系统从其运行环境中获取信息,来识别其所处的阶段,然后呈现不同的行为逻辑。
配置
很重要,不同的阶段,利益攸关者不同,配置
的需求也不一样。
1、Enviroment
所谓的环境
,其本质上是一组信息,对于软件系统来说,这类信息由外到内
单向传递,用来影响或改变软件的行为模式。
环境
相关的最重要、最核心的两个概念是:信息源
和 使用者
,前者表明信息是从何而来,后者表明信息是为谁所用。在软件系统中,使用者
必然是程序,对于 Spring 应用来说,就是 spring bean
。无论是自定义的软件,还是基于框架(比如 spring)的软件,在设计 环境信息
相关的模块(后面称其为 配置模块 )
时,都必然要围绕着这两个核心概念展开。
配置模块
的核心工作是解析和适配,获取信息源的信息,将其解析为 配置对象
,并建立与 功能对象
之间的关系。
Spring 应用的运行环境由 Environment 对象来表示,在设计上,环境
分为两个层面:profiles
和 properties
。
profile
是 spring bean 的逻辑分组,当 bean 所在的 profile
状态为 active
时才会被注册到容器(ioc container)中,可以选择使用 xml
或者 注解
的方式将 spring beans 分配到不同的 profile
中。
在 Spring boot 应用中,可以通过 spring.profiles.active=test,game,sunday
属性来配置激活的 profile,没有被 @Profile
注解的 bean,属于 default profile
。
2、@Profile
@Profile 注解可以应用在下列场合:
- 应用在
@Component
,@Configuration
注释的 Java Class 上; - 作为
meta-annotation
,构建新的Annotation
; - 应用在
@Bean
注解的方法上;
如果在 @Configuration
类上使用 @Profile
,那么将适用于其所有的 @Bean
方法和 @Import
注解,除非单独为它们添加特定的 @Profile
。
profile
的名称可以为字符串或者是 profile
表达式,例如 p1 & p2
。@Profile({"p1", "p2"})
表示,当 p1
或 p2
至少有一个处于激活状态时,其标识的 bean
才会被注册到环境中。
在 xml
文件中,通过 <beans profile="p1,p2">
的方式来定义 profile。
3、PropertySource
接口 ConfigurableEnvironment
提供了设置 profile
和操作 property sources
的方法,允许 配置方
来配置环境信息。
应用环境可以关联多个 PropertySource
,这些 PropertySource
可以增删改:
1、添加新的 PropertySource
:
ConfigurableEnvironment environment = new StandardEnvironment();
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, String> myMap = new HashMap<>();
myMap.put("xyz", "myValue");
propertySources.addFirst(new MapPropertySource("MY_MAP", myMap));
2、删除默认的系统属性的 PropertySource
MutablePropertySources propertySources = environment.getPropertySources();
propertySources.remove(StandardEnvironment.SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME)
3、测试阶段,模拟系统环境:
MutablePropertySources propertySources = environment.getPropertySources();
MockPropertySource mockEnvVars = new MockPropertySource().withProperty("xyz", "myValue");
propertySources.replace(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, mockEnvVars);
对于 Spring 应用来说,要保证 ApplicationContext 的 refresh 方法调用前完成所有 PropertySource
相关的操作。
Spring 提供了多种类型的 PropertySource 实现,可以按需选择。
4、@PropertySource
@PropertySource
注解提供了一种 声明式的
添加 PropertySource
的机制,例如:
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
可以通过 @PropertySource
把配置文件中的属性设置到 Environment 对象中。
@PropertySource
路径中的 ${...}
占位符信息,比如 @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
,会从已经配置的属性信息中自动解析,如果 my.placeholder
在系统属性(System properties),或者环境变量(environment variables)中定义了,那么相应的占位符会被正确解析,如果没有定义,则使用 :
后面的默认值,这里是 default/path
。
当一个属性在多个 PropertySource
中定义时,最后一个被解析的 PropertySource
中的值有效。
例如有两份配置文件 application.properties
分别在用户目录和 classpath 中:
@Configuration
@PropertySource("classpath:/conf/application.properties")
public class ConfigA { }
@Configuration
@PropertySource("file:${user.dir}/application.properties")
public class ConfigB { }
那么,到底哪个文件中的参数生效,取决于两个配置类的加载顺序。
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(ConfigA.class);
ctx.register(ConfigB.class);
ctx.refresh();
上例中,ConfigB 中的配置生效。
5、@Configuration
在 Spring 应用中,使用 @Configuration
来建立 配置信息
与 bean
之间的关系,两者分别对应了 环境
与 业务逻辑
。
1、被 @Configuration
注解的类,可以通过 AnnotationConfigApplicationContext
类中的 register
方法来注册:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.register(AppConfig.class);
ctx.refresh();
MyBean myBean = ctx.getBean(MyBean.class);
2、也可以通过 xml
文件来配置:
<beans>
<context:annotation-config/>
<bean class="com.acme.AppConfig"/>
</beans>
可以 @Configuration
+ @ImportResource(locations={"classpath:beans.xml"})
的形式来导入 xml 中配置的 bean
3、应用 Environment 中的信息:
@Configuration
public class AppConfig {
@Autowired Environment env;
@Bean
public MyBean myBean() {
MyBean myBean = new MyBean();
myBean.setName(env.getProperty("bean.name"));
return myBean;
}
}
4、配置属性源:PropertySource
@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig {
@Inject Environment env;
@Bean
public MyBean myBean() {
return new MyBean(env.getProperty("bean.name"));
}
}
5、注入属性:
@Configuration
@PropertySource("classpath:/com/acme/app.properties")
public class AppConfig {
@Value("${bean.name}") String beanName;
@Bean
public MyBean myBean() {
return new MyBean(beanName);
}
}
6、使用 @Impoert
引用其他配置类:
@Configuration
public class DatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return DataSource
}
}
@Configuration
@Import(DatabaseConfig.class)
public class AppConfig {
private final DatabaseConfig dataConfig;
public AppConfig(DatabaseConfig dataConfig) {
this.dataConfig = dataConfig;
}
@Bean
public MyBean myBean() {
// reference the dataSource() bean method
return new MyBean(dataConfig.dataSource());
}
}
在调用 new AnnotationConfigApplicationContext(AppConfig.class)
初始化 AppConfig.class
时, DatabaseConfig.class
也会被初始化到 Context 中。
7、与 @Profile
注解结合使用:
@Profile("development")
@Configuration
public class EmbeddedDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return embedded DataSource
}
}
@Profile("production")
@Configuration
public class ProductionDatabaseConfig {
@Bean
public DataSource dataSource() {
// instantiate, configure and return production DataSource
}
}
8、使用 @ImportResource
导入资源:
@Configuration
@ImportResource("classpath:/com/acme/database-config.xml")
public class AppConfig {
@Inject DataSource dataSource; // from XML
@Bean
public MyBean myBean() {
// inject the XML-defined dataSource bean
return new MyBean(this.dataSource);
}
}
9、嵌嵌套的 @Configuration 类:
@Configuration
public class AppConfig {
@Inject DataSource dataSource;
@Bean
public MyBean myBean() {
return new MyBean(dataSource);
}
@Configuration
static class DatabaseConfig {
@Bean
DataSource dataSource() {
return new EmbeddedDatabaseBuilder().build();
}
}
}
10、懒加载模式: @Lazy
默认情况下,@Bean
方法会在容器初始化阶段立即实例化,@Lazy
注解标识的 @Bean
,可以延迟实例化。
6、Resource
JDK 提供的访问资源的类(如 java.net.URL, File 等),并不能很好地满足各种底层资源的访问需求,比如缺少从 类路径
或者 WEB 容器上下文
中获取资源的操作类。
Spring 设计了一个 Resource 接口,它为应用提供了更强大的访问底层资源的能力。
public interface Resource extends InputStreamSource {
boolean exists();
boolean isOpen();
URL getURL() throws IOException;
File getFile() throws IOException;
Resource createRelative(String relativePath) throws IOException;
String getFilename();
String getDescription();
}
Resource 主要实现类:
- ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示;
- FileSystemResource:文件系统资源,资源以文件系统路径的方式表示,如
D:/conf/bean.xml
; - ServletContextResource:为访问 Web 容器上下文中的资源而设计的类,负责以相对于 Web 应用根目录的路径加载资源,支持以流和 URL 的方式访问,在 War 解包的情况下,也可以通过 File 的方式访问,该类还可以直接从 Jar 包中访问资源;
- URLResource:Url 封装了 java.net.URL,它使用户能够访问任何可以通过 URL 表示的资源,如文件系统的资源、HTTP 资源、FTP 资源等;
Spring 可以将配置信息放置在任何地方(如数据库、LDAP 中),只要可以通过 Resource 接口返回配置信息就可以了。
String filePath = "D:/home/spring-demo/WebRoot/WEB-INF/classes/info.txt"
Resource fileResource = new FileSystemResource(filePath);
Resource classPathResource = new ClassPathResource("info.txt");// 通过 System.getProperty("java.class.path") 可获取 class path 信息;
Resource servletContextResource = new ServletContextResource(servletContext, "/WEB-INF/classes/info.txt")
可以通过 createRelative(String relativePath)
在对应 Resource 资源的相对路径上创建文件。
对于远程服务器(Web 服务器或 FTP 服务器)的文件资源,用户可以 方便地通过 UrlResource 进行访问。
EncodedResource
资源加载时默认采用系统编码读取资源内容,如果资源文件采用特殊的编码格式,那么可以通过 EncodedResource 对资源进行编码,以保证资源内容操作的正确性。
Resource resource = new ClassPathResource("info.txt");
EncodedResource encodeResource = new EncodedResource(resource, "UTF-8");
资源文件加载
Spring 提供了一个强大加载资源的机制,可以在不显示使用 Resource 实现类的情况下,仅通过资源地址的特殊标识(资源地址表达式
)就可以加载相应的资源。
地址前缀 | 示例 | 资源类型 |
---|---|---|
classpath | classpath: info.txt | 从类路径中加载资源,资源文件可以在标准的文件系统中,也可以在 jar 或 zip 等包中 |
file | file:D:/home/spring-demo/WebRoot/WEB-INF/classes/info.txt | 使用 UrlResource 从文件目录中装载资源,可以采用相对路径,也可以采用绝对路径 |
http:// | http://www.abc.com/resource/info.txt | 从 web 服务器中加载资源 |
ftp:// | ftp://www.abc.com/resource/info.txt | 从 web 服务器中加载资源 |
classpath*:conf/*.xml
:会扫描 classpath 下所有的 jar 中出现的指定路径的文件;而 classpath:
只会在第一个加载的包的路径下查找。
Ant 风格资源地址支持 3 中匹配符:
classpath:conf/t?st.xml
:?
,匹配文件名中的一个字符;file:D:/conf/*.xml
:*
匹配文件名中任意个字符;classpath:com/**/test.xml
:**
匹配多层路径;
ResourceLoader 可以根据资源地址的名称来加载资源,不过,资源地址仅支持带资源类型前缀的表达式,不支持 Ant 风格的资源路径表达式。
public interface ResourceLoader {
Resource getResource(String location);
}
ApplicationContext 实现了 ResourceLoader 接口,因此可以通过 ApplicationContext 对象来获取资源:
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("http://myhost.com/resource/path/myTemplate.txt");
Resource template = ctx.getResource("file:///some/resource/path/myTemplate.txt");
当路径信息中没有资源前缀时,会根据 ApplicationContext
的实现(比如 ClassPathXmlApplicationContext,FileSystemXmlApplicationContext
)来推断。
任何实现了 ResourceLoaderAware 接口的 Bean,都会被注入一个 ResourceLoader 实例,这个实例就是当前的 ApplicationContext 实例。当然也可以通过注解的形式来注入一个 ResourceLoader 属性。
ResourcePatternResolver 扩展了 ResourceLoader 接口,定义了新的接口方法: getResources
,该方法支持带资源路径前缀及 Ant 风格的资源路径表达式。
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resources[] = resolver.getResources("classpath*:conf/*.xml");