1 定义bean的方式

常见的定义Bean的方式有:

  • 通过xml的方式,例如:

    <bean id="dictionaryRelMap" class="java.util.HashMap"/>
  • 通过注解的方式,在Class上使用@Component等注解,例如
    @Component
    public class xxxServicer{
    ....
    }
  • 通过在@Configuration类下的@Bean的方式,例如
    @Configuration
    public class xxxConfiguration{
    @Bean
    public myBean myBean(){
    return new myBean();
    }
    }

虽然这三种定义Bean的方式不一样,对应的处理细节也不一样,但是从大的逻辑上来看,都是一样。主要的流程如下图: 最关键的就是问题就是这么去找到定义Bean的方式,然后生成BeanDefinition后注册到Spring上下文中,由Spring自动创建Bean的实例。

2 BeanDefinition

BeanDefinition是一个接口,用来描述一个Bean实例,例如是SINGLETON还是PROTOTYPE,属性的值是什么,构造函数的参数是什么等。简单来说,通过一个BeanDefinition我们就可以完成一个Bean实例化。 BeanDefinition及其主要的子类:

  • RootBeanDefinition和ChildBeanDefinition: 这2个BeanDefinition是相对的关系,自Spring 2.5 出来以后,已经被GenericBeanDefinition代替。因为这样强迫我们在编写代码的时候就必须知道他们之间的关系。
  • GenericBeanDefinition: 相比于RootBeanDefinition和ChildBeanDefinition在定义的时候就必须硬编码,GenericBeanDefinition的优点可以动态的为GenericBeanDefinition设置parent。
  • AnnotatedBeanDefinition:看名字就是知道是用来读取通过注解定义Bean。

3 通过xml文件定义Bean

通过xml定义Bean是最早的Spring定义Bean的方式。因此,怎么把xml标签解析为BeanDefinition(), 入口是在org.springframework.beans.factory.xml.XmlBeanDefinitionReader这个类,但是实际干活的是在org.springframework.beans.factory.xml.BeanDefinitionParserDelegate。代码很多,但实际逻辑很简单,就是解析Spring定义的<bean> <property> 等标签 。 之前写过一篇文章介绍过如何自定义Spring标签 ,并解析后注册到Spring中——传送门

4 通过@Component等Spring支持的注解加载Bean

如果要使用@Component等注解定义Bean,一个前提条件是:有<context:component-scan/>或者@ComponentScan注解。但这2个方式还是有一点点区别:

4.1 <context:component-scan/>

由于<context:component-scan/>是一个xml标签,因此是在解析xml,生成的类org.springframework.context.annotation.ComponentScanBeanDefinitionParser,关键代码:

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
//获取base-package标签
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); // 实际处理类是ClassPathBeanDefinitionScanner
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
//扫描basePackage下所有的类,如果有@Component等标签就是注册到Spring中
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}

4.2 @ComponentScan

注解对应生成的类是org.springframework.context.annotation.ComponentScanAnnotationParser 其实最后实际干活的还是ClassPathBeanDefinitionScanner这个。ComponentScanAnnotationParser类的生成是伴随着@Configuration这个注解处理过程中(意思说@ComponentScan必须和@Configuration一起使用)。而处理@Configuration其实是org.springframework.context.annotation.ConfigurationClassPostProcessor。是不是感觉有点绕。
其实简单来说,在处理@Configuration的时候发现有@ComponentScan注解,就会生成ComponentScanAnnotationParser去扫描@Component注解

4.3 ClassPathBeanDefinitionScanner

上面说到了,无论注解还是标签的方式,最后都会交给ClassPathBeanDefinitionScanner这个类来处理,这个类做的就是1.扫描basePackage下所有class,如果有@Component等注解,读取@Component相关属性,生成ScannedGenericBeanDefinition,注册到Spring中。

5 通过@Bean方式

前面说了@ComponentScan是在@Configuration处理过程中的一环,既然@Bean注解也是必须和@Configuration一起使用,那么说明@Bean的处理也是在@Configuration中,其实最后是交给ConfigurationClassBeanDefinitionReader这个类来处理的,关键代码:

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
TrackedConditionEvaluator trackedConditionEvaluator) { //如果自己是通过@Import注解定义的,那么需要把自己注册到Spring中
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//这里就是处理方法上的@Bean
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//处理@ImportResource,里面解析xml就是上面说到的解析xml的XmlBeanDefinitionReader
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

6 把BeanDefinition实例化

前面分别说了怎么把不同定义Bean的方式转换为BeanDefinition加入到Spring中去(确切来说是保持在BeanFactory的BeanDefinitionMap中),实例化这些BeanDefinition是在ApplicationContext最后阶段,关键代码在DefaultListableBeanFactory中

  @Override
public void preInstantiateSingletons() throws BeansException {
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
if (isFactoryBean(beanName)) {
final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
boolean isEagerInit;
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return ((SmartFactoryBean<?>) factory).isEagerInit();
}
}, getAccessControlContext());
}
else {
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean<?>) factory).isEagerInit());
}
if (isEagerInit) {
getBean(beanName);
}
}
else {
getBean(beanName);
}
}
}
}

