首页 > 编程语言 >Spring源码分析(十一)ApplicationContext详细介绍(上)

Spring源码分析(十一)ApplicationContext详细介绍(上)

时间:2023-09-06 14:06:49浏览次数:46  
标签:ApplicationContext String Locale Spring System 源码 println new public

在前面的文章中,已经完成了官网关于IOC内容核心的部分。包括容器的概念,Spring创建bean的模型BeanDefinition的介绍容器的扩展点(BeanFactoryProcessor,FactoryBean,BeanPostProcessor)以及最重要的bean的生命周期等。接下来大概还有花三篇文章完成对官网中第一大节的其他内容,之所以要这么做,是我自己粗读了一遍源码后,再读一遍官网,发现源码中的很多细节以及难点都在官网中介绍了。所以这里还是先把官网的内容过一遍,也是为了更好的进入源码学习阶段。

本文主要涉及到官网中的1.13,1.15,1.16小节中的内容以及第二大节的内容

ApplicationContext

  1. ApplicationContext的继承关系

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source

从上图中可以发现,ApplicationContext接口继承了很多接口,这些接口我们可以将其分为5类:

  • MessageSource,主要用于国际化
  • ApplicationEventPublisher,提供了事件发布功能
  • EnvironmentCapable,可以获取容器当前运行的环境
  • ResourceLoader,主要用于加载资源文件
  • BeanFactory,负责配置,创建,管理bean,IOC功能的实现主要就依赖于该接口子类实现

关于这些接口的具体功能的介绍在后文会介绍,当前我们需要知道最重要的一点就是ApplicationContext继承了BeanFactory接口,也就是或它具有BeanFactory所有的功能。

  1. ApplicationContext的功能

Spring中的国际化(MessageSource)

国际化是什么?

应用程序运行时,可根据客户端操作系统的国家/地区,语言的不同而显示不同的界面。比如客户端OS的语言环境为大陆的简体中文,程序就显示为简体中文,对应的客户端OS的语言环境为美国,程序就显示为英文。OS的语言环境可在控制面板手动设置。国际化的英文单词是Internationalization,简称为i18n,i是第一个字母,18表示中间忽略了18个字母,N是最后一个字母。

假设我们在开发一个支持多国语言的web应该程序,要求系统能够根据客户端的系统语言类型返回对应的界面:英文的操作系统返回英文界面,而中文的操作系统则返回中文界面——这便是典型的i18n国际化问题 。对于有国际化要求的应该程序,我们不能简单的采用硬编码的方式编写用户界面信息,报错信息等内容,而必须为这些需要国际化的信息进行特殊处理。简单来说,就是为每种语言提供一套相应的资源文件,并以规范命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。

Java中的国际化

国际化信息也称为本地化信息,一般需要两个条件才可以确定一个特定类型的本地化信息,它们分别是“语言类型”和“国家/地区的类型”。如中文本地化信息既有中国大陆地区的中文,又有中国台湾、中国香港地区的中文,还有新加坡地区的中文。

【部分国际化代码】:

ar_sa 阿拉伯语(沙特阿拉伯)
ar_iq 阿拉伯语(伊拉克)
eu 巴斯克语
bg 保加利亚语
zh_tw 中文(中国台湾)
zh_cn 中文(中华人民共和国)
zh_hk 中文(中国香港特别行政区)
zh_sg 中文(新加坡)
hr 克罗地亚语
en 英语
en_us 英语(美国)
en_gb 英语(英国)
en_au 英语(澳大利亚)
en_ca 英语(加拿大)

本地化对象(Locale)

Java通过java.util.Locale类表示一个本地化对象,允许通过语言参数和国家/地区参数创建一个确定的本地化对象

Locale locale=new Locale("zh","cn");//中文,中国
Locale locale2=new Locale("en","us");//英文,美国
Locale locale3=new Locale("zh");//中文--不指定国家
Locale locale4=Locale.CHINA;//中文,中国
Locale locale5=Locale.CHINESE;//中文

