SpringIOC容器它会以某种方式,加载配置文件中的 信息,将其解析为一个个的BeanDefinition.之后将BeanDefinition注册到容器之中。Spring IOC容器在实现的时候经过的过程可以使用如下图片表示:
分为两个主要部分:
其一 :容器启动阶段
其二:bean实例化阶段。
在容器的启动阶段主要做的是一些信息收集的过程(主要依赖于工具类BeanDefinitionReader),将收集的信息组成BeanDefinition.将BeanDefinition注册到相应的BeaneanRegistry.
Bean实例化的过程在请求方通过容器的getBean方法明确请求某个对象时候触发/隐式依赖关系调用时候也会触发该动作。此阶段做的操作主要是判断当前的请求对象是否已经被实例化过了。根据情况进行注入。当该对象实现某些回调接口。也会根据回调函数接口装配它
容器的前置处理 BeanFactoryPostProcess:
此后内容主要是对于容器实现第一阶段进行处理。Spring提供了容器扩展机制BeanFactoryPostProcess。这个机制允许我们在实例化相应对象之前对注册到容器中的BeanDefinition的存储信息进行修改。可以根据这个机制对Bean增加其它信息。修改Bean定义的某些属性值。
想自定义前置处理器需要实现BeanFactoryPostProcess
接口。当一个容器存在多种前置处理的时候,可以让前置处理器的实现类同时继承Ordered
接口。
Spring容器提供了数种现成的前置处理器,常见的如:
-
PropertyPlaceholderConfigurer
:允许在xml文件中使用占位符。将占位符代表的资源单独配置到简单的Properties文件中加载 -
PropertyOverrideConfigurer
:不同于PropertyPlaceholderConfigurer的是,该类用于处理容器中的默认值覆为新值的场景 -
CustomEditorConfigurer
:此前的两个前置处理器处理的均是BeanDefinition.通过把BeanDefinition的数据修改达到目的。CustomEditorConfigurer没有对BeanDefinition做任何变动。负责的是将后期会用到的信息注册到容器之中。例如将类型转换器注册到BeanDefinition中。供BeanDefinition将获取到的String类型参数转换为需要的类型。
装配前置处理器
装配前置处理器分为两种情况。一种是普通的BeanFactory.一种是ApplicationContext.对于ApplicationContext可以自动识别并装配前置处理器。
ApplicationContext的装配可以直接用xml文件实现:
配置文件:
propertyHolder.properties
代码:
ApplicationContext factory=new ClassPathXmlApplicationContext("simple.xml");
启动的时候自动装载前置增强bean。无须额外操作。
BeanFactory装配方式:
ConfigurableListableBeanFactory factory=new XmlBeanFactory(new ClassPathResource("simple.xml"));
PropertyPlaceholderConfigurer configurer=new PropertyPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("propertyHolder.properties"));
configurer.postProcessBeanFactory(factory);
三种前置处理器
1.PropertyPlaceholderConfigurer前置处理器
应用场景:
不想将系统管理相关的业务配置信息混杂到XML文件中的时候,可以将文件配置到properties.如此可以在当系统参数发生变幻时候,只需修改properties配置文件。强化可读性的同时,也降低了维护难度。
代码:
配置文件
${java.version}
bean包:
package com.example.demo.postprocess;
public class MyPropertyHolderBean {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public void say() {
System.out.println("username:"+username+";password:"+password);
}
}
装配测试:
ConfigurableListableBeanFactory factory=new XmlBeanFactory(new ClassPathResource("simple.xml"));
PropertyPlaceholderConfigurer configurer=new PropertyPlaceholderConfigurer();
configurer.setLocation(new ClassPathResource("propertyHolder.properties"));
configurer.postProcessBeanFactory(factory);
MyPropertyHolderBean bean=(MyPropertyHolderBean) factory.getBean("propertyHolder");
bean.say();
分析:
对于PropertyPlaceholderConfigurer前置处理器的处理分步:
- 创建xml文件装配需要使用前置增强装配的bean,使用占位符,占用value值
- 创建properties文件,键值与xml中占位符名称一致
- 通过beanFactory装载BeanDefinition
- 创建PropertyPlaceholderConfigurer对象并把beanFactory作为入参,为beanFactory增加前置增强操作,替换BeanDefinition中的展位符
PropertyPlaceholderConfigurer前置增强不止会扫描装载的properties文件还会扫描System类下的属性。
PropertyPlaceholderConfigurer提供了三种默认的装载规则:
-
SYSTEM_PROPERTIES_MODE_FALLBACK
:默认的装载规则。当properties中存在的时候使用properties中的值,不存在的时候则选择System中的值 -
SYSTEM_PROPERTIES_MODE_NEVER
:从不扫描System中的值 -
SYSTEM_PROPERTIES_MODE_OVERRIDE
:扫描系统值,当与properties中的值冲突时候,采用System中的值
当不设置的时候默认采用的是SYSTEM_PROPERTIES_MODE_FALLBACK。可以通过PropertyPlaceholderConfigurer对象的
setSystemPropertiesMode()
方法或者setSystemPropertiesModeName()
方法,修改其扫描注入的规则。
本例子采用的BeanFactory方式进行测试,Application容器方式的方式装配起来更为简单。对于装配模式的设定可以在其bean定义的XML文件中使用注入的方式处理。
举例如:
propertyHolder.properties
其余的操作同正常的使用Application一样。十分简便。
2.PropertyOverrideConfigurer前置处理器
应用场景:
可以通过PropertyOverrideConfigurer对容器中任何想处理的bean定义中的信息进行覆盖替换
。如此当容器创建bean的时候其bean的属性值就不同于xml文件的值了。
代码:
配置文件:
工具人
简单的密码
properties文件内容
overrideBean.username=\u6700\u65B0\u7684\u5DE5\u5177\u4EBA
overrideBean.password=\u6700\u65B0\u7684\u5BC6\u7801
测试用小Demo:
ConfigurableListableBeanFactory factory=new XmlBeanFactory(new ClassPathResource("simple.xml"));
PropertyOverrideConfigurer configurer=new PropertyOverrideConfigurer();
configurer.setLocation(new ClassPathResource("propertyOverride.properties"));
configurer.postProcessBeanFactory(factory);
MyPropertyOverrideBean bean=(MyPropertyOverrideBean)factory.getBean("overrideBean");
bean.say();
分析:
PropertyOverrideConfigurer的用法与PropertyPlaceholderConfigurer的使用方式类似。不同之处在于PropertyOverrideConfigurer的properties文件中的键值是bean名加其内部属性。而PropertyPlaceholderConfigurer的键值是其占位符的名。其次两者值注入的方式一种是替换占位符,一种是覆盖旧有的值。同时PropertyOverrideConfigurer不存在扫描System的情况。
Spring还可以支持对其加密的properties进行处理。PropertyOverrideConfigurer
与PropertyPlaceholderConfigurer
均继承了PropertyResourceConfigurer
,PropertyResourceConfigurer中提供了一个方法convertPropertyValue()
方法,可以通过覆盖该方法对相应的配置项内容进行转换。
例子:
propertyOverride.properties
这里使用了方法替换的方式实现处理,使用继承的方式也可以,自行创建一个后置处理类,继承系统的后置处理类,之后重写其convertPropertValue()
即可。在此不给具体代码了。
方法替换类:
public class MyReplacePostProcess implements MethodReplacer{
@Override
public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
// TODO Auto-generated method stub
return "对不起,"+obj.getClass().getSimpleName()+"的"+method.getName()+"被替换掉了";
}
}
其余的操作与之前例子一致,获得的结果是进行转换处理后的值。对于其Application的操作与PropertyPlaceholderConfigurer的类似在此也不加解释了。
3.CustomEditorConfigurer前置处理器
场景:
CustomEditorConfigurer与其余的两个前置处理器有所不同,它负责辅助性将后期会用到的信息注入到容器之中,本身对于BeanDefinition没有做任何变更
。
在使用配置文件配置参数的过程中会出现一种情况。配置文件中记载的数据都是String类型的,但是程序中的对象的类型却是多种多样的。想要完成这种转化,需要转化规则相关信息。CustomEditorConfigurer前置处理器负责的就是处理传递类型转换信息消息
的一种前置处理器。
对于Spring内部使用PropertyEditor
帮助我们把String转化为其它类型。每个对象类型对应一个的PropertyEditor.在做类型转换的过程中,采用默认JavaBean框架的PropertyEditor搜寻逻辑,继承对原生类型及Color,Font等类型的转化。
Sping还提供了部分的PropertyEditor:
StringArrayPropertyEditor
. :此类型会将符合CSV格式的字符串转化为String[] 数组的形式,默认为以“,”分割的字符串。可以选择字符串的分割方式。ByteArrayPropertyEditor,CharArrayPropertyEditor均属于功能的PropertyEditorClassEditor
:.根据String类型的class名称,将其转换为对应的class对象,效果与Class.forName(String)
类似,入参可以是一个String[]数组,以数组形式传入的场合其作用与ClassArrayPropertyEditor一致FileEditor
:Spring提供的 对应 File文件类型的PropertyEditor。同属于对资源进行定位类型的还包括InputStreamEditor
,URLEditor
等LocaleEditor
:针对Locale
类型的PropertyEditorPatternEditor
:针对Pattern
的PropertyEditor
以上的这些PropertyEditor容器默认会加载使用,即使不告诉容器如何对这些类型进行转换,容器依旧可以正确的进行转换工作。当我们需要转换的类型在以上PropertyEditor之外的场合。需要我们给出针对这种情况的自定义PropertyEditor,并且通过CustomEditorConfigurer告知容器,以让其可以实现正确的转化。
例子:
下面给出一个针对日期转换的PropertyEditor例子。对于Spring而言提供了个简便的实现自定义PropertyEditor的方法,通过实现PropertyEditor
接口太复杂的情况下,可以通过继承PropertyEditorSupport
类重写setAsText(String)
来实现自定义。
PropertyEditor代码:
public class DatePropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
// TODO Auto-generated method stub
SimpleDateFormat format=new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
try {
Date dateValue=format.parse(text);
setValue(dateValue);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
xml配置文件:
2019/1/1 11:11:11
测试用代码:
ConfigurableListableBeanFactory factory=new XmlBeanFactory(new ClassPathResource("simple.xml"));
CustomEditorConfigurer configurer=new CustomEditorConfigurer();
Map, Class extends PropertyEditor>> map=new HashMap();
DatePropertyEditor editor= new DatePropertyEditor();
map.put(java.util.Date.class,editor.getClass());
configurer.setCustomEditors(map);
configurer.postProcessBeanFactory(factory);
DateEditorBean bean=(DateEditorBean)factory.getBean("dateEditor");
System.out.println(bean.getDate());
分析:
本例子使用继承PropertyEditorSuppot
自定义创建了一个字符串日期转换器。供给bean装配注入的时候使用。
具体操作方式分如下:
- 继承PropertyEditorSupport,重写其
setAsText()
方法。将处理后的值通过setValue()
的方式注入 - 定义bean,同时使用setter注入方式注入值
- 创建容器+前置处理器
CustomEditorConfigurer
对象, - 创建Map对象,将需要处理的类型的class与PropertyEditor对象的class对应上
- 使用CustomEditorConfigurer对象的
setCustomEditors()
将PropertyEditor注册 - 调用CustomEditorConfigurer对象的
postProcessBeanFactory()
告知容器前置处理的变更情况
至此容器前置增强内容结束,附上git代码地址:
github下载地址