通过getBean实例化BeanFactory,代码是在AbstractAutowireCapableBeanFactory中

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {
//处理xxAware接口
if (System.getSecurityManager() != null) {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
invokeAwareMethods(beanName, bean);
return null;
}
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
// 调用BeanPostProcessors#postProcessBeforeInitialization
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
//初始化,先判断是否是InitializingBean,
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
// 调用BeanPostProcessors#postProcessAfterInitialization
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
从上面初始化和注释内容可以看出,InitializeBean接口的方法(以及Init-method)和BeanPostProcessors的调用顺序

7 总结

综上分析,Spring加载Bean其实大的思想都是一样的,先读取相关信息生成BeanDefinition,然后通过BeanDefinition初始化Bean。如果知道了上面了套路以后,就可以清楚怎么自定义Xml标签或者自定义注解向Spring中注入Bean。

 

Spring多种加载Bean方式简析的更多相关文章

  1. spring Boot加载bean

    1.SpringBoot中加载bean,可以使用注解@compenent直接加载到applicationContext容器中 2.在直接类@Configuration中,手动注册bean,如:

  2. spring加载bean流程解析

    spring作为目前我们开发的基础框架,每天的开发工作基本和他形影不离,作为管理bean的最经典.优秀的框架,它的复杂程度往往令人望而却步.不过作为朝夕相处的框架,我们必须得明白一个问题就是sprin ...

  3. Spring中加载xml配置文件的六种方式

    Spring中加载xml配置文件的六种方式 博客分类: Spring&EJB XMLSpringWebBeanBlog  因为目前正在从事一个项目,项目中一个需求就是所有的功能都是插件的形式装 ...

  4. Spring中加载配置文件的方式

    原文:http://blog.csdn.net/snowjlz/article/details/8158560 Spring 中加载XML配置文件的方式,好像有3种, XML是最常见的Spring 应 ...

  5. Spring中加载ApplicationContext.xml文件的方式

    Spring中加载ApplicationContext.xml文件的方式 原文:http://blog.csdn.net/snowjlz/article/details/8158560 1.利用Cla ...

  6. 【死磕 Spring】----- IOC 之 加载 Bean

    原文出自:http://cmsblogs.com 先看一段熟悉的代码: ClassPathResource resource = new ClassPathResource("bean.xm ...

  7. spring的配置文件在web.xml中加载的方式

    web.xml加载spring配置文件的方式主要依据该配置文件的名称和存放的位置不同来区别,目前主要有两种方式. 1.如果spring配置文件的名称为applicationContext.xml,并且 ...

  8. spring加载Bean的解析过程(二)

    1.例如: BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring.xml")); User user ...

  9. spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件)转

    关键字:spring容器加载完毕做一件事情(利用ContextRefreshedEvent事件) 应用场景:很多时候我们想要在某个类加载完毕时干某件事情,但是使用了spring管理对象,我们这个类引用 ...

随机推荐

  1. webStorm Linux Ubuntu 中文搜狗输入问题

    1 打开安装路径下bin/webstorm.sh vim ~/WebStorm-145.597.6/bin/webstorm.sh 2.在打开文件的最前面加入如下代码: export XMODIFIE ...

  2. 搭建ftp服务器实现文件共享

    FTP服务器(File Transfer Protocol Server)是在互联网上提供文件存储和访问服务的计算机,它们依照FTP协议提供服务. FTP(File Transfer Protocol ...

  3. 用JavaScript和jQuery实现瀑布流

    ▓▓▓▓▓▓ 大致介绍 在慕课网上学习了用原生js和jQuery实现瀑布流,在这里做个笔记 ▓▓▓▓▓▓ 用JavaScript实现 基本结构: <div id="main" ...

  4. [C++]现行的试卷封面并获取学生题目得分信息以及学号信息的原型系统

    大二的时候写的一个CV小玩意,最终决定还是把它放出来,也许会帮助到很多人,代码写的很丑,大家多多包涵.附加实验报告主要部分. 课题背景及意义: 本项目主要目标是设计一套能自动分析我校现行的试卷封面并获 ...

  5. 重启osd服务失败:Start request repeated too quickly

    背景 OS:Ubuntu 16.04 修改了osd的一些配置,修改后,需要重启osd服务才能生效.第一次重启后,配置立刻生效.再改了一些配置,重启osd服务后,配置却不再生效了.ps命令查看进程,发现 ...

  6. cstring头文件函数解析

    原创作品,转载请注明来源:http://www.cnblogs.com/shrimp-can/p/5643829.html 在使用由字符数组或指针组成的字符串的时候,要用到一些函数,这些函数通常包含在 ...

  7. MYSQL数据库-其他

    FROM:实验楼 索引: 当表中有大量记录时,若要对表进行查询,没有索引的情况是全表搜索.而如果在表中已建立索引,在索引中找到符合查询条件的索引值,通过索引值就可以快速找到表中的数据. 建立索引: $ ...

  8. 学习Redux之分析Redux核心代码分析

    1. React,Flux简单介绍 学习React我们知道,React自带View和Controller库,因此,实现过程中不需要其他任何库,也可以独立开发应用.但是,随着应用程序规模的增大,其需要控 ...

  9. Memcache存储机制与指令汇总

    1.memcache基本简介 memcached是高性能的分布式内存缓存服务器.一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度.提高可扩展性. Memcach ...

  10. servlet与jsp的区别(转)

    一.基本概念 1.1 Servlet Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面.它担当客户请求(Web浏览器或其他HTTP客户程序)与服务器 ...