Spring官网阅读(十一) ApplicationContext 详细介绍(上)
在前面的文章中,我们已经完成了官网关于IOC内容的核心部分。包括容器的概念,Spring创建Bean的模型BeanDefinition的介绍。容器的扩展点(BeanFactoryPostProcessor,BeanPostProcessor,FactoryBean),以及重要的Bean的生命周期等。接下来大概花三篇文章对官网的第一大节的其它内容的学习,之所以要这么做,是笔者粗读了一遍源码之后,再读一遍官网,发现源码中很多难点和细节点都在官网中介绍了,所以这里先跟大家一起把官网中的内容都过一遍,也是为了更好的进入源码阶段的学习。
本文主要涉及到官网中的1.13、1.15.1.16小节中的内容以及第二大节的内容
ApplicationContext的继承关系
从上图中可以发现,ApplicationContext接口继承了很多接口,这些接口我们可以分为五类:
MessageSource 主要用于国际化
ApplicationEventPublisher 提供了事件发布功能
EnvironmentCapable 可以用于获取容器当前的运行环境
ResourceLoader 主要用于加载资源文件
BeanFactory 负责创建,配置,管理Bean,IOC功能的实现主要就依赖于该接口的实现。
关于这些接口的具体功能的介绍在后文会介绍,当前我们需要知道最重要的一点就是ApplicationContext继承了BeanFactory 接口,也就是说它具有BeanFactory 的所有功能。
2、ApplicationContext的功能
Spring中的国际化(MessageSource )
国际化是什么?
应用程序运行时,可根据客户端操作系统的国家/地区,语言的不同显示不同的界面,比如客户端OS的语言环境为大陆的简体中文,程序就显示为简体中文,客户端OS的语言环境为美国–英语,程序就显示为美式英语。OS的语言环境可以在控制面板中手动设置,国际化的英文单词是Internationnalization,单词较长,通常简称 i18n,i是第一个字母,18表示中间省略了18个字母,N是最后一个字母。
假设我们正在开发一个支持多国语言的Web应用程序要求系统能够根据客户端的系统的语言类型返回对应的界面:英文的操作系统返回英文界面,而中文的操作系统返回中文的界面—这便是典型的 i18n国际化问题。对于有国际化要求的应用系统。我们不能简单的采用被编码的方式编写界面信息,报错信息等内容,而必须为这些国际化的信息处理。简单的说,就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户语言选择适合的资源文件。
Java中的国际化
国际化信息也成为本地信息,一般需要两个条件才可以确定一个特定类型的本地化信息,他们分别是“语言类型”和“国家/地区的类型”。如中文本地化消息既有中国大陆地区的中文,又有中国台湾,中国香港地区的中文,还有新加坡地区的中文。
本地化对象(Locale)
java通过java.util.Locale类表示一个本地化对象,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象。
Locale locale = new Locale("zh","cn");//中文,中国
Locale locale = new Locale("en","us");//英文,美国
Locale locale = new Locale("zh");//中文,不确定国家
Locale locale = Locale.CHINA//中文,中国
Locale locale = new Locale("zh","cn");//中文
在持有一个Locale对象后,我们需要将同一个文字或者数字根据不通的地区/语言格式化成不通的表现形式,所以这里我们还需要一个格式化的操作,JDK给我们提供了以下几个常见的类用于国际化
NumberFormat:可处理数字,百分数,货币等,下面以货币为例:
public class Test01 {
public static void main(String[] args) {
//通过语言地区确定一个Locale对象
//中国,中文
Locale chinaLocale = new Locale("zh","cn");
//美国,英文
Locale usLocale = new Locale("en","us");
//获取货币格式化对象
NumberFormat chinaCurrencyFormat = NumberFormat.getCurrencyInstance(chinaLocale);
NumberFormat usLocaleCurrencyFormat = NumberFormat.getCurrencyInstance(usLocale);
//中文,中国下的货币表现形式
String chinaCurrency = chinaCurrencyFormat.format(99.9);
//美国,英文下的货币表现形式
String usCurrency = usLocaleCurrencyFormat.format(99.9);
System.out.println(chinaCurrency);
System.out.println(usCurrency);
}
}
MessageFormat:在numberFormat和DateFormat的基础上提供了强大的占位符字符串的格式化功能,它支持事件、货币、数字以及对象属性的格式化操作
1、简单的占位符替换
public class Test02 {
public static void main(String[] args) {
Locale chinaLocale = new Locale("zh","cn");
String str1 = "{0},你好|你于{1}在农业银行存入{2}.";
MessageFormat messageFormat = new MessageFormat(str1,chinaLocale);
Object[] o = {"小红", new Date(),99.99};
System.out.println(messageFormat.format(o));
}
}
//小红,你好|你于21-5-25 下午9:15在农业银行存入99.99.
指定格式化类型跟格式化样式的占位符替换
public class Test03 {
public static void main(String[] args) {
String str1 = "{0},你好!你于{1, date, long}在农业银行存入{2,number,currency}。";
MessageFormat messageFormat = new MessageFormat(str1, Locale.CHINA);
Object[]o={"小红",new Date(),1313};
System.out.println(messageFormat.format(o));
}
}
//小红,你好!你于2021年5月25日在农业银行存入¥1,313.00。
在上面的例子中,0,1,2代表的是占位符的索引,从0开始计数。date,number为格式化的类型。long,currency为格式化样式。
formatType:格式化类型,取值范围如下:
number:调用NumberFormat进行格式化
date: 调用dateFormat进行格式化
time:调用DateFormat进行格式化
choice:调用ChoiceFormat进行格式化
FormatStyle: 设置使用的格式化样式
short
medium
long
full
integer
currency
percent
SubformatPatten(子格式模式,形如#。##)
对于具体的使用方法就不多赘述了,大家可以自行百度
资源文件的加载
在实现国际化的过程中,由于我们的用户界面信息,报错信息等内容不能采用硬编码的方式,所以在不同的区域/语言环境下能进行不同的提示,我们需要为不同的环境提供不同的资源文件,同时需要遵循一定的规范。
命名规范:资源名_语言代码_国/地区代码.properties
举一个例子:假设资源名为content,语言为英文,国家为美国,则与其对应的本地化资源文件命名为content_en_US.properties.
下面以IDEA为例,创建资源文件并加载读取
1、创建资源文件,在Resource目录下,创建一个Bundle
此时会在Resource目录下生成如下目录结构
在这两个配置文件中,我分别添加了两段配置
public class Test04 {
public static void main(String[] args) {
ResourceBundle usResourceBundle = ResourceBundle.getBundle("i18n", Locale.US);
System.out.println(usResourceBundle.getString("name"));
System.out.println(usResourceBundle.getString("age"));
ResourceBundle chinaResourceBundle = ResourceBundle.getBundle("i18n", Locale.US);
System.out.println(chinaResourceBundle.getString("name"));
System.out.println(chinaResourceBundle.getString("age"));
}
}
输出
Spring中的MessageSource
在聊完了Java中的国际化后,我们回归主题ApplicationContext接口继承了MessageSource接口,MessageSource接口又提供了国际化的功能,所以ApplicationContext也具有国际化的功能,接下来我们着重看看MessageSource接口。
接口:
public interface MessageSource {
//code表示国际化资源中的属性名,args用于传递格式化串占位符所用的运行期参数
//当在资源找不到时,返回defaultMessage参数所指定的默认信息
//local表示本地化对象
@Nullable
String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale);
//与上面的方法类似,只是在找不到资源时直接抛出异常
String getMessage(String code, @Nullable Object[] args, Locale locale) throws NoSuchMessageException;
//将属性名,参数数组等信息封装起来,它的功能和第一个方法相同
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
}
UML类图
我们依次分析下各个类的作用
public interface HierarchicalMessageSource extends MessageSource {
//为当前的MessageSource设置父MessageSource
void setParentMessageSource(@Nullable MessageSource parent);
//获取当前MessageSource的父MessageSource
@Nullable
MessageSource getParentMessageSource();
}
1、MessageSourceSupport,这个类的作用类似于我们之前介绍的MessageFormat,主要提供了对消息的格式化功能。从这个继承关系中我们也能看出,Spring在设计时将消息的获取以及格式化进行了分隔。而在我们实际使用到具体的实现类时,又将功能做了聚合。
2、DelegatingMessageSource,将所有获取消息的请求委托给父类查找,如果父类没有就报错
3、AbstractMessageSource,实现了HierarchicalMessageSource,提供了对消息的处理方式,方便子类具体的消息类型实现邦定的策略。
4、AbstractResourceBasedMessageSource提供了对Bundle的处理方式
5、ResourceBundleMessageSource,基于JDK的ResourceBuncle实现,根据名称加载Bundle
6、ReloadableResourceBundleMessageSource,提供了定时刷新功能,允许在不重启系统的情况下,更新资源信息。
7、StaticMessageSource,主要用于程序测试
Spring中的简单使用
我们直接取官网中的Demo,先看官网上的一段说明:
从上文中我们可以提取以下几点信息:
1、Spring容器在启动时会自动查找一个名称定义的messageSource的Bean(同时需要实现MessageSource接口),如果找到了,那么所以获取信息的请求都会由这个类处理,如果在当前容器中没有找到的话,会在父容器中继续查找。
2、如果没有找到,那么Spring会自己new一个DelegatingMessageSource对象,并用这个对象处理消息。
基于上面的结论,我们可以做如下配置:
<beans>
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>format</value>
<value>exceptions</value>
<value>windows</value>
</list>
</property>
</bean>
</beans>
同时配置下面的properties
# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
测试代码
public class Main10 {
public static void main(String[] args) {
MessageSource resources = new ClassPathXmlApplicationContext("spring.xml");
String message = resources.getMessage("message", null, "Default", Locale.ENGLISH);
System.out.println(message);
String message2 = resources.getMessage("argument.required",
new Object [] {"userDao"}, "Required", Locale.UK);
System.out.println(message2);
}
}
输出:
Alligators rock!
The userDao argument is required.
Spring中的环境 (Environment)
这小结对应官网中的1.13小结
在前面的ApplicationContext的继承关系中我们知道ApplicationContext这个接口继承了一个EnvironmentCapable接口,而这个接口的定义非常简单,如下
public interface EnvironmentCapable {
Environment getEnvironment();
}
可以看到它只是简单的提供了一个获取Enviromenf对象的方法,那么这个Environment对象是做什么的呢?
1、什么是环境(Envirniomenf)?
它其实代表了当前Spring容器的运行环境,比如JDK环境,系统环境;每个环境都有自己的配置数据,如System.getProperties()可以拿到JDK环境数据,System.getenv()可以拿到系统变量,ServletContext.getInitParameter()可以拿到Servlet环境配置数据。Spring抽象了一个Envirniomenf来表示Spring应用程序环境配置,它整合了各种各样的外部环境,并做统一访问的方法。
2、接口定义
public interface Environment extends PropertyResolver {
//获得当前激活的profiles的名称
String[] getActiveProfiles();
//获取默认的profile的名称
String[] getDefaultProfiles();
@Deprecated
boolean acceptsProfiles(String... profiles);
//判断指定的profiles是否激活
boolean acceptsProfiles(Profiles profiles);
}
public interface PropertyResolver {
//当前的环境中是否包含这个属性
boolean containsProperty(String key);
//获取属性值,如果找不到返回null
@Nullable
String getProperty(String key);
//获取属性值,如果找不到返回默认值
String getProperty(String key, String defaultValue);
//获取指定类型的属性值,找不到返回null
@Nullable
<T> T getProperty(String key, Class<T> targetType);
//获取指定类型的属性值,找不到返回默认值
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
//获取属性值找不到抛出异常IllegalStateException
String getRequiredProperty(String key) throws IllegalStateException;
//获取指定类型的属性值,找不到抛出异常IllegalStateException
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
//替换文本中的占位符($(key))的值,找不到不解析
String resolvePlaceholders(String text);
//替换文本中的占位符($(key))的值,找不到抛出异常IllegalArgumentException
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
我们可以看到,Environment 接口继承自PropertyResolver,而Environment 接口自身主要提供了对profile的操作,PropertyResolver接口主要提供了对当前运行环境中的属性的操作,如果我们去查看它的一些方法的实现可以发现,对属性的操作大部分依赖于PropertySource。
所以学习Environment 接口前,我们首先要了解Profile以及PropertySource
3、Profile
我们先看官网上对profile的定义
从这段话中我们可以知道
1、Profile是一组逻辑上的Bean的定义
2、只有这个Profile被激活时,这组Bean才会被注册到容器中
3、我们既可以通过注解的方式加入profile也可以通过XML的方式
4、Environment主要决定哪个Profile被激活,在没有Profile被激活时使用哪个作为默认的Profile
注解方式(@Profile)
1、简单使用
@Component
@Profile("dev")
public class StudentService {
public StudentService(){
System.out.println("studentService in dev");
}
}
@Component
@Profile("prd")
public class TeacherService {
public TeacherService(){
System.out.println("teacherService in prd");
}
}
public class Main11 {
public static void main(String[] args) {
AnnotationConfigApplicationContext aa = new AnnotationConfigApplicationContext();
aa.register(AppConfig.class);
aa.getEnvironment().setDefaultProfiles("prd");
aa.refresh();//输出 teacherService in prd
// aa.getEnvironment().setActiveProfiles("dev");
// aa.refresh();//输出 studentService in dev
}
}
上面的例子中我们分别给两个组件(StudentService,TeacherService)配置了不通的Profile,可以看到我们利用Environment激活不同的Profile时,可以分别创建不同的两个类。
在实际生产环境中,我们往往会将“prd”,“dev”这种代表代表环境的标签放到系统环境变量中,这样依赖于不同系统的统一环境变量,我们就可以将应用程序运行在不同的peofile下。
2、结合逻辑运算符使用
有时间我们某一组件可能同时要运行在多个profile下,这个时候逻辑运算符就派上用场了,我们可以通过如下运算符,对profile进行逻辑运算
!非,只有这个profile不被激活才能生效
& 两个profile同时激活才能生效
| 只要其中一个profile激活就能生效
比如上面的例子中我们可以新增两个类,如下
@Component
@Profile("!prd")
public class ProfileService {
public ProfileService(){
System.out.println("profileService in !prd");
}
}
@Component
@Profile("dev & qa")
public class OtherService {
public OtherService(){
System.out.println("otherService in dev & qa");
}
}
public class Main11 {
public static void main(String[] args) {
AnnotationConfigApplicationContext aa = new AnnotationConfigApplicationContext();
aa.register(AppConfig.class);
aa.getEnvironment().setActiveProfiles("dev","qa");
aa.refresh();//输出 otherService in dev & qa profileService in !prd
// aa.getEnvironment().setDefaultProfiles("prd");
// aa.refresh();//输出 teacherService in prd
// aa.getEnvironment().setActiveProfiles("dev");
// aa.refresh();//输出 studentService in dev
}
}
为了编码的语义,有时候我们会将Profile封装成不同的注解,如下
@Configuration
public class AppConfig {
@Bean("dataSource")
@Profile("development")
public DataSource standaloneDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.HSQL)
.addScript("classpath:com/bank/config/sql/schema.sql")
.addScript("classpath:com/bank/config/sql/test-data.sql")
.build();
}
@Bean("dataSource")
@Profile("production")
public DataSource jndiDataSource() throws Exception {
Context ctx = new InitialContext();
return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}
3、PropertySource
通过我们的Environment 对象,除了能操作profile对象之外,通过之前的继承结构我们知道,他还能进行一些关于属性的操作。而这些操作是建立在Spring本身对运行环境中的一些属性文件的抽象而来的,抽象而成的结果就是PropertySource。
public abstract class PropertySource<T> {
protected final String name;//属性源名称
protected final T source;//属性源(比如来自map就是一个map对象)
public PropertySource(String name, T source) {
Assert.hasText(name, "Property source name must contain at least one character");
Assert.notNull(source, "Property source must not be null");
this.name = name;
this.source = source;
}
@SuppressWarnings("unchecked")
public PropertySource(String name) {
this(name, (T) new Object());
}
//获取属性源的名字
public String getName() {
return this.name;
}
//获取属性源
public T getSource() {
return this.source;
}
//是否包含某个属性
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
//得到属性名对应的属性值
@Nullable
public abstract Object getProperty(String name);
}
除了上面的几个方法外,PropertySource还定义了几个静态的内部类
从上图可以看到,基于PropertySource的子类可以分为两类,一类是StubPropertySource,一类是ComparisonPropertySource,这两类都是基于特殊的功能设计的
1、StubPropertySource:这个类主要起到类似一个占位符的作用,例如一个基于servletContext的PropertySource只能等待,直到servletContext对象对这个PropertySource所在的上下文可用,在这种情况下需要用到StubPropertySource来预设置PropertySource的位置和顺序,之后在上下文刷新时期,再用一个ServletContextPropertySourc
来进行替换
2、ComparisonPropertySource,这个类设计的目的就是为了比较,除了hashCode()
,equals()
,toString()
方法能被调用外,其余方法被调用时均会抛出异常
而PropertySource的另外一些子类都是继承自EnumerablePropertySource,我们来看看EnumerablePropertySource这个类对PropertySource做了哪些补充。其定义如下:
public abstract class EnumerablePropertySource<T> extends PropertySource<T> {
public EnumerablePropertySource(String name, T source) {
super(name, source);
}
protected EnumerablePropertySource(String name) {
super(name);
}
//覆盖了这个方法
@Override
public boolean containsProperty(String name) {
return ObjectUtils.containsElement(getPropertyNames(), name);
}
//新增了这个方法
public abstract String[] getPropertyNames();
}
这个类跟我们PropertySource
的区别在于:
1、复写了containsProperty()这个方法
2、新增了一个getPropertyNames()方法
并且我们可以看到,在containsProperty这个方法里面调用了getPropertyNames,这么做的理由是什么呢?为什么它不直接使用父类的containsProperty这个方法要复写一个呢?我们对比一下父类的实现
/**
* Return whether this {@code PropertySource} contains the given name.
* <p>This implementation simply checks for a {@code null} return value
* from {@link #getProperty(String)}. Subclasses may wish to implement
* a more efficient algorithm if possible.
* @param name the property name to find
*/
//是否包含某个属性
public boolean containsProperty(String name) {
return (getProperty(name) != null);
}
结合这个类上面的一段javadoc
/**
* A {@link PropertySource} implementation capable of interrogating its
* underlying source object to enumerate all possible property name/value
* pairs. Exposes the {@link #getPropertyNames()} method to allow callers
* to introspect available properties without having to access the underlying
* source object. This also facilitates a more efficient implementation of
* {@link #containsProperty(String)}, in that it can call {@link #getPropertyNames()}
* and iterate through the returned array rather than attempting a call to
* {@link #getProperty(String)} which may be more expensive. Implementations may
* consider caching the result of {@link #getPropertyNames()} to fully exploit this
* performance opportunity.
*
* <p>Most framework-provided {@code PropertySource} implementations are enumerable;
* a counter-example would be {@code JndiPropertySource} where, due to the
* nature of JNDI it is not possible to determine all possible property names at
* any given time; rather it is only possible to try to access a property
* (via {@link #getProperty(String)}) in order to evaluate whether it is present
* or not.
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.1
* @param <T> the source type
*/
Spring设计这个类的主要目的是为了,让调用者可以不访问其中的source对象但是能判断这个PropertySource
中是否包含了指定的key,所以它多提供了一个
getPropertyNames,同时这段javadoc指出,子类应该缓存这个方法的返回值去尽可能的压榨性能。
接下来,我们分别看一下其它的实现类。
1、MapPropertySource
MapPropertySource的source来自一个map,这个类结构很简单,这里不说。
用法如下:
public class Test08 {
public static void main(String[] args) {
Map<String,Object> map=new HashMap<>();
map.put("name","wang");
map.put("age",23);
MapPropertySource source_1=new MapPropertySource("person",map);
System.out.println(source_1.getProperty("name"));//wang
System.out.println(source_1.getProperty("age"));//23
System.out.println(source_1.getName());//person
System.out.println(source_1.containsProperty("class"));//false
}
}
2、ResourcePropertySource
source是一个profile对象,继承自MapPropertySource。与MapPropertySource这个类的用法相同。
3、ServletContextPropertySource
source是servletContext对象
4、SystemEnvironmentPropertySource
继承自MapPropertySource,source来源于系统环境
5、CompositePropertySource
内部可以保存多个PropertySource
private final Set<PropertySource<?>> propertySources = new LinkedHashSet<>();
取值时依次遍历这些propertySources
PropertySources
在阅读PropertySource的源码过程中有一段javadoc需要注意下
* <p>{@code PropertySource} objects are not typically used in isolation, but rather
* through a {@link PropertySources} object, which aggregates property sources and in
* conjunction with a {@link PropertyResolver} implementation that can perform
* precedence-based searches across the set of {@code PropertySources}.
{@code PropertySource}对象通常不会单独使用,而是使用
*通过一个{@link PropertySources}对象来聚合属性源和
*连接一个{@link PropertyResolver}实现可以执行
*跨{@code PropertySources}的基于优先级的搜索
接口定义
public interface PropertySources extends Iterable<PropertySource<?>> {
/**
* Return a sequential {@link Stream} containing the property sources.
* @since 5.1
*/
default Stream<PropertySource<?>> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* Return whether a property source with the given name is contained.
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
boolean contains(String name);
/**
* Return the property source with the given name, {@code null} if not found.
* @param name the {@linkplain PropertySource#getName() name of the property source} to find
*/
@Nullable
PropertySource<?> get(String name);
}
继承自Iterable,所以具有迭代功能。
唯一子类MutablePropertySources
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
这个类中持有了一个CopyOnWriteArrayList,其他的方法都是增删properties
6、PropertyResolver
接口定义
public interface PropertyResolver {
//当前的环境中是否包含这个属性
boolean containsProperty(String key);
//获取属性值,如果找不到返回null
@Nullable
String getProperty(String key);
//获取属性值,如果找不到返回默认值
String getProperty(String key, String defaultValue);
//获取指定类型的属性值,找不到返回null
@Nullable
<T> T getProperty(String key, Class<T> targetType);
//获取指定类型的属性值,找不到返回默认值
<T> T getProperty(String key, Class<T> targetType, T defaultValue);
//获取属性值找不到抛出异常IllegalStateException
String getRequiredProperty(String key) throws IllegalStateException;
//获取指定类型的属性值,找不到抛出异常IllegalStateException
<T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
//替换文本中的占位符($(key))的值,找不到不解析
String resolvePlaceholders(String text);
//替换文本中的占位符($(key))的值,找不到抛出异常IllegalArgumentException
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;
}
UML类图
它的实现类主要有两种:
各种Resolver,主要是PropertySourcesPropertyResolver
各种Environment
PropertySourcesPropertyResolver使用示例
public class Test09 {
public static void main(String[] args) {
MutablePropertySources sources = new MutablePropertySources();
sources.addLast(new MapPropertySource("map",new HashMap<String, Object>(){
{
put("name", "zhang");
put("age",18);
}
}));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(sources);
System.out.println(propertyResolver.containsProperty("name"));
System.out.println(propertyResolver.getProperty("age"));
System.out.println(propertyResolver.resolvePlaceholders("my name is ${name},I am ${age}"));
}
}
关于Environment
实现主要分为两种
StandardEnvironment
,标准环境,普通Java应用时使用,会自动注册System.getProperties()
和System.getenv()
到环境- StandardServletEnvironment:标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet)、ServletContext及有选择性的注册JNDI实例到环境
w HashMap<String, Object>(){
{
put(“name”, “zhang”);
put(“age”,18);
}
}));
PropertyResolver propertyResolver = new PropertySourcesPropertyResolver(sources);
System.out.println(propertyResolver.containsProperty(“name”));
System.out.println(propertyResolver.getProperty(“age”));
System.out.println(propertyResolver.resolvePlaceholders(“my name is ${name},I am ${age}”));
}
}
关于`Environment`实现主要分为两种
1. `StandardEnvironment`,标准环境,普通Java应用时使用,会自动注册`System.getProperties()`和 `System.getenv()`到环境
2. StandardServletEnvironment:标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet)、ServletContext及有选择性的注册JNDI实例到环境