在持有一个Locale对象后,我们需要将同一个文字或者数字根据不同的地区/语言格式化成不同的表现形式,所以这里我们还需要一个格式化的操作,JDK给我们提供了几个常见的类用于国际化格式

NumberFormat:可以处理数字,百分数,货币等。下面以货币为例:

public static void main(String[] args) {
   // 1.通过语言跟地区确定一个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);  // 输出 ¥99.90
   // 美国,英文下的货币表现形式
   String usCurrency = usLocaleCurrencyFormat.format(99.9); // 输出 $99.90
   System.out.println(chinaCurrency);
   System.out.println(usCurrency);
}

格式化对象

DateFormat:通过DateFormat#getDateInstance(int style,Locale locale)方法按本地化的方式对日期进行格式化操作。该方法第一个入参为时间样式,第二个入参为本地化对象

public static void main(String[] args) {
   // 1.通过语言跟地区确定一个Locale对象
   // 中国,中文
   Locale chinaLocale = new Locale("zh", "cn");
   // 美国,英文
   Locale usLocale = new Locale("en", "us");
   DateFormat chinaDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,chinaLocale);
   DateFormat usDateFormat = DateFormat.getDateInstance(DateFormat.YEAR_FIELD,usLocale);
   System.out.println(chinaDateFormat.format(new Date())); // 输出 2020年1月15日
   System.out.println(usDateFormat.format(new Date()));    // 输出 January 15, 2020
}

MessageFormat:在NumberFormat和DateFormat的基础上提供了强大的占位符字符串的格式化功能,它支持时间、货币、数字以及对象属性的格式化操作

  1. 简单的占位符替换
public static void main(String[] args) {
   // 1.通过语言跟地区确定一个Locale对象
   // 中国,中文
   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));
   // 输出:小红,你好!你于20-1-15 下午4:05在农业银行存入99.99。
}
  1. 指定格式化类型和格式化样式的占位符替换
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));
   // 输出:小红,你好!你于2020年1月15日在农业银行存入¥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

