2

上回展示了IOC的大致实现的原型,那么在Spring框架中具体是怎么实现这个容器根据metadata元信息配置加载POJO的过程的呢?在整个Spring IOC容器的工作过程中有很多地方是设计地相当灵活的,供给使用者很多空间去完成自己的任务,而不是一味地只是完成容器的机械过程。

这是整个IOC容器工作过程的过程图:

clipboard.png

容器启动阶段

  • 加载配置文件信息

  • 解析配置文件信息

  • 装配BeanDefinition

  • 后处理

首先配置文件或者注解等元信息和JavaBean的类信息被加载到IOC容器中,容器读取到xml格式的配置文件,这个配置文件是使用者声明的依赖关系和装配中需要特别关注的地方,是装配Bean的早期“外部图纸”,容器中的解析引擎可以把我们写入的文本形式的字符元信息解析成容器内部可以识别的BeanDefinition,可以把BeanDefinition理解成为类似反射机制的类结构,这个通过对JavaBean和配置文件进行分析得到的BeanDefinition获取了组装一个符合要求的JavaBean的基本结构,如果需要除了BeanDefinition之后还要对这个BeanDefinition再做修改的话则执行这个后处理,后处理一般是通过Spring框架内的BeanFactoryPostProcessor处理的。

我们仍然使用上次使用过的例子来说明这个BeanDefinition的运作原理:有三个bean,主模块MainModule和依赖模块DependModuleA,DependModuleB,前者依赖后面两个模块构成,在配置文件里我们一般会这么进行依赖的声明:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <bean id="mainModule" class="com.rocking.demo.MainModule">
        <property name="moduleA">
            <ref bean="moduleA"/>
        </property>
        <property name="moduleB">
            <ref bean="moduleB"/>
        </property>
    </bean>
    <bean id="moduleA" class="com.rocking.demo.DependModuleAImpl"></bean>
    <bean id="moduleB" class="com.rocking.demo.DependModuleBImpl"></bean>
</beans>

这是我们的程序演示一个标准的BeanFactory容器(Spring IOC容器的实现之一)对上面配置文件的装配:

class MainModule {
    private DependModuleA moduleA;
    private DependModuleB moduleB;

    public DependModuleA getModuleA() {
        return moduleA;
    }

    public void setModuleA(DependModuleA moduleA) {
        this.moduleA = moduleA;
    }

    public DependModuleB getModuleB() {
        return moduleB;
    }

    public void setModuleB(DependModuleB moduleB) {
        this.moduleB = moduleB;
    }

}

interface DependModuleA {
    public void funcFromModuleA();
}

interface DependModuleB {
    public void funcFromModuleB();
}

class DependModuleAImpl implements DependModuleA {

    @Override
    public void funcFromModuleA() {
        System.out.println("This is func from Module A");
    }

}

class DependModuleBImpl implements DependModuleB {

    @Override
    public void funcFromModuleB() {
        System.out.println("This is func from Module B");
    }

}

public class SimpleIOCDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("Beans.xml");
        MainModule mainModule = (MainModule) beanFactory.getBean("mainModule");
        mainModule.getModuleA().funcFromModuleA();
        mainModule.getModuleB().funcFromModuleB();
    }
}

这里我们的配置文件和JavaBean被加载读取并被解析,这里的BeanDefinition生成使用过程掩藏在其中,这是实际上在IOC内部发生的大致过程:

public class SimpleIOCDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        AbstractBeanDefinition mainModule = new RootBeanDefinition(MainModule.class);
        AbstractBeanDefinition moduleA = new RootBeanDefinition(DependModuleAImpl.class);
        AbstractBeanDefinition moduleB = new RootBeanDefinition(DependModuleBImpl.class);
        
        beanFactory.registerBeanDefinition("mainModule", mainModule);
        beanFactory.registerBeanDefinition("moduleA", moduleA);
        beanFactory.registerBeanDefinition("moduleB", moduleB);
        
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.add("moduleA", moduleA);
        propertyValues.add("moduleB", moduleB);
        mainModule.setPropertyValues(propertyValues);
        
        MainModule module = (MainModule) beanFactory.getBean("mainModule");
        module.getModuleA().funcFromModuleA();
        module.getModuleB().funcFromModuleB();
    }
}

对xml的元信息进行加载读取后,IOC解析引擎会将其中提到的模块依据其真实类型创建成BeanDefinition,这个BeanDefinition可以看成是一种反射或者代理的过程,目的是为了让IOC容器清楚以后要创建的实例对象的bean结构,然后将这些bean结构注册到BeanFactory中去,之后将主模块的依赖以setter注入的形式加入到主模块的属性中去,(这一点要看主模块提供的是setter方法还是初始化方法),这个过程结束后注册完所有“图纸”上规定的bean的Definition后,BeanFactory就已经成型。之后只要调用getBean方法即可将符合要求的bean生产出来,这是下一阶段的过程,我们之后再说。

在将BeanDefinition这一“图纸”上的信息注册到BeanFactory完毕后,我们仍然可以对已经注册完的BeanDefinition进行改动的操作,这就是我们前面提到的Spring为使用者设计的灵活的地方之一,不是说所有的过程不可控,而是在很多地方留了很多使用者可以发挥的余地。具体的办法是使用BeanFactory处理器BeanFactoryPostProcessor来介入对BeanFactory的处理以进一步改写我们需要修改的BeanDefinition部分。这个过程对应流程里的“后处理”过程。
以常见的处理器之一:属性占位符配置处理器为例,就是在已经构建完成已注册完毕的BeanFactory之后再对它处理,以使得BeanDefinition相应属性里的内容修改为配置处理器指定配置文件里的信息:

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions( new ClassPathResource( "Beans.xml"));
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setLocation( new ClassPathResource( "about.properties"));
configurer.postProcessBeanFactory( beanFactory);

