AbstractApplicationContext是ApplicationContext的抽象实现类,其中最重要的是refresh()方法,它定义了容器在加载配置文件之后的各项处理过程。java
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. prepareRefresh(); // (1)初始化BeanFactory ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. postProcessBeanFactory(beanFactory); // (2)调用工厂后处理器 invokeBeanFactoryPostProcessors(beanFactory); // (3)注册Bean后处理器 registerBeanPostProcessors(beanFactory); // (4)初始化消息源 initMessageSource(); // (5)初始化应用上下文事件广播器 initApplicationEventMulticaster(); // (6)初始化其余特殊Bean,由具体子类实现 onRefresh(); // (7)注册事件监听器 registerListeners(); // (8)初始化全部单实例的Bean(Lazy加载的除外) finishBeanFactoryInitialization(beanFactory); // (9)完成刷新并发布容器刷新事件 finishRefresh(); } catch (BeansException ex) { // Destroy already created singletons to avoid dangling resources. destroyBeans(); // Reset 'active' flag. cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... resetCommonCaches(); } } }
下图描述了Spring容器从加载配置文件到建立一个Bean的完整流程:mysql
Spring中的组件按照所承担的角色能够划分为两类:web
org.springframework.beans.factory.config.BeanDefinition
是配置文件<bean>元素标签在容器中的内部表示,是与<bean>一一对应的。org.springframework.beans.factory.support.InstantiationStrategy
负责根据BeanDefinition对象建立一个Bean实例。咱们在配置文件中配置的都是字面值,若是把它们转换成对应数据类型(如double、int)的值或对象呢?spring
任何实现了java.beans.PropertyEditor
接口的类都是属性编辑器,其主要功能就是将外部的设置值转换成JVM内部的对应类型。sql
PropertyEditor是属性编辑器的接口,它规定了将外部设置值转换为内部JavaBean属性值的接口方法,是内部属性值和外部设置值的桥梁。编程
BeanInfo主要描述了JavaBean的哪些属性能够编辑及对应的属性编辑器。BeanInfo和JavaBean的对应关系经过两者命名规范肯定:对应JavaBean的BeanInfo的命名规范应该是<Bean>BeanInfo
,如Car对应的BeanInfo为CarBeanInfo。数组
JavaBean规范提供了一个默认的属性编辑器PropertyEditorManager,保存一些常见类型的属性编辑器。缓存
Spring为常见的属性类型提供了默认的属性编辑器PropertyEditorRegistrySupport,里边有多个用于保存属性编辑器的Map类型变量,键为属性类型,值为对应的属性编辑器实例。常见的类型以下所示。并发
类 别 | 说 明 |
---|---|
基本数据类型 | 如:boolean、byte、short、int等; |
基本数据类型封装类 | 如:Long、Character、Integer等; |
两个基本数据类型的数组 | char[]和byte[]; |
大数类 | BigDecimal和BigInteger |
集合类 | 为5种类型的集合类Collection、Set、SortedSet、List和SortedMap提供了编辑器 |
资源类 | 用于访问外部资源的8个常见类Class、Class[]、File、InputStream、Locale、Properties、Resource[]和URL |
Step1:咱们能够经过扩展java.beans.PropertyEditorSupport类,并覆盖其中的setAsText()方法便可自定义属性编辑器。app
class KFCWaitress { private KFCCombo kfcCombo; // getters & setters } class KFCCombo { private String burger; private String drink; // getters & setters } /** * KFCCombo的Editor */ class KFCComboEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { // 将字面值转换为属性类型对象 String[] textArr = text.split(","); KFCCombo kfcCombo = new KFCCombo(); kfcCombo.setBurger(textArr[0]); kfcCombo.setDrink(textArr[1]); // 调用父类的setValue()方法设置转换后的属性对象 setValue(kfcCombo); } }
Step2:若是使用BeanFactory须要手动调用registerCustomEditor(class requiredType, PropertyEditor propertyEditor)
方法注册自定义的属性编辑器;若是使用ApplicationContext,只须要在配置文件中经过CustomEditorConfigurer注册便可。
<!-- (1)配置自动注册属性编辑器的CustomEditorConfigurer --> <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer"> <property name="customEditors"> <map> <!-- (2)属性编辑器对应的属性类型 --> <entry key="com.ankeetc.spring.KFCCombo" value="com.ankeetc.spring.KFCComboEditor"/> </map> </property> </bean> <bean id="myWaitress" class="com.ankeetc.spring.KFCWaitress"> <!-- (3)该属性将使用(2)处的属性编辑器完成属性填充操做 --> <property name="kfcCombo" value="Zinger Burger,Mirinda"/> </bean>
在(3)处,直接经过一个字符串配置一个Bean。BeanWrapper在设置KFCCombo类型的属性时,将会检索自定义属性编辑器的注册表,若是发现有KFCCombo属性类型有对应的属性编辑器时,就会使用该方法的setAsText()
转换该对象。
Spring提供了一个PropertyPlaceholderConfigurer来引用外部属性文件,它实现了BeanFactoryPostProcessor接口,所以也是一个Bean工厂后处理器。
经过PropertyPlaceholderConfigurer并引入属性文件,实现使用属性名来引用属性值。
<!-- 建立PropertyPlaceholderConfigurer的Bean并引入属性文件 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location" value="classpath:application.properties"/> <property name="fileEncoding" value="utf-8"/> </bean> <!-- 也可使用这种方式引用属性文件 --> <context:property-placeholder location="classpath:application.properties" file-encoding="utf-8"/> <!-- 经过属性名引用属性值 --> <bean id="myCat" class="com.ankeetc.spring.Cat"> <property name="name" value="${name}"/> </bean>
${属性名}
引用属性文件中的属性项,其中${
为默认的占位符前缀,能够根据须要改成其余的前缀符。}
。在使用基于注解配置Bean时,能够经过@Value注解为Bean的成员变量或方法入参自动注入容器中已有的属性,也可使用@Value注入字面值。
public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(“com.ankeetc.spring); System.out.println(applicationContext.getBean(Cat.class).getName()); } } @Configuration class Config { @Bean public PropertyPlaceholderConfigurer configurer() { PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer(); configurer.setLocation(new PathMatchingResourcePatternResolver().getResource("classpath:application.properties")); configurer.setFileEncoding("utf-8"); return configurer; } } @Component class Cat { @Value("${name}") private String name; public String getName() { return name; } }
若是属性是敏感的,通常不容许使用明文形式保存,此时须要对属性进行加密.PropertyPlaceHolderConfigurer继承自PropertyResourceConfigurer类,后者有几个有用的protected方法(方法默认是空的即不会转换),用于在属性使用以前对属性列表中的属性进行转换。
void convertProperties(Properties props)
:属性文件的全部属性值都封装在props中,覆盖该方法,能够对全部的属性值进行转换处理。String convertProperty(String propertyName, String propertyValue)
:在加载属性文件并读取文件中的每一个属性时,都会调用此方法进行转换处理。String convertPropertyValue(String originalValue)
:和上一个方法相似,只不过没有传入属性名。public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.ankeetc.spring"); // userName没有被改变 System.out.println(applicationContext.getBean(DataSource.class).getUserName()); // password值被改变 System.out.println(applicationContext.getBean(DataSource.class).getPassword()); } } @Component class DataSource { @Value("${userName}") private String userName; @Value("${password}") private String password; public String getUserName() { return userName; } public String getPassword() { return password; } } @Configuration class Config { @Bean public EncryptPropertyPlaceholderConfigurer encryptPropertyPlaceholderConfigurer() { EncryptPropertyPlaceholderConfigurer configurer = new EncryptPropertyPlaceholderConfigurer(); configurer.setLocation(new PathMatchingResourcePatternResolver().getResource("classpath:application.properties")); configurer.setFileEncoding("utf-8"); return configurer; } } class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { @Override protected String convertProperty(String propertyName, String propertyValue) { if ("password".equals(propertyName)) { // 在此过滤并实现相关的揭秘逻辑 return "Decrypt" + propertyValue; } else { return propertyValue; } } }
${}
来实现属性之间的相互引用\
将属性分为多行dbName=myDatabase url=jdbc:mysql://localhost:3306/${dbName}
在XML配置文件中,可使用#{beanName.propName}
的方式引用其余Bean的属性值。
public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:beans.xml"); System.out.println(applicationContext.getBean(Application.class).getDatabaseName()); System.out.println(applicationContext.getBean(Application.class).getDatabasePassword()); } } class DatabaseConfig { private String userName; private String password; // getters & setters } class Application { private String databaseName; private String databasePassword; // getters & setters }
<bean id="databaseConfig" class="com.ankeetc.spring.DatabaseConfig"> <property name="userName" value="lucas"/> <property name="password" value="123456"/> </bean> <!--经过#{databaseConfig.propName}的方式引用databaseConfig的属性值--> <bean id="applicationConfig" class="com.ankeetc.spring.Application"> <property name="databaseName" value="#{databaseConfig.userName}"/> <property name="databasePassword" value="#{databaseConfig.password}"/> </bean>
使用@Value("#{beanName.propName}")
的形式也能够引用其余类的属性值。
@Component class Application { @Value("#{databaseConfig.userName}") private String databaseName; @Value("#{databaseConfig.password}") private String databasePassword; }
国际化信息的含义是根据不一样的地区语言类型返回不一样的信息,简单来讲就是为每种语言配置一套对应的资源文件。
国际化信息也称为本地化信息,由java.util.Locale
类表示一个本地化对象。它由语言参数和国家/地区参数构成。
zh
、en
CN
、TW
、HK
、EN
、US
java.util包下的NumberFormat、DateFormat、MessageFormat都支持本地化的格式化操做,并且MessageFormat还支持占位符的格式化操做。
使用ResourceBundle能够访问不一样本地化资源文件,文件名必须按照以下格式来命名:资源名_语言代码_国家地区代码.properties
,其中语言代码和国家/地区代码是可选的。假如默认资源文件的文件名为application.properties
,则中文中国大陆的资源文件名为application_zh_CN.properties
。
public class Main { public static void main(String[] args) { // 若是找不到对应的资源文件,将会使用默认的资源文件 // getBundle是类路径的文件名,并且不带.properties后缀 ResourceBundle zhCN = ResourceBundle.getBundle("application", Locale.SIMPLIFIED_CHINESE); ResourceBundle enUs = ResourceBundle.getBundle("application", Locale.US); System.out.println(zhCN.getString("name")); System.out.println(enUs.getString("name")); } }
Spring定义了访问国际化信息的MessageSource接口,主要方法以下:
String getMessage(String code, Object[] args, String defaultMessage, Locale locale)
:code表示国际化资源中的属性名;args用于传递格式化串占位符所用的运行期参数;当在资源找不到对应属性名时,返回defaultMessage参数所指定的默认信息;locale表示本地化对象;String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException
:与上面的方法相似,只不过在找不到资源中对应的属性名时,直接抛出NoSuchMessageException异常;String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException
:MessageSourceResolvable将属性名、参数数组以及默认信息封装起来,它的功能和第一个接口方法相同。HierarchicalMessageSource
接口的做用是创建父子层级的MessageSource结构。StaticMessageSource
主要用于程序测试,它容许经过编程的方式提供国际化信息。ResourceBundleMessageSource
实现类容许经过beanName指定一个资源名(包括类路径的全限定资源名),或经过beanNames指定一组资源名。ReloadableResourceBundleMessageSource
提供了定时刷新功能,容许在不重启系统的状况下,更新资源的信息。public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); System.out.println(applicationContext.getBean("myMessageSource1", MessageSource.class).getMessage("name", null, Locale.US)); System.out.println(applicationContext.getBean("myMessageSource1", MessageSource.class).getMessage("name", null, Locale.SIMPLIFIED_CHINESE)); } }
<!--可使用basename指定资源文件的路径--> <bean id="myMessageSource1" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="application"/> </bean> <!--可使用basenames指定多组资源文件的路径--> <bean id="myMessageSource2" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>application</value> </list> </property> </bean> <!--当使用ReloadableResourceBundleMessageSource可使用cacheSeconds指定刷新周期--> <!--刷新周期默认为-1即不刷新,单位是秒--> <bean id="myMessageSource3" class="org.springframework.context.support.ReloadableResourceBundleMessageSource"> <property name="basename" value="application"/> <property name="cacheSeconds" value="100"/> </bean>
因为ApplicationContext自己也继承了MessageSource接口,因此ApplicationContext的全部实现类自己也是一个MessageSource对象,国际化信息是整个容器的公共设施。
在本章(1.1 ApplicationContext内部原理)咱们提到,在ApplicationContext会在initMessageSource()方法中,Spring经过反射机制找出bean名为messageSource
(bean名必须是messageSource)且类型为MessageSource子类的Bean,将这个Bean定义的信息资源加载为容器级的国际化信息资源。
public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); System.out.println(applicationContext.getMessage("name", null, Locale.US)); } }
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="application"/> </bean>
事件体系是观察者模式的一种具体实现方式,一共有以下几个角色:
java.util.EventObject
是Java中的事件。java.util.EventListener
是用于描述事件的接口,是一个没有任何方法的标记接口。public class Main { public static void main(String[] args) { Waitress waitress = new Waitress("田二妞"); waitress.addEventListener(new Chef("王二狗")); waitress.order("宫保鸡丁"); // 厨师[王二狗]收到服务员[田二妞]的订单,开始作[宫保鸡丁] } } // 一个餐厅的点单事件,继承了EventObject class OrderEventObject extends EventObject { private String order; public String getOrder() { return order; } public OrderEventObject(Object source, String order) { super(source); this.order = order; } } // 服务员是事件源,由她产生点单事件 class Waitress { private String name; // 服务员维护了全部在餐厅的厨师,即监听器注册表 private List<Chef> eventListenerList = new ArrayList<>(); public Waitress(String name) { this.name = name; } public String getName() { return name; } public void addEventListener(Chef chef) { eventListenerList.add(chef); } // 该方法是广播器,即把点单事件通知给注册表中的所有厨师 public void order(String order) { OrderEventObject orderEventObject = new OrderEventObject(this, order); eventListenerList.forEach(var -> var.cook(orderEventObject)); } } // 厨师是事件监听器 class Chef implements EventListener { private String name; public Chef(String name) { this.name = name; } // 监听到点单事件并做出相关反应 public void cook(EventObject o) { System.out.println(String.format("厨师[%s]收到服务员[%s]的订单,开始作[%s]", name, ((Waitress)o.getSource()).getName(), ((OrderEventObject)o).getOrder())); } }
onApplicationEvent(E event)
,该方法接受ApplicationEvent事件对象,在该方法中编写事件的响应处理逻辑。boolean supportsEventType(Class<? extends ApplicationEvent> eventType)
:指定监听器支持哪一种类型的容器事件,即它只会对该类型的事件作出响应;boolean supportsSourceType(Class<?> sourceType)
:指定监听器仅对何种事件源对象作出响应。当发生容器事件时,容器主控程序将调用事件广播器将事件通知给事件监听器注册表中的事件监听器。Spring为事件广播器提供了接口和实现类。
Spring在ApplicationContext接口的抽象实现类AbstractApplicationContext中完成了事件体系的搭建。AbstractApplicationContext拥有一个applicationEventMulticaster(应用上下文事件广播器)成员变量,它提供了容器监听器的注册表。AbstractApplicationContext在refresh()这个容器启动启动方法中经过如下3个步骤搭建了事件的基础设施:
public void refresh() throws BeansException, IllegalStateException { // (5)初始化应用上下文事件广播器 initApplicationEventMulticaster(); // (7)注册事件监听器 registerListeners(); // (9)完成刷新并发布容器刷新事件 finishRefresh(); }
假如咱们但愿容器刷新时打印一行文字,能够继承GenericApplicationListener并实现相关方法。
public class Main { public static void main(String[] args) { // new AnnotationConfigApplicationContext()会调用refresh方法,MyListener会监听到并处理 ApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.ankeetc.spring"); // stop事件不会被监听到 ((AnnotationConfigApplicationContext) applicationContext).stop(); } } @Component class MyListener implements GenericApplicationListener { // 判断是不是刷新事件 @Override public boolean supportsEventType(ResolvableType eventType) { return ResolvableType.forClass(ContextRefreshedEvent.class).equals(eventType); } @Override public boolean supportsSourceType(Class<?> sourceType) { return true; } // 在此实现监听到相关事件的处理逻辑 @Override public void onApplicationEvent(ApplicationEvent event) { System.out.println("Hello world"); } @Override public int getOrder() { return 0; } }