SubformatPattern (子格式模式,形如#.##)

对于具体的使用方法就不多赘述了,大家可以自行百度。

资源文件的加载

在实现国际化的过程中,由于我们的用户界面信息、报错信息等内容都不能采用硬编码的方式,所以为了在不同的区域/语言环境下能进行不同的显示,我们需要为不同的环境提供不同的资源文件,同时需要遵循一定的规范。

  • 命名规范:资源名_语言代码_国/地区代码.properties

举一个例子:假设资源名为content,语言为英文,国家为美国,则与其对应的本地化资源文件命名为content_en_US.properties。

下面以IDEA为例,创建资源文件并加载读取

  1. 创建资源文件,在Resource目录下,创建一个Bundle

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_02

  1. 添加需要兼容的区域/语言,我这里就添加一个英语/美国,给这个Bundle命名为i18n,名字随意(本人在日本企业,所以用的日文操作系统)

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_03

  1. 此时会在Resource目录下生成如下的目录结构

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_04

在两个配置文件中,我分别添加了两段配置:

【i18n.properties】:

#小明(资源文件对文件内容有严格的要求:只能包含ASCII字符,所以必须将非ASCII字符的内容转换为Unicode代码的表示方式)
name=\u5c0f\u660e
#他十九岁了
age=19

【i18n_en_US.properties】:

name=Xiaoming
age=19

示例代码:

public static void main(String[] args) {
   // i18n要跟我们之前创建的Bundle的名称一致
   // Locale.US指定了我们要拿这个Bundle下的哪个区域/语言对于的资源文件,这里获取到的是i18n_en_US.properties这个配置文件
   ResourceBundle usResourceBundle = ResourceBundle.getBundle("i18n", Locale.US);
   System.out.println(usResourceBundle.getString("name")); // 输出Xiaoming
   System.out.println(usResourceBundle.getString("age"));
   ResourceBundle chinaResourceBundle = ResourceBundle.getBundle("i18n");
   System.out.println(chinaResourceBundle.getString("name"));  // 输出小明
   System.out.println(chinaResourceBundle.getString("age"));
}

Spring中的MessageSource

在聊完了JAVA中的国际化后,我们回归主题,ApplicationContext接口继承了MessageSource接口,MessageSource接口又提供了国际化的功能,所以ApplicationContext也具有国际化的功能(禁止套娃)。接下来我们着重看看MessageSource这个接口。

接口的定义

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_05

UML类图

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_06

我们一次分析下各个类的作用

  1. HierarchicalMessageSource,该接口提供了设置获取父容器的方法,用于构建MessageSource体系的父子层级结构。其方法定义如下:

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_07

  1. MessageSourceSupport,这个类的作用类似于之前介绍的MessageFormat,主要提供了对消息的格式化功能。从这个基础关系中也能看出,Spring在设计时将消息的获取以及各式化进行了分隔。而在我们实际使用到具体的实现类时,又将功能做了聚合。
  2. DelegatingMessageSource,将所有获取消息的请求委托给父类查找,如果父类没有就报错
  3. AbstractMessageSource,实现了HierarchicalMessageSource,提供了对消息的通用处理方式,方便子类对具体的消息类型实现特定的策略。
  4. AbstractResourceBasedMessageSource,提供了对Bundle的处理方式
  5. ResourceBundleMessageSource,提供了定时刷新功能,运行在不重启系统的情况下,更新资源的信息。
  6. StaticMessageSource,主要用于程序测试。

Spring中的简单使用

这里直接取官网中的Demo,先看官网上的一段说明:

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_08

从上文中,可以得出几点信息:

  1. Spring容器在启动时会自动查找一个名称定义的messageSource的bean(同时需要实现MessageSource接口),如果找到了,那么所有获取信息的请求都会由这个类处理。如果在当前容器没有找到的话,会在父容器中继续查找。
  2. 如果没有找到,Spring会自己new一个DelegatingMessageSource对象,并用这个对象处理消息

基于上面的结论,做如下配置:

<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>

同时配置下面三个properties文件:

# in format.properties
message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.

测试代码:

public static void main(String[] args) {
   MessageSource resources = new ClassPathXmlApplicationContext("application.xml");
   String message1 = resources.getMessage("message", null, "Default", null);
   String message2 = resources.getMessage("argument.required",
         new Object [] {"userDao"}, "Required", null);
   System.out.println(message1); // 输出 Alligators rock!
   System.out.println(message2); // 输出 The userDao argument is required.
}

同时Spring对资源的加载也遵循我们在Java国际化中提到的规范,可以将上面例子中的exception.properties,更名为exception_en_GB.properties。

// 可以看出这种方式跟我们使用JAVA直接加载国际化资源没有太大差别
public static void main(final String[] args) {
   MessageSource resources = new ClassPathXmlApplicationContext("beans.xml");
   String message = resources.getMessage("argument.required",
         new Object [] {"userDao"}, "Required", Locale.UK);
   System.out.println(message);
}
  1. Spring中的环境(Environment)

这小节内容对应官网中的1.13小节

在前面的ApplicationContext的继承关系中我们知道ApplicationContext这个接口继承了一个EnvironmentCapable接口,而这个接口的定义非常简单,如下

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_09

可以看到它只是简单的提供了一个获取Environment对象的方法,那么这个Environment对象是做什么的呢?

  1. 什么是环境(Environment)?

它其实代表了当前Spring容器的运行环境,比如JDK环境,系统环境。每个环境都有自己的配置数据,如System.getProperties()可以拿到JDK环境数据,System.getenv()可以拿到系统变量,ServletContext.getInitParameter()可以拿到Servlet环境配置数据。Spring抽象了一个Environment来表示Spring应用程序环境配置,整合了各种各样的外部环境,并且提供统一访问的方法。

  1. 接口定义

Spring源码分析(十一)ApplicationContext详细介绍(上)_System_10

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_11

可以看到,Environment接口继承了PropertyResolver,而Environment接口自身主要提供了对Profile的操作,PropertyResolver接口主要提供了对当前运行环境中属性的操作,如果我们去查看它的一些方法的实现可以发现,对属性的操作大都依赖于PropertySource。

  1. Profile

先看官网上的介绍:

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_12

从上面这段话可以知道

  1. Profile是一组逻辑上的bean的定义
  2. 只有这个Profile被激活时,这组bean才会被注册到容器中
  3. 我们即可以通过注解的方式来讲bean加入到指定的Profile中,也可以通过XML的形式
  4. Environment主要决定哪个Profile要被激活,在没有激活的Profile时要使用哪个作为默认的Profile

注解方式(@Profile)

  1. 简单使用
@Component
@Profile("dev")
public class IndexService {
   public IndexService() {
      System.out.println("indexService in dev");
   }
}


@Component
@Profile("prd")
public class LifeCycleService {
   public LifeCycleService() {
      System.out.println("LifeCycleService in prd");
   }
}


public static void main(String[] args) {


    AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();


    ac.register(ProfileConfig.class);


    //ac.getEnvironment().setActiveProfiles("prd");


    //ac.refresh();        // 输出 LifeCycleService in prd


    ac.getEnvironment().setActiveProfiles("dev");


    ac.refresh();          // 输出 IndexService in
}

在上面的例子中,给两个组件(LifeCycleService , IndexService)配置了不同的profile,可以看到当我们利用gEnvironment激活不同的profile时,可以分别只创建不同的两个类。

在实际生产环境中,我们往往会将"prd","dev"这种代表环境的标签放在系统环境变量中,这样依赖于不同的系统的同一环境变量,我们就可以将应用程序运行在不同的profile下。

  1. 结合逻辑运算符使用

有时候我们某一组件可能同时要运行在多个profile下,这个时候逻辑运算符就派上用场了,我们可以通过如下三种运行符,对profile进行逻辑原酸

  • !:非,只有这个profile不被激活才能生效
  • &:两个profile同时激活才能生效
  • |:只要某一个profile激活就能生效

比如在上面的例子中,可以新增两个类,如下:

public static void main(final String[] args) {
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
      ac.register(Appconfig.class);
//    ac.getEnvironment().setActiveProfiles("prd");
//    ac.refresh(); // 输出 LifeCycleService in prd
//    ac.getEnvironment().setActiveProfiles("dev");
//    ac.refresh(); //输出 IndexService in dev
      ac.getEnvironment().setActiveProfiles("dev","qa");
      ac.refresh(); //输出 IndexService in dev 
      // ProfileService in !prd
      //TestService in dev &* qa
      
   }

为了编码的语义,有时候我们会将不同的profile封装成不同的注解,如下:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Profile("production")
public @interface Production {
}

有时候可能要将多个bean同时基于某个profile下,这个时候每个bean添加一个@Profile注解显得麻烦,如果我们采用@Bean方式申明的bean,可以直接在配置类上添加@Profile注解,如下(这里直接取官网中的例子,不做验证了):

@Configuration
public class Appconfig {


   @Bean("dataSource")
   @Profile("development")
   public DataSource standaloneDataSource() {
      return (DataSource) 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");
   }
}
  1. 注意一种特殊的场景

如果我们对使用了@Bean注解的方式进行了重载,那么要求所有重载的方法都在同一个@Profile下,负责@Profile的语义会被覆盖,什么意思呢?看下面的demo:

public class A {
   public A() {
      System.out.println("independent A");
   }
   public A(B b) {
      System.out.println("dependent A with B");
   }
}


public class B {
}


@Configuration
@ComponentScan("com.xxl.test")
public class Appconfig {
   @Bean
   @Profile("dev")
   public A a() {
      return new A();
   }


   @Bean
   @Profile("prd")
   public A a(B b) {
      return new A(b);
   }


   @Bean
   @Profile("prd | dev")
   public B b() {
      return new B();
   }
}


public class Test {
   public static void main(String[] args) {
      AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
      ac.register(Appconfig.class);
      ac.getEnvironment().setActiveProfiles("dev");
      ac.refresh(); // 输出:dependent A with B
  }
}

我们命名激活的是Dev这个profile,为什么创建的时候用的带参的构造函数呢?这是因为Spring在创建bean时,方法的优先级高于profile,前提是方法的参数在Spring容器内(在上面例子中,如果我们将B的profile限定为Dev,那么创建的A就会是同管控参构造创建的)。这里暂且不多说,大家知道这种场景存在即可,在后面的源码分析在介绍,这里涉及到Spring对创建bean的方法的推断(即包括构造函数也包括factoryMethod)。

XML方式

<!--在beans标签中指定profile属性即可-->
<beans profile="development"
      xmlns="http://www.springframework.org/schema/beans"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xmlns:jdbc="http://www.springframework.org/schema/jdbc"
      xsi:schemaLocation="...">


   <jdbc:embedded-database id="dataSource">
      <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
      <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
   </jdbc:embedded-database>
</beans>

XML方式不多赘述,大家有需要可以自行研究

  1. PropertySource

通过我们的Environment对象,除了能操作profile对象之外,通过之前的继承结构我们知道,它还能进行一些关于属性的操作,而这些操作是建立在Spring本身对运行环境中的一些属性文件的抽象而来的,抽象而成的结果就是PropertySource。

接口定义:

public abstract class PropertySource<T> {
   protected final  String name;//属性源名称
   protected final T source;//属性源(比如来自Map,那就是一个Map对象)
   public String getName();  //获取属性源的名字  
   public T getSource();        //获取属性源  
   public boolean containsProperty(String name);  //是否包含某个属性  
   public abstract Object getProperty(String name);   //得到属性名对应的属性值   
   // 返回一个ComparisonPropertySource对象
   public static PropertySource<?> named(String name) {
      return new ComparisonPropertySource(name);
   }
}

除了上面定义的这些方法外,PropertySource中还定义了几个静态内部类,我们在下面的UML类图分析时进行介绍

UML类图

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_13

从上图可以看到,基于PropertySource子类主要分为两类,一类是StubPropertySource,另一类是EnumerablePropertySource,而StubPropertySource这一类都是什么于PropertySource中的静态内部类。这两个类主要是为了完成一些特殊的功能而设计的。

  1. StubPropertySource:这个类主要起到类似一个占位符的作用,例如,一个基于ServletContext的PropertySource必须等待,直到ServletContext对象对这个PropertySource所在的上下文可用。这种情况下,需要用到StubPropertySource来预设这个PropertySource的位置和顺序,之后再上下文刷新时期,再用一个ServletContextPropertySourc来替换
  2. ComparisonPropertySource:这个类设计的目的是为了进行比较,除了hashCode(),equals(),toString()方法能被调用外,其余方法被调用均会抛出异常

而PropertySource的另外一些子类,都是继承自EnumerablePropertySource的,我们先来看EnumerablePropertySourcez这个类对父类PropertySource进行了那些补充:

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_14

这个类和PropertySource的区别在于:

  1. 重写了containsProperty方法
  2. 新增了getPropertyNames方法

并且可以看到,在containsProperty这个方法中调用了getPropertyNames(),这么做的理由是什么呢?为什么不直接使用父类的containsProperty方法而要自己复写一个?对比下父类的实现:

Spring源码分析(十一)ApplicationContext详细介绍(上)_System_15

结合这个类上的一段Java doc:

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_16

Spring设计这个类的主要目的是为了让调用者不访问其中的Source对象但是能判断这个PropertySource中是否包含了指定的key,所以它多提供了一个getPropertyNames,同时这段Java doc 还指出,子类是实现应该考虑去缓存getPropertyNames这个方法的返回值去尽可能的压榨性能。

接下来,分别看看它的各个实现类:

  • MapPropertySource

MapPropertySource的source来自于一个map,这个类结构很简单。用法如下:

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
}
  • ResourcePropertySource

