内部机制
AbstractApplicationContext的refresh方法刻画了Spring容器启动后所执行的各项操作
//初始化BeanFactory
postProcessBeanFactory(beanFactory);
//调用工厂后处理器
//反射找到所有实现BeanFactoryPostProcessor接口的Bean,调用postProcessBeanFactory()方法
invokeBeanFactoryPostProcessors(beanFactory);
//注册Bean后处理器
//反射找到所有实现BeanPostProcessor接口的Bean,注册到容器Bean后处理器的注册表中
registerBeanPostProcessors(beanFactory);
//初始化消息源
//国际消息资源
initMessageSource();
//初始化应用上下文事件广播器
initApplicationEventMulticaster();
//初始化特殊Bean
onRefresh();
//注册事件监听
registerListeners();
//初始化所有单例的Bean
finishBeanFactoryInitialization(beanFactory);
//完成刷新并发布容器刷新事件
//上下文刷新事件
finishRefresh();
作业流程
- ResourceLoader加载Spring配置,使用Resource表示这个配置文件资源
- BeanDefinitionReader读取Resource所指向的配置文件资源,解析
<bean>
成为BeanDefinition保存在BeanDefinitionRegistry中 容器扫描BeanDefinitionRegistry中的BeanDefinition,反射找到所有实现BeanFactoryPostProcessor接口的Bean,调用后处理对BeanDefinition进行加工处理,
(1) 半成品BeanDefinition加工成成品的BeanDefinition
(2) 反射找到实现了PropertyEditor的bean,注册到Spring容器属性编辑器中(PropertyEditorRegistry)取出加工后的BeanDefinition,调用instantiationStrategy进行Bean实例化
- 使用BeanWrapper对Bean进行封装,它结合Bean 的BeanDefinition以及容器中属性编辑器,完成Bean 的属性注入
- 利用Bean后处理器(所有实现BeanPostProcessor接口的Bean)对以及完成属性设置工作的Bean进行后续加工
BeanDefinition
BeanDefinition与<bean>
一一对应,<bean>
有class、scope、lazy-init,BeanDefinition有beanClass、scope、lazyInit。
Spring通过BeanDefinition将配置文件<bean>
的信息转化为容器的内部表示,注册到BeanDefinitionRegistry中。
创建BeanDefinition步骤:
- 通过BeanDefinitionReader读取配置信息,然后xml解析成BeanDefinition(半成品,占位符没有被处理
${}
) - 利用BeanFactoryPostProcessor把半成品BeanDefinition变成成品的BeanDefinition(替换占位符为实际的值)
InstantiationStrategy
负责根据BeanDefinition进行Bean 的创建,SimplInstantiationStrategy是默认的实现类,通过构造函数、带参构造函数、工厂方法进行Bean实例化。
BeanWrapper
负责完成Bean属性填充的任务。一个BeanWrapperImpl实例内部封装了两类组件:待处理的Bean、设置Bean属性的属性编辑器。Spring从BeanDefinition中获取Bean 的propertyValue,通过属性编辑器对propertyValue进行转换得到Bean的属性。
属性编辑器
主要功能就是把外部的属性值转化为JVM内部对应的类型,就是一个类型转化器(String转int…)
BeanWrapperImpl扩展PropertyEditorRegisterSupprt,PropertyEditorRegisterSupprt默认有32个属性编辑器,用于处理一些常用的属性转换保存在defaultEditors的Map中,
用户可以自定义属性编辑器,会保存在customEditors的Map中。
默认的编辑器例子
defaultEditors.put(char.class,CharacterEditor(false));
defaultEditors.put(Character.class,CharacterEditor(true));
自定义属性编辑器
比如我们有一个User类,他需要一个Address类
public class User {
private Address address;
......
}
普通方法会使用<bean>
ref直接注入Address
<bean id="address" class="com.sunjie.Address">
<property name="province" ref="浙江"></property>
<property name="city" ref="杭州"></property>
</bean>
<bean class="com.sunjie.User">
<property name="address" ref="address"></property>
</bean>
我们这里尝试使用属性编辑器来注入
继承PropertyEditorSupport重写setAsText方法,用户设置Address属性
public class CustomAddressEditor extends PropertyEditorSupport {
public void setAsText(String text){
if(text == null || text.indexOf(",") == -1){
throw new IllegalArgumentException("设置的字符串格式不正确");
}
String[] infos = text.split(",");
Address address = new Address();
address.setProvince(infos[0]);
address.setCity(infos[1]);
setValue(address);
}
public String getAsText() {
Object value = getValue();
if(value == null){
return "";
}else{
Address address = (Address)value;
return address.getProvince()+","+address.getCity();
}
}
}
注册自定义编辑器
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.sunjie.Address"
value="com.sunjie.CustomAddressEditor"/>
</map>
</property>
</bean>
注入
<bean id="user" class="com.sunjie.User">
<property name="address" value="浙江,杭州"/>
</bean>
使用外部属性文件
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:fileEncoding="utf-8">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
PropertyPlaceholderConfigurer属性
- locations:如果一个外部配置文件使用 location,多个使用locations,类似于list
- fileEncoding:属性文件的编码格式,默认使用操作系统默认编码方式
- order:多个PropertyPlaceholderConfigurer架子啊顺序
- placeholderPrefix:占位符前缀,默认
${
- placeholderSuffix:占位符后缀,默认
}
如果外部属性文件直接定义password的明文,不安全。可以使用加密解密配置
继承PropertyPlaceholderConfigurer,里面有三个方法
- converProperties 对所有属性进行转换
- converProperty读取每个属性的时候,都会调用
- converPropertyValue读取每个属性的时候,都会调用只是没有key
这里改写converProperty,用户对指定的几个Key进行处理,
外部配置文件使用密文,加载的时候判断key是否是password,如果是就进行解密得到明文。
public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
//如果是password就进行解密
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if(propertyName.equals("password")){
String decryptValue = DESUtils.getDecryptString(propertyValue);
return decryptValue;
}else{
return propertyValue;
}
}
}
加载配置文件就使用这个类去加载
<bean class="com.sunjie.EncryptPropertyPlaceholderConfigurer"
p:fileEncoding="utf-8">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
从配置文件中引用值使用${password}
这种方式,从Bean中应用值使用#{user.password}
比如
public class SystemConfig {
private String password;
......
}
则通过以下方式获取
#{systemConfig.password}