Spirng前置处理器

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文件实现:
配置文件:

   <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>propertyHolder.properties</value>
                </list>
            </property>
        </bean>

代码:

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配置文件。强化可读性的同时,也降低了维护难度。
代码:
配置文件

  <bean id="propertyHolder" class="com.example.demo.postprocess.MyPropertyHolderBean">
            <property name="username">
                <value>${java.version}</value>
            </property>
            <property name="password" value="${propertyHolder.password}"></property>
        </bean>

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文件中使用注入的方式处理。

举例如:

        <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>propertyHolder.properties</value>
                </list>
            </property>
            <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_FALLBACK"></property>
        </bean>

其余的操作同正常的使用Application一样。十分简便。

2.PropertyOverrideConfigurer前置处理器

应用场景:
可以通过PropertyOverrideConfigurer对容器中任何想处理的bean定义中的信息进行覆盖替换。如此当容器创建bean的时候其bean的属性值就不同于xml文件的值了。
代码:
配置文件:

        <bean id="overrideBean" class="com.example.demo.postprocess.MyPropertyOverrideBean">
            <property name="username">
                <value>工具人</value>
            </property>
            <property name="password">
                <value>简单的密码</value>
            </property>
        </bean>

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进行处理。PropertyOverrideConfigurerPropertyPlaceholderConfigurer均继承了PropertyResourceConfigurer,PropertyResourceConfigurer中提供了一个方法convertPropertyValue()方法,可以通过覆盖该方法对相应的配置项内容进行转换。

例子:

    <bean id="dealWithReplace" class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
            <property name="locations">
                <list>
                    <value>propertyOverride.properties</value>
                </list>
            </property>
            <replaced-method name="convertPropertyValue" replacer="replaceMethod"></replaced-method>
        </bean>

这里使用了方法替换的方式实现处理,使用继承的方式也可以,自行创建一个后置处理类,继承系统的后置处理类,之后重写其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均属于功能的PropertyEditor

  • ClassEditor:.根据String类型的class名称,将其转换为对应的class对象,效果与Class.forName(String)类似,入参可以是一个String[]数组,以数组形式传入的场合其作用与ClassArrayPropertyEditor一致

  • FileEditor :Spring提供的 对应 File文件类型的PropertyEditor。同属于对资源进行定位类型的还包括InputStreamEditor,URLEditor

  • LocaleEditor :针对Locale类型的PropertyEditor

  • PatternEditor:针对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配置文件:

  <bean id="dateEditor" class="com.example.demo.postprocess.DateEditorBean">
            <property name="date">
                <value>2019/1/1 11:11:11</value>
            </property>
        </bean>

测试用代码:

ConfigurableListableBeanFactory factory=new XmlBeanFactory(new ClassPathResource("simple.xml"));
        CustomEditorConfigurer configurer=new CustomEditorConfigurer();
        Map<Class<?>, 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下载地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352