source是一个Properties对象,继承自MapPropertySource,与MapPropertySource用法相同

  • ServletContestPropertySource

source为ServletContext对象

  • SystemEnvironmentPropertySource

继承自MapPropertySource,它的source也是一个map,但来源于系统环境

  • CompositePropertySource

内部可以保存多个PropertySource

private final Set<PropertySource<?>> propertySources = new LinkedHashSet<PropertySource<?>>();

取值时依次遍历这些PropertySource

PropertySources

我们在阅读PropertySources源码时,会发现其中有一段这样的Java doc解释,其中提到了

Spring源码分析(十一)ApplicationContext详细介绍(上)_资源文件_17

也就是说,PropertySources通常都不会单独使用,而是通过PropertySources对象。

  • 接口定义

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_18

个接口由于继承了Iterable接口,所以它的子类也具备了迭代能力

  • 唯一子类
public class MutablePropertySources implements PropertySources {
   private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
......
}

这个类最大的特点就是,持有一个保存PropertySources的CopyOnWriteArrayList集合,并且它其余提供的方法,都是在往集合中增删PropertySources。

  1. PropertyResolver

在之前的Environment的接口定义中 我们知道,Environment接口继承了PropertyResolver接口,接下来我们再来关注下这个接口的定义

接口定义

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_19

