之前写的草稿居然手残清掉了。。。
spring容器内部是如何实现的呢?在使用的时候,应该都不会怎么考虑spring的内部实现,但是了解一下他背后的秘密也是很有意思的一件事情
从这幅图片中可以看出来spring容器作为核心,首先以某种方式加载Configuration Metadata 容器会将这些信息解析绑定,装配成系统可用的对象,时刻准备着被调用
1.容器启动阶段
容器开始启动的时,通过某种方式加载Configuration Metadata,将配置文件中的bean信息解析后,将解析之后的信息组合成相应的BeanDefinition,然后将这些BeanDefinition注册到BeanDefinitionRegistry中。这样启动阶段任务就完成了
2.Bean实例化阶段
当程序中有请求对象操作或者隐式调用getBean方法的时候,容器会先检查是否已经实例化该对象。如果没有,容器就会根据注册好的BeanDefinition的信息实例化对象,并为其注入依赖;如果对象已经被一些回调接口,也可以根据回调接口装配,装配完毕之后可以直接返回该对象提供使用。
容器启动的时候加点小料
Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。该机制允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做相应的修改。这就相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改其中bean定义的某些属性,为bean定义增加其他信息等
BeanFactoryProcessor扩展机制中包含了:
- PropertyPlaceholderConfigurer
开发中,我们通常会将一些配置信息不直接放在配置文件,而是将信息用properties文件保存,如果信息有所改变,直接在properties文件中修改,这样既能保证信息的安全性,也能保证修改方便,类似于下面的数据库信息配置
<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>
</bean>
====================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
配置信息中放占位符,引入配置文件,会将对应的属性名称替换掉
2. PropertyOverrideConfigurer
这个扩展用来覆盖替换信息的,也就是说对容器中配置的任何想处理的bean定义的property信息进行覆盖替换
dataSource.maxActive=200
--------------------------------------
# pool-adjustment.properties 11
dataSource.minEvictableIdleTimeMillis=1000
dataSource.maxActive=50
--------------------------------------
<bean class="org.springframework.beans.factory.config.PropertyOverrideConfigurer">
<property name="location" value="pool-adjustment.properties"/>
</bean>
这里的location对应的配置文件中的信息有一条配置已经被配置过一次,系统引用的值是第二个,因为第一个已经被覆盖掉了(但是我觉得吧,如果这样子的话,直接修改配置文件中的值不就好了 - -!)
- CustomEditorConfigurer
该扩展是辅助性地将后期需要用的信息注入到容器中,对于BeanDefinition没有影响
spring提供了PropertyEditor,可以完成类型转换,具体的类型转化:
- StringArrayPropertyEditor 将符合CSV格式的字符串转换成String[]数组的形式
- ClassEditor 根据String类型的class名称,直接将其转换成相应的Class对象
- FileEditor 针对java.util.Locale类型
- PatternEditor 针对java.util.regex.Pattern类型
也可以自定义PropertyEditor,但是呢我觉得想要类型转换,这种方式貌似也没有在Java中直接转换,不过spring真的强大
Bean的生命周期
- Bean的实例化与BeanWrapper
容器内部实现的过程中,会使用“策略模式”选择使用某种方式实例化,一般会使用反射或者CGLIB动态字节码初始化
BeanWrapper接口通常在spring内部使用,作用是对某个bean进行“包裹”,然后对“包裹”进行操作,如设置或者是获取对象的某个属性。 - 各色的Aware接口
实例化完成之后,容器会去检查对象是否实现了某些Aware接口,如果有的话又有些相应操作:
- org.springframework.beans.factory.BeanNameAware 将该对象实例的bean定义对应的beanName设置到当前对象实例
- org.springframework.beans.factory.BeanClassLoaderAware 将对应加载当前bean的Classloader注入当前对象实例
- org.springframework.beans.factory.BeanFactoryAware BeanFactory容器会将自身设置到当前对象实例。这样,当前对象实例就拥有了一个BeanFactory容器的引用
BeanPostProcessor
这个接口和之前看到的BeanFactoryProcessor是不一样的,区分方法也很简单,BeanFactoryProcessor是容器启动时候作用,BeanPostProcessor是在对象实例化阶段作用就很好区别了
接口声明了两个方法:
postProcessBeforeInitialization 用来前置处理将要执行的方法
postProcessAfterInitialization 后置处理将要执行的方法InitializingBean和init-method
其中之一可以用来完成初始化工作,用过init-method,在项目启动的时候将数据导入到数据库
<bean id="serverGroupManager" class="com.mhc.fiat.syndingorginfo.task.InitDingDingInfo" init-method="init"/>
- DisposableBean与destroy-method
当对象的调用完成之后,容器将检查singleton类型的bean实例,看其是否实现了org.springframework.beans.factory.DisposableBean接口。或者其对应的bean定义是否通过的destroy-method属性指定了自定义的对象销毁方法。如果是,就会为该实例注册一个用于对象销毁的回调(Callback),以便在这些singleton类型的对象实例销毁之前,执行销毁逻辑。