美文网首页Java 杂谈细说Spring
细说Spring——IoC详解(深入IoC实现)

细说Spring——IoC详解(深入IoC实现)

作者: Jivanmoon | 来源:发表于2018-05-22 12:46 被阅读10次

    容器启动阶段我们可以其实可以偷偷做一些事情

    书接上文:细说Spring——IoC详解(一),我们已经知道了容器实现控制反转和依赖注入的过程可以分为两个阶段:

    • 容器启动阶段
    • Bean的实例化阶段

    其实在这个两个阶段我们都可以偷偷的做一些事情,我们可以根据具体的场景加入自定义的扩展逻辑,下面我们就来了解一下容器启阶段我们可以做哪些事情。

    Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。我们已经知道BeanDefinition中保存了创建一个对象所需要的所有信息,那么既然我们可以修改BeanDefinition所保存的信息,那么是不是我们就可以为所欲为了,哈哈,比如我们可以修改其中bean定义的某些属性,为bean定义增加其他信息等,想想就很激动呢。那我们就接着看看怎么使用BeanFactoryPostProcessor容器扩展机制吧。

    这里要把BeanFactoryApplicationContext两种容器分开来讲:

    • 首先是BeanFactory,我们也知道BeanFactoryApplicationContext的父类,那么功能上BeanFactory也是比较弱小的,我们需要使用手动写代码来应用BeanFactoryPostProcessor,例如:
    // 声明将被后处理的BeanFactory实例
    ConfigurableListableBeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("配置文件地址"));
    // 声明要使用的BeanFactoryPostProcessor
    PropertyPlaceholderConfigurer propertyPostProcessor = new PropertyPlaceholderConfigurer();
    propertyPostProcessor.setLocation(new ClassPathResource("jdbc.properties"));
    // 执行后处理操作
    propertyPostProcessor.postProcessBeanFactory(beanFactory); 
    
    • 接着是更高级的ApplicationContext容器,这个就牛逼多了,他可以自动识别容器中的BeanFactoryPostProcessor实例对象,并使用他们,是的,是“他们”,我们可以在一个容器中使用多个BeanFactoryPostProcessor,这个时候聪明的你就可能想到顺序的问题,我们先买个关子,这里还是说怎么在ApplicationContext容器中使用BeanFactoryPostProcessor,我们其实只要把BeanFactoryPostProcessor的实现类配置到配置文件中就可以了(额,突然想到好像也应该把xml配置文件讲一下,这个还是先挖个坑把)如下所示:
    <beans>
     <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
             <list>
                 <value>conf/jdbc.properties</value>
                 <value>conf/mail.properties</value>
             </list> 
         </property>
     </bean>
    </beans> 
    

    是不是简单多了,果然还是高级的好啊。接下来就让我们看看BeanFactoryPostProcessor到底哪里神通广大吧。在我们学习要自定义实现BeanFactoryPostProcessor之前,我们可以先来看看为Spring已经提供的几个现成的BeanFactoryPostProcessor实现类:

    1、PropertyPlaceholderConfigurer

    在我们没有学习过PropertyPlaceholderConfigurer之前,我们在写xml配置文件的时候可能需要将一些可能改变的数据写的xml配置文件中,比如数据库连接信息、邮件服务器等相关信息,这些信息可能在后来发生改变,这样直接写到xml配置文件中肯定是有问题的,如果我们可以将这些可能会变的信息写到另外一个文件中就好了。这个时候就可以使用到PropertyPlaceholderConfigurer了。

    PropertyPlaceholderConfigurer允许我们在XML配置文件中使用占位符,并将这些占位符所代表的资源单独配置到简单的properties文件中来加载。以数据源的配置为例,使用
    PropertyPlaceholderConfigurer之后,我们就可以像下面这样写配置文件:

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
         <property name="url">
         <value>${jdbc.url}</value>
         </property>
         <property name="driverClassName">
         <value>${jdbc.driver}</value>
         </property>
         <property name="username">
         <value>${jdbc.username}</value>
         </property>
         <property name="password">
         <value>${jdbc.password}</value> 
    <bean>
    

    那么这些占位符所表示的资源我们存放在哪里呢?,还记得我们上面写的

    propertyPostProcessor.setLocation(new ClassPathResource("jdbc.properties"));
    
        <property name="locations">
             <list>
                 <value>conf/jdbc.properties</value>
                 <value>conf/mail.properties</value>
             </list> 
         </property>
    

    吗?这里的jdbc.properties就是存放占位符所表示资源的地方,你不会告诉我你没有见过properties文件吧,那你可得赶紧好好学习了。下面是jdbc.properties文件的内容:

    jdbc.url=jdbc:mysql://server/MAIN?useUnicode=true&characterEncoding=ms932&failOverReadOnly=false&roundRobinLoadBalance=true
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.username=your username
    jdbc.password=your password 
    

    PropertyPlaceholderConfigurer的实现机制就是当BeanFactory在第一阶段加载完成所有配置信息时,BeanFactory中保存的对象的属性信息还只是以占位符的形式存在,如${jdbc.url}${jdbc.driver}。当
    PropertyPlaceholderConfigurer作为BeanFactoryPostProcessor被应用时,它会使用properties配置文件中的配置信息来替换相应BeanDefinition中占位符所表示的属性值。这样,当进入容器实现的第二阶段实例化bean时,bean定义中的属性值就是最终替换完成的了。有了这样好用的工具,是不是感觉生活都变得美好了。接下来我们来看第二个BeanFactoryPostProcessor

    2、PropertyOverrideConfigurer

    比起我们第一个学习的PropertyPlaceholderConfigurer,这个PropertyOverrideConfigurer确实是在偷偷地做一些事情,我们可以通过PropertyOverrideConfigurer对容器中配置的任何你想处理的bean定义的property
    息进行覆盖替换,这里还是举个例子吧,还是前面的数据源的配置文件为例。

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="driverClassName">
            <value>${jdbc.driver}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
        <property name="testOnBorrow">
            <value>true</value>
        </property>
        <property name="testOnReturn">
            <value>true</value>
            </property>
        <property name="testWhileIdle">
            <value>true</value>
        </property>
        <property name="minEvictableIdleTimeMillis">
            <value>180000</value>
        </property>
        <property name="timeBetweenEvictionRunsMillis">
            <value>360000</value>
        </property>
        <property name="validationQuery">
            <value>SELECT 1</value>
        </property>
        <property name="maxActive">
            <value>100</value>
        </property>
    </bean>
    

    这里面有minEvictableIdleTimeMillistimeBetweenEvictionRunsMillis两个属性,假设我们按照如下代码把将PropertyOverrideConfigurer加载到容器之后,dataSource原来定义的默认值就会被pool-adjustment.properties文件中的信息所覆盖:

    <bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
     <property name="location" value="pool-adjustment.properties"/>
    </bean> 
    
    # pool-adjustment.properties 11
    dataSource.minEvictableIdleTimeMillis=1000
    dataSource.timeBetweenEvictionRunsMillis=50 
    

    这里需要注意我们在写PropertyOverrideConfigurer配置文件的时候,一定是是以XML中配置的bean定义的beanName为标志开始的(通常就是id指定的值)比如上面pool-adjustment.properties中的dataSource.minEvictableIdleTimeMillis=1000,后面跟着相应被覆盖的property的名称。

    3、CustomEditorConfigurer

    我们知道,不管对象是什么类型,也不管这些对象所声明的依赖对象是什么类型,通常都是通过XML文件格式来配置这些对象类型。但XML所记载的,都是String类型,即容器从XML格式的文件中读取的都是字符串形式,最终应用程序却是由各种类型的对象所构成。要想完成这种由字符串到具体对象的转换(不管这个转换工作最终由谁来做),都需要这种转换规则相关的信息,而CustomEditorConfigurer就是帮助我们传达类似信息的。

    在了解怎么使用CustomEditorConfigurer之前,我们需要先了解PropertyEditor,那么PropertyEditor到底是何方神圣呢?

    其实PropertyEditor就是那个真正干活的人,就是他实现了具体的类型转换逻辑,而CustomEditorConfigurer更像是一个领导,通常不都是领导动动嘴,下属跑断腿,哈哈,这里PropertyEditor就是干活的下属。

    Spring中提供了很多的PropertyEditor,对应了各种转换逻辑,这里就不一一列举了,如果想研究一下,可以去org.springframework.beans.propertyeditors下面研究。这里着重讲解一下怎么实现自定义的PropertyEditor

    4、实现自定义的PropertyEditor

    首先我们先来看一看PropertyEditor接口的定义:

    public interface PropertyEditor {
    
        void setValue(Object value);
    
        Object getValue();
    
        boolean isPaintable();
    
        void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box);
    
        String getJavaInitializationString();
    
        String getAsText();
    
        void setAsText(String text) throws java.lang.IllegalArgumentException;
      
        String[] getTags();
    
        java.awt.Component getCustomEditor();
    
        boolean supportsCustomEditor();
    
        void addPropertyChangeListener(PropertyChangeListener listener);
    
        void removePropertyChangeListener(PropertyChangeListener listener);
    }
    

    不要被这么多的方法给吓到,对于我们使用者来说其实重点只有下面4个方法:

    • void setValue(Object value);设置属性值
    • Object getValue();获取属性值
    • String getAsText();把属性值转换成String
    • void setAsText(String text);String转换成属性值

    所以Java很机智地提供了一个适配器java.beans.PropertyEditorSupport来帮助我们实现属性值的转换,它帮助我们实现了大部分的方法,我们只需要重写getAsTextsetAsText的逻辑就可以了,哈哈,又可以偷懒了。
    这里我就也偷个懒,我在学习PropertyEditor的时候发现了一篇讲解的特别好的博客,里面有自定义PropertyEditor的例子,还包括了一下我本来不是很清楚的知识,这里大家就去看看这个博客就可以了:Spring PropertyEditor分析

    5、最后填一个坑

    还记得我说过的一个容器可以有多个BeanFactoryPostProcessor吗,还记得我并没有说如果有多个BeanFactoryPostProcessor时的执行顺序问题,这里就解答一下。
    这里首先要提出一个Ordered接口,这个接口的作用就是指定操作顺序的,下面来看一下这个接口的定义:

    public interface Ordered {
      
        int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    
        int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
    
        int getOrder();
      
    }
    

    只有1个方法:getOrder(); 2个变量:最高级(数值最小)和最低级(数值最大)。
    这里我们可以看出来数值越小,那么对应的优先级就越大。
    我们如果有时间,可以去看一下前面讲的PropertyPlaceholderConfigurerPropertyOverrideConfigurerCustomEditorConfigurer三个PropertyPlaceholderConfigurer的源码,就会发现他们都直接或间接的实现了Ordered接口,也就是说,我们可以通过指定其order属性的值,为这些PropertyPlaceholderConfigurer安排顺序,这样我们就再也不用担心顺序的问题了,同时我们自定义的PropertyPlaceholderConfigurer也可以通过实现Ordered接口,来指定顺序。

    第二篇也结束了,请继续关注接下的内容。

    相关文章

      网友评论

      • 97c424f6736d:看了你的文章,写的真的很棒,我会一直关注你的,加油

      本文标题:细说Spring——IoC详解(深入IoC实现)

      本文链接:https://www.haomeiwen.com/subject/nzyhjftx.html