UML类图

它的洗类主要有两种:

  1. 各种Resolver:主要是PropertySourcesPropertyResolver
  2. 各种Environment

Spring源码分析(十一)ApplicationContext详细介绍(上)_Source_20

PropertySourcesPropertyResolver使用示例:

public static void main(String[] args) {


   MutablePropertySources sources = new MutablePropertySources();
   sources.addLast(new MapPropertySource("map", new HashMap<String, Object>() {
      {
         put("name", "wang");
         put("age", 12);
      }
   }));//向MutablePropertySources添加一个MapPropertySource


   PropertyResolver resolver = new PropertySourcesPropertyResolver(sources);
   System.out.println(resolver.containsProperty("name"));//输出 true
   System.out.println(resolver.getProperty("age"));//输出 12
   System.out.println(resolver.resolvePlaceholders("My name is ${name} .I am ${age}."));
  • 关于Environment实现主要分为两种
  1. StandardEnvironment,标准环境,普通Java应用时会使用,会自动注册System.getProperties()和 System.getenv()到环境
  2. StandardServletEnvironment,标准Servlet环境,其继承了StandardEnvironment,Web应用时使用,除了StandardEnvironment外,会自动注册ServletConfig(DispatcherServlet),ServletContest及有选择性的注册JNDI实例到环境

总结

这篇文章,主要写了ApplicationContext的部分知识,首先我们知道ApplicationContext继承了5类接口,正犹豫继承了这5类接口,所以它具有了以下这些功能:

  • MessageSource,主要用于国际化
  • AplicationEventPublisher,提供了事件发布功能
  • EnvironmentCapable,可以获取容器当前运行的环境
  • RurceLoader,主要用于加载资源文件
  • BeanFactory,负责配置,创建,管理bean,IOC功能的实现主要依赖于该接口子类实现

在上文,我们分析学习了国际化,以及Spring中环境的抽象(Environment)。对于国际化而言,首先我们要知道国际化到底是什么?简而言之,国际化就是为每种语言提供一套相应的资源文件,并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件。其次,我们也一起了解了java中的国际化,最后学习了Spring对java国际化的一些封装,也就是MessageSource接口

对于Spring中环境的抽象(Environment)这块内容比较多,主要知道Environment 了两个功能

  • 为程序运行提供不同的剖面,即profile
  • 操作程序运行中的属性资源

整个Environment体系可以用下图表示

Spring源码分析(十一)ApplicationContext详细介绍(上)_System_21

对上图的解释:

  1. Environment可以激活不同的profile而为程序选择不同的剖面,一个profile其实就是一组Spring中的bean。
  2. Environment继承了PropertyResolver,从而可以操作程序运行时的属性资源,而PropertyResolver的实现依赖于PropertySource,同时PropertySource一般不会独立使用,而是被封装进一个PropertySources对象中。

标签:ApplicationContext,String,Locale,Spring,System,源码,println,new,public
From: https://blog.51cto.com/u_15668812/7386234

相关文章

  • 二级医院信息系统源码(HIS) Angular+Nginx+ Java
    基层医疗云HIS作为基于云计算的B/S构架的HIS系统,为基层医疗机构提供了标准化的、信息化的、可共享的医疗信息管理系统,可有效进行医疗数据共享与交换,解决数据重复采集及信息孤岛等问题,实现对基层医疗数据的分析和挖掘,为基层卫生机构提供科学合理的业务管理服务。可实现“云部署”,即......
  • spring boot logback日志显示时间差8小时
    参考:https://blog.csdn.net/u014453475/article/details/100579856官方文档:Thesecondparameterspecifiesatimezone.Forexample,the'%date{HH:mm:ss.SSS,Australia/Perth}wouldprintthetimeinthetimezoneofPerth,Australia,theworld'smostiso......
  • SpringBoot启动报数组下标越界
    问题描述:启动读取配置文件时报错关键字:ERRORorg.springframework.boot.SpringApplication-Applicationrunfailedjava.lang.ArrayIndexOutOfBoundsException:-1ConnectedtothetargetVM,address:'127.0.0.1:58753',transport:'socket'2023-09-0611:09......
  • Spring学习第一步
    @TOC<hrstyle="border:solid;width:100px;height:1px;"color=#000000size=1">1、Spring概述1.1简介Spring:春天--->给软件行业带来了春天2002年,RodJahnson首次推出了Spring框架雏形interface21框架。2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1......
  • android源码分析1--updater(l上)
    一install.cpp中调用updater:constchar*binary="/tmp/update_binary";constchar**args=(constchar**)malloc(sizeof(char*)*5);args[0]=binary;args[1]=EXPAND(RECOVERY_API_VERSION);//definedinAndroid.mkchar*temp=......
  • aosp源码分析 5.0 BlockImageUpdateFn
    block_image_update("/dev/block/bootdevice/by-name/system",package_extract_file("system.transfer.list"),"system.new.dat","system.patch.dat");//args://-blockdevice(orfile)tomodifyin-place......
  • 直播系统源码部署,高效文件管理与传输的FTP协议
    引言: 在直播系统源码部署的过程中,开发协议是支持直播系统源码功能技术搭建成功并发挥作用的关键之一,在直播系统源码的众多协议中,有一个协议可以帮助直播系统源码部署完成后用户进行媒体文件的上传、下载、管理等操作,这个协议就是FTP协议,本文就将具体介绍直播系统源码的FTP协议......
  • SpringBoot整合Redis
    SpringBoot整合Redis整合springcache导入依赖<!--引入redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>......
  • Spring Boot中自动装配机制的原理
    SpringBoot中自动装配机制的原理1.自动装配,简单来说就是自动把第三方组件的Bean装载到SpringIOC容器里面,不需要开发人员再去写Bean的装配配置,2.在SpringBoot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。3.@SpringBootApplication是一个复合注......
  • 007-SpringBoot+Mybatis+Sqlite框架搭建
    1,配置文件(application.yaml)server:port:6695spring:datasource:url:jdbc:sqlite:D:/examtolearn.dbusername:password:driver-class-name:org.sqlite.JDBCmybatis:mapper-locations:classpath:mapper/*.xmlconfiguration:log-......