主要流程:
1、读取配置文件
2、实例化bean和填充bean属性
这个粗略的流程感觉更像是一个需求,有了这个需求,那么spring内部是怎么处理的呢?
我们知道spring的两个核心接口BeanFactory和ApplicationContext。BeanFactory主要定义容器的核心方法,ApplicationContext加以扩展,主要使用的还是ApplicationContext。在ApplicationContext的子类中,AbstractApplicationContext中的refresh()方法定义了容器加载配置文件及装配Bean的过程。
AbstractApplicationContext#refresh()代码如下:
// Prepare this context for refreshing.
//1 准备刷新工作
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 2 实例化BeanFactory,将配置文件的信息装入到容器的Bean定义的注册表(BeanDefinitionRegistry中),此时Bean还未初始化
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
// 3 准备BeanFactory 主要是加载一些类
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
// 4 留作子类实现
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
// 5 调用工厂后处理器
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//6 注册bean后处理器
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//7 初始化消息源
initMessageSource();
// Initialize event multicaster for this context.
//8 初始化事件广播器
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//9 钩子方法
onRefresh();
// Check for listener beans and register them.
// 10 注册监听器
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//11 完成bean实例化(除lazy-init),并放入缓存中
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 12 广播刷新事件
finishRefresh();
}
其实直接粘贴代码意义不大。重点还是要看refresh()函数的重点步骤。
一、prepareRefresh():准备工作。
在spring代码中,很容易看到这样的逻辑:就是在一个流程动作上下包裹着前后的动作。也就是说:在刷新动作时,会包裹刷新前动作和刷新后动作。这些动作可以有操作,也可以没有。又如:在读取xml文件加载bean时的一个操作(处理xml前,处理xml后):
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
这里主要记录了一下容器开始时间,初始化了属性资源。
二、ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
作用:实例化BeanFactory,将配置文件的信息装入到容器的Bean定义的注册表(BeanDefinitionRegistry中),此时Bean还未初始化
其中,AbastractApplicationContext#obtainFreshBeanFactory()如下:
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
这里的getBeanFactory()方法用来获取实例。重点是refreshBeanFactory()方法。
AbstractRefreshableApplicationContext#refreshBeanFactory()
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
如果存在BeanFacotry(),则销毁。这里要保证在BeanDefinitionRegistry中存储的是最新的xml对应的Bean映射。然后就到了loadBeanDefinitions(beanFactory)加载BeanDefinitions.中间跳过一些步骤,然后就到了XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource encodedResource)
显然是要从相应的资源(这里是XML中)读取信息。截取一段信息:
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
这里提到了doLoadBeanDefinitions(inputSource, encodedResource.getResource());方法。点进去之后跟想的一样,加载xml文件
try {
Document doc = doLoadDocument(inputSource, resource);
return registerBeanDefinitions(doc, resource);
}
接着跳过几步就到了把xml中的DOM节点信息转化为Bean对应属性的方法
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
if (this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
这里到了parseBeanDefinitions(root, this.delegate)方法。也就是把root信息转化为BeanDefinitions。点进去发现spring处理两类节点:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这里就是遍历Element了。然后对每个节点判断,如果是默认命名空间(http://www.springframework.org/schema/beans)下的元素,则使用parseDefaultElement(ele, delegate)方法,否则使用delegate.parseCustomElement(ele);方法。查看默认元素的方法:parseDefaultElement(ele, delegate);
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
这里有四个节点:
public static final String ALIAS_ELEMENT = "alias";
public static final String BEAN_ELEMENT = "bean";
public static final String NESTED_BEANS_ELEMENT = "beans";
这里再看一下DefaultBeanDefinitionDocumentReader#processBeanDefinition(ele, delegate)方法
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
里面有一个行代码跳转到registerBeanDefinition(bdHolder, getReaderContext().getRegistry())
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String aliase : aliases) {
registry.registerAlias(beanName, aliase);
}
}
}
这里有一个地方需要注意:前面提到过的BeanDefinitionRegistry,也就是把配置信息装入到BeanDefinitionRegistry中。进入 registerBeanDefinition方法中如下:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "'beanName' must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
this.beanDefinitionMap.put(beanName, beanDefinition);
}
这里我们可以看到,beanName和beanDefinition装入到了一个Map中。这个map的定义如下:
/** Map of bean definition objects, keyed by bean name */
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);
其中,key位bean的名字,value为BeanDefinition。
这个时候的Bean还是没有属性的。
三、prepareBeanFactory(beanFactory):加载一些类资源
四、postProcessBeanFactory(beanFactory):留作子类实现,这里为空
五、invokeBeanFactoryPostProcessors(beanFactory):调用BeanFactoryPostProcessor接口处理beanFactory.
这个方法的参数是beanFactory,它包含了在xml定义的bean信息。调用这个方法,可以在读取xmlbean信息之后以及实例化bean之前修改bean定义的属性。
spring中,有内置的一些BeanFactoryPostProcessor实现类,常用的有:
通过这几个实现类可以了解到这个接口会起到的一些作用:对占位符进行处理等
其中:BeanFactoryPostProcessor接口定义如下:
public interface BeanFactoryPostProcessor {
/**
*在其标准初始化后修改应用程序上下文的内部bean工厂。 所有bean定义都将被加载,但是没有bean将被实例化。
*这允许覆盖或添加属性,即使是急切地初始化bean。
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
这一步的重点是理解BeanFactoryPostProcessor接口的作用。实现这个接口,可以实现修改bean属性的功能。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置'order'属性来控制各个BeanFactoryPostProcessor的执行次序。
这里摘抄一个网上的例子(原地址:http://blog.csdn.net/caihaijiang/article/details/35552859)实现一个自定义的BeanFactoryPostProcessor实现类来实现动态修改Bean属性功能。
1.在配置文件中定义一个名为MyBean的bean,这里只提供了两个简单的属性desc和mark。以及定义一个实现BeanFactoryPostProcessor接口的类MyBeanFactoryPostProcessorImpl
<bean id="myBean" class="uodut.spring3x.MyBean">
<property name="desc" value="这里通过自定义MyBeanFactoryPostProcessorImpl来动态改变mark的值"></property>
<property name="mark" value="initMark"></property>
</bean>
<bean id="myBeanFactoryPostProcessor" class="uodut.spring3x.MyBeanFactoryPostProcessorImpl"/>
2.MyBeanFactoryPostProcessorImpl类实现如下:
public class MyBeanFactoryPostProcessorImpl implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition bd = beanFactory.getBeanDefinition("myBean");
System.out.println("属性值:" + bd.getPropertyValues().toString());
MutablePropertyValues propertyValues = bd.getPropertyValues();
if (propertyValues.contains("mark")) {
propertyValues.addPropertyValue("mark", "initMark->afterMark");
}
}
}
3.定义一个测试方法来测试
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("BeanTest.xml");
MyBean bean = (MyBean) context.getBean("myBean");
System.out.println("描述:" + bean.getDesc());
System.out.println("备注:" + bean.getMark());
}
运行后输出结果如下:(可以看到mark属性的值从initMark变成了initMark->afterMark)
1 属性值:PropertyValues: length=2; bean property 'desc'; bean property 'mark'
2 描述:这里通过自定义MyBeanFactoryPostProcessorImpl来动态改变mark的值
3 备注:initMark->afterMark
六、registerBeanPostProcessors(beanFactory);注册BeanPostProcessors
BeanPostProcessor,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。这里说的初始化方法,指的是下面两种:
1)bean实现了InitializingBean接口,对应的方法为afterPropertiesSet
2)在bean定义的时候,通过init-method设置的方法
注意:BeanPostProcessor是在spring容器加载了bean的定义文件并且实例化bean之后执行的。BeanPostProcessor的执行顺序是在BeanFactoryPostProcessor之后。
这里在上面的例子基础上修改:
public class MyBean implements InitializingBean{
public void afterPropertiesSet() throws Exception {
System.out.println("调用afterPropertiesSet方法");
this.desc = "在初始化方法中修改之后的描述信息";
}
}
声明MyBeanPostProcessorImpl实现BeanPostProcessor
public class MyBeanPostProcessorImpl implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor,对象" + beanName + "调用初始化方法之前的数据: " + bean.toString());
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor,对象" + beanName + "调用初始化方法之后的数据:" + bean.toString());
return bean;
}
}
运行测试用例:
属性值:PropertyValues: length=2; bean property 'desc'; bean property 'mark'
调用setDesc方法
调用setMark方法
BeanPostProcessor,对象myBean调用初始化方法之前的数据: [描述:原始描述信息, 备注:initMark->afterMark]
调用afterPropertiesSet方法
调用initMethod方法
BeanPostProcessor,对象myBean调用初始化方法之后的数据:[描述:在初始化方法中修改之后的描述信息, 备注:initMark->afterMark]
描述:在初始化方法中修改之后的描述信息
备注:initMark->afterMark
可以看到在初始化方法前后会调用BeanPostProcessor接口的两个方法。
七、initMessageSource();初始化消息源
八、initApplicationEventMulticaster();初始化应用上下文事件广播
九、onRefresh();初始化其他特殊的bean,这是一个钩子方法。
十、registerListeners();注册监听器
十一、finishBeanFactoryInitialization(beanFactory);初始化所有单实例的Bean,使用lazy-init的bean除外。初始化Bean后,将它们放入Spring容器的缓存中。
十二、finishRefresh();创建上下文刷新事件,事件广播器负责将这些事件广播到每个注册的事件监听器中。
spring容器从加载配置文件到创建出一个完整的Bean的流程
1、ResourceLoader从存储介质中加载Spring配置信息,并使用Resource表示该配置文件的资源
2、BeanDefinitionReader读取Resource所指向的配置文件资源,然后解析配置文件。配置文件中的每个<bean>解析成一个BeanDefinition对象,并保存到BeanDefinitionRegistry中。
3、容器扫描BeanDefinitionRegistry中的BeanDefinition,使用java反射机制识别出实现BeanFactoryPostProcessor接口的Bean,然后调用BeanFactoryPostProcessor的实现类对BeanDefinitionRegistry中的BeanDefinition进行加工处理。主要作以下两个工作:
1)对使用到占位符的<bean>元素标签进行解析,得到最终的配置值。这意味着对半成品式的BeanDefinition对象进行加工处理并得到成品的BeanDefinition对象。(这里用到了前文提到的PropertyPlaceholderConfigurer)
2)对BeanDefinitionRegistry中的BeanDefinition进行扫描,通过反射机制找出所有属性编辑器的Bean(实现java.beans.PropertyEditor接口的Bean),并自动将它们注册到spring容器的属性编辑器注册表中(PropertyEditorRegistry)
4、Spring容器从BeanDefinitionRegistry中取出加工后的BeanDefinition,并调用InstantiationStrategy进行Bean实例化的工作。
5、在实例化Bean时,Spring容器使用BeanWrapper对Bean进行封装,它结合该Bean的BeanDefinition以及容器中属性编辑器,完成Bean属性的设置工作。
6、BeanPostProcess对完成属性设置的Bean进行后续加工,装配出一个准备就绪的Bean。
参考资料:
使用 windows命令和iconv.exe批量转换文件编码 iconv是知名的开源跨平台编码转换库,iconv.exe是iconv库在windows下的命令行工具,iconv.exe的一般用法:iconv.exe -f gbk -t utf-8 gbk.txt >
鸢尾花三种聚类算法(K-means,AGNES,DBScan)的python实现:结果效果比较明显:同一鸢尾花数据集测试结果如图所示,可以看出K-means的聚类效果最好。k-means对于大型数据集也是简单高效、时间复杂度、空间复杂度低。 最重要是数据集大时结果容易局部最优;需要预先设定K值,对最先的K个点选取很敏感;对噪声和离群值非常敏感;只用于numerical类型数据;不能解决非凸数...
往Redis中初始化几条测试数据:china:beijingchina:shandong:hezechina:shandong:jinan 测试代码:Java代码 Jedis jedis = new Jedis("10.110.20.152", 6379); Set&lt;String&gt; set = jedis.keys("china...
spring boot项目控制台打印如下日志,但程序可正常运行,并未报错:( ( )___ | '_ | '| | ’ / ` |/ )| |)| | | | | || (| | ) ) ) )’ || .__|| ||| |, | / / / /=========||==============|/=///_/:: Spring Boot :: (v2.6.4)上述错误讲述的是:Sqlsession未注册同步,同步注册未激活,JDBC connection不
经常碰到系统跑着跑着一段时间内存满了,出现内存泄漏的问题,系统软件太庞大,这类问题又不好直接从源码中分析,所以需要借助工具来分析了,kmemleak就是这样的一个工具。在Kernelhacking中打开CONFIG_DEBUG_KMEMLEAK =y即使能了kmemleak,其实就是开了一个内核线程,该内核线程每10分钟(默认值)扫描内存,并打印发现新的未引用的对象的数量。kmemleak的原理...
我们知道在处理业务查询数据库的时候一定会用到多表查询。 今天我们就来整理下mybatis在注解和非注解方式中如何进行一对一、一对多的关联查询。 其实在处理这个问题上,mybatis提供了两种方式,其一是嵌套结果集方式,二是嵌套select,因为第二种方式要每次要进行n+1次查询会影响系统性能,所以我们这里不做介绍,我们来看嵌套结果集查询。一、一对一关联首先是非注解方式 这里我们以班级 - 老师
<br />一. 如何初始化一个TreeView? <br /><br /><br />弄一个窗口,放上一个TreeView和一个Button,分别取名为TV1和Btn1。如果需要 <br />在每个节点前有个图,请在窗口上放上一个ImageList,取名为ImageList1,双击 <br />它,加入六个图标。还要记得记得将TV1的Images属性改为ImageList1噢。双击按 <br />钮Btn1,在里面填入以下代码,然后按F9运行,点击Btn1就可以看到效果了。 <br
我们有时候会勾选“include depencies with "Provided" scope”。这个会造成我们本地打包的时候,会包含 provided 的依赖,但是在线上的时候是不会含有provided 依赖的。所以会有一种情况:系统使用了一个 provided 依赖的类,本地会没有问题,但是发布到线上会出现问题。这里记录一下。...
文章目录关系型数据库1. MySQL数据库2. Microsoft SQL Server数据库3. Oracle数据库4. Postgresql数据库5. Sybase数据库6. DB2数据库7. Access数据库非关系型数据库1. Mongodb数据库2. Redis数据库3. Hbase数据库4. Memcaced数据库关系型数据库1. MySQL数据库 [下载地址] My...
无监督学习(unsupervised learning)没有已知标签的训练集,只给一堆数据集,通过学习去发现数据内在的性质及规律。K-Means聚类算法步骤随机取k个样本作为初始均值向量(或者采用别的方式获取初始均值向量);根据每个样本与均值向量的距离来判断各个样本所属的蔟。根据分好的蔟再次计算新的均值向量,根据新的均值向量再对每个样本进行划分。循环步骤2,3,直到分类结果...
基于TMS320DM355的四路视频多媒体监控器时间:2009-06-19 13:16:14 来源:国外电子元器件 作者:广州大学华软软件学院 胡伟1 引言 数字视频是利用诸如摄像机的视频捕捉设备采集外界影像信息并将影像信息数字化,然后将其记录到储存介质(如录像带、硬盘、光盘)或通过网络传输。而视频监控在安全防范系统中具有重要意义。这里提出基于TMS