BeanFactoryPostProcessor将对BeanFactory处理,处理的结果就是把BeanDefinition中定义的某些属性改成BeanFactoryPostProcessor定义位置处的某些信息。

Bean 实例化阶段

有了经过处理的BeanDefinition的“内部图纸”的指导下,容器可以进一步把BeanDefifnition通过反射或CGLIB动态字节码生产的方式化为存在于内存中的活化实例对象,再将BeanDefinition规定的依赖对象通过setter注入或者初始化注入的方式装配进新创建的实例对象中,这里是实实在在地将依赖对象的引用赋给需要依赖的对象属性中。
但是这里需要注意的是创建的实例不仅仅是一个简单的bean定义的实例,而是一个经过Spring包装的BeanWrapper实例,这里为什么要采用BeanWrapper的方式来包装bean呢?是因为BeanWrapper提供了统一访问bean属性的接口,在创建完了基本的bean的框架后要对其中的属性进行设置,每个bean的setter方法都不一样,所以如果直接用反射设置的话会非常复杂,所以spring提供这种包装来简化属性设置:

BeanWrapper beanWrapper = new BeanWrapperImpl(Class.forName("com.rocking.demo.MainModule"));
beanWrapper.setPropertyValue( "moduleA", Class.forName("com.rocking.demo.DepModuleAImpl").newInstance());
beanWrapper.setPropertyValue( "moduleB", Class.forName("com.rocking.demo.DepModuleBImpl").newInstance());
MainModule mainModule= (MainModule) beanWrapper.getWrappedInstance();
mainModule.getModuleA().funcFromA();
mainModule.getModuleB().funcFromB();

以上的过程展示了在Spring内部,通过获取类的反射容器了解将来包装的实例bean的结构并作出包装,使用统一的属性设置方法setPropertyValue来对这个包装的实例设置属性,最后得到的bean实例通过getWrappedInstance拿到,可以发现已经成功将其属性赋值。

这个时候的bean实例其实已经完全可以使用了,但是Spring同样在实例化阶段也为我们准备了灵活的策略以完成使用者对这个阶段的介入,和容器启动阶段的BeanFactoryPostProcessor控制BeanDefinition类似,在实例化阶段,Spring提供了BeanPostProcessor处理器来对已经装配好的实例进行操作,以完成可能需要的改动:、

这里举个例子来说明,定义一个BeanPostProcessor的实现类,实现其中的方法postProcessAfterInitialization和postProcessBeforeInitialization来定义对在bean实例装配之后和之前分别进行的操作,在BeanFactory添加了这个处理器后就会在每次调用getBean方法装配实例的时候,都会传入根据“图纸”装配出的bean实例(包括装配过程中创建的依赖实例bean)调用这两个方法,这些方法可以对这些bean实例实施修改。

下面是一个这样的例子(MainModule及其依赖关系和本文之前的例子相同):

class ModuleC {
    private String x;

    public String getX() {
        return x;
    }

    public void setX(String x) {
        this.x = x;
    }
    
}

class ModulePostProcessor implements BeanPostProcessor{

    @Override
    public Object postProcessAfterInitialization(Object object, String string)
            throws BeansException {
        System.out.println(string);
        if(object instanceof ModuleC){
            System.out.println(string);
            ((ModuleC)object).setX("after");
        }
        return object;
    }

    @Override
    public Object postProcessBeforeInitialization(Object object, String string)
            throws BeansException {
        if(object instanceof ModuleC){
            ((ModuleC)object).setX("before");
        }
        return object;
    }
    
}

public class VerySimpleIOCKernal {
    public static void main(String[] args) throws ClassNotFoundException, BeansException, InstantiationException, IllegalAccessException {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(new ClassPathResource("Beans.xml"));
        ModulePostProcessor postProcessor = new ModulePostProcessor();
        beanFactory.addBeanPostProcessor(postProcessor);
        MainModule module = (MainModule) beanFactory.getBean("mainModule");
        ModuleC moduleC = (ModuleC) beanFactory.getBean("moduleC");    
        System.out.println(moduleC.getX());
    }
}

这是bean的依赖关系配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="mainModule" class="com.rocking.demo.MainModule">
        <property name="moduleA">
            <ref bean="moduleA"/>
        </property>
        <property name="moduleB">
            <ref bean="moduleB"/>
        </property>
    </bean>
    <bean id="moduleA" class="com.rocking.demo.DepModuleAImpl">
        <property name="infoA">
            <value>${moduleA.infoA}</value>
        </property>
    </bean>
    <bean id="moduleB" class="com.rocking.demo.DepModuleBImpl">
        <property name="infoB">
            <value>info of moduleB</value>
        </property>
    </bean>
    <bean id="moduleC" class="com.rocking.demo.ModuleC">
    </bean>
</beans>

从最终的结果我们可以看出,每次调用getBean方法得到的bean实例(包括因依赖关系生成的)都将被BeanPostProcessor获取进行前置和后置处理。

除了类似上面的BeanPostProcessor的办法对装配好的bean再做处理外,Spring还可以通过配置init-method和destroy-method来对bean的初始化和销毁过程设置回调函数,这些回调函数也还可以灵活地提供更改bean实例的机会。

整个Spring IOC的过程其实总体来说和我们自己写的IOC原型在本质上是一样的,只不过通过复杂的设计使得IOC的过程能够更灵活有效地提供给使用者更多的发挥空间,除此之外,Spring的IOC也在安全性、容器的稳定性、metadata到bean转换的高效性上做到了精美的设计,使得IOC这一Spring容器的基础得以稳固。


JinhaoPlus
1.5k 声望92 粉丝

扎瓦程序员