Spring_IOC_02——原理解析

如果对IOC的概念还不是很清楚,可以先阅读上一篇文章:Spring_IOC_01——概念讲解

Spring IOC 体系结构

Spring提供了 IoC容器 来管理和容纳我们所开发出的各种各样的Bean,并且我们可以从中获取各种发布在Spring IoC 容器里的Bean,并且通过描述可以得到它。

Spring IoC 容器设计

Spring IoC 容器主要基于以下两个接口:

  • BeanFactory
  • ApplicationContext

其中,Spring IoC 容器所定义的最底层接口是BeanFactory。它有很多实现类,ApplicationContext就是Spring IOC容器最高级接口之一,它是BeanFactory的其中一个子接口,并且对BeanFactory功能做了很多扩展,所以绝大部分情况下我们都推荐使用 ApplicationContext 作为 Spring IoC 容器。

继承关系

BeanFactory

从上图我们可以看到,BeanFactory位于设计的最底层,它定义了IoC容器的基本规范。我们先看它提供了哪些方法:


BeanFactory

在BeanFactory里,只对IOC容器的基本行为做了定义,而不关系bean是如何定义怎样加载的,正如我们只关系工厂里得到声明的产品对像,而不关心工厂的具体生产操作,从名字也可以看出,这是一个典型的工厂模式。
由于这个接口的重要性,有必要对其中关键接口定义做一下简要说明:

  • getBean:
    对应了多个方法来获取配置给 Spring IoC 容器的Bean。
    1)按照类型获取Bean:
    bean = (Bean) factory.getBean(Bean.class);
    注意:要求在Spring中只配置了一个这种类型的实例,否则会报错。
    2)按照bean的ID获取bean:
    bean = (Bean) factory.getBean("beanName");
    注意:这种方式并不安全,IDE不会检查其安全性(关联性)
    3)根据类型和ID获取bean:(推荐
    bean = (Bean) factory.getBean("beanName", Bean.class);
    这种方式通过ID获取Bean,获取的同时也可以对Bean进行类型检查。

  • isSingleton 和 isPrototype:
    用于判断该Bean的类型,是单例的还是原型的。如果是单例的,则该Bean在容器中是作为唯一一个单例而存在的。如果是原型的,则每次从容器中获取Bean都会生成一个新的实例。默认情况下Spring容器中的Bean都是单例的。(具体bean的作用域接下来会讲)

  • getAliases:
    用于获取别名的方法。

ApplicationContext

根据ApplicationContext 的类继承关系图,可以看到 ApplicationContext 接口扩展了许多接口,因此它的功能十分强大,所以在实际应用中常用到的事 ApplicationContext 接口。

BeanFactory和ApplicationContext的区别

  • BeanFactory:
    是Spring中最底层的接口,只提供了最简单的IoC功能,负责配置,创建和管理Bean。

  • ApplicationContext:
    实际应用中一般推荐使用该接口,它继承自BeanFactory,拥有最基本的IoC功能。
    除此之外,还提供了以下扩展功能
    1)支持信息源,可以实现国际化。(实现了 MessagsSource 接口)
    2)支持统一的资源加载。(实现了 ResourcePatternResolver 接口)
    3)支持消息机制/应用事件。(实现了 ApplicationEventPublisher 接口)
    4)支持AOP功能。



Spring IoC 容器的初始化

Bean的定义和初始化,在Spring IoC 容器中是两个步骤:先定义(BeanDefinition),然后初始化和依赖注入。

  • Bean的定义分为以下3步

1)Resouce定位
Spring IoC 容器根据开发者的配置,扫描资源,进行定位。在 Spring 的开发中,通过XML配置或者注解都是常见的方式,定位的内容是由开发者提供的。

2)BeanDefinition载入
当Resoure定位到信息后,保存到Bean定义(BeanDefinition)中,此时并不会创建Bean的实例。

3)BeanDefinition注册
这个过程,就是将 BeanDefinition 的信息发布到 Spring IoC 容器中。注意:此时依然没有Bean对应的实例。

完成了以上三个步骤,Bean 就在 Spring IoC 容器中被定义了(创建了 Bean 对象的定义类 BeanDefinition,将 Bean 元素的信息设置到 BeanDefinition 中作为记录),而 没有被初始化,更没有完成依赖注入,也就是没有注入到其配置的资源给Bean,此时,它还不能完全被使用。当依赖注入时,才利用这些记录信息,创建和实例化具体的Bean对象


Spring IoC 容器的依赖注入

依赖注入发生的时间

当 Spring IoC 容器完成了Bean定义资源的定位,载入和解析注册以后。IoC容器已经管理了Bean定义的相关数据,但是此时IoC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况发生:

1) 用户第一次通过 getBean 方法向IoC容器索要Bean时,IoC容器会触发依赖注入。

2) 用户在Spring的配置选项 <Bean> 元素配置了 lazy-init 的属性。其含义是:懒加载的方式初始化Bean。在没有配置的情况下,它的默认值为default,在default情况下就是false。也就是说 Spring IoC 默认会自动初始化Bean。即让容器在解析注册Bean定义时,进行预实例化,触发依赖注入。

依赖注入的策略

在 Spring 中,如果 Bean 的作用域定义的是单例模式(Singleton),则容器在创建之前会先从缓存中查找,以确保整个容器中只存在一个实例对象。如果Bean定义的事原型模式(Prototype),则容器每次都会创建一个新的实例对象。
此外,在Spring MVC 中,还可以指定作用域为 Request, Session 和 global Session。(此部分内容在下一期细讲)

实例化方式的选择

对使用工厂方法和自动装备特性的 Bean 的实例化,调用相应的工厂方法或者参数匹配的构造方法即可完成实例化工作,但是对于我们最常使用的默认无参构造方法,就需要使用相应的初始化策略(JDK反射或者CGLIB)来进行初始化。

具体来说,如果Bean的方法被覆盖了,则使用JDK反射机制进行实例化,否则。使用CGLIB进行实例化。(这是由JDK代理和CGLIB代理方式决定的,JDK代理是生成了接口的一个实现类,但是如果没有接口则无法使用)



Spring IoC 高级特性

1)lazy-init 延迟加载

在前面我们了解到,Spring容器的初始化和Bean的依赖注入是分开的。Spring容器在初始化的时候,会对Bean资源进行定位,载入,和注册。但是完成这三步以后,Spring并不会对Bean进行依赖注入。

但是Spring默认配置下,在Spring容器初始化完成以后,会对Bean资源依赖注入,这样可以及时发现Bean存在的问题,当然我们也推荐这样做。(虽然延迟加载可以减少服务器启动时间,但是这样就无法在开始时候就把问题暴露出来)

下面说一下延迟加载的设置:
Spring 根节点 <beans> 节点提供了一个配置参数 default-lazy-init 默认为false。也就是说,默认时候是不进行延迟加载的,这样的话,在 Spring 容器初始化的时候,就会对bean进行注入。如果设置为true,那整个<beans>下面的节点默认进行延迟加载。

<beans>下的<bean>节点也有一个配置参数lazy-init,默认情况下为default 也就是<beans>上面的设置的default-lazy-init。如果在这里设置了的话,优先级比default-lazy-init高,指定Bean延迟加载。


2)FactoryBean 的实现

FactoryBean 接口,以Bean结尾,表示它是一个Bean。注意要和BeanFactory区别。

我们根据Bean的ID,从BeanFactory中获取的对象,实际上是FactoryBean接口getObject()方法返回的对象。

源码如下:

//工厂Bean,用于产生其他对象  
public interface FactoryBean<T> {  
   //获取容器管理的对象实例  
    T getObject() throws Exception;  
    //获取Bean工厂创建的对象的类型  
    Class<?> getObjectType();  
    //Bean工厂创建的对象是否是单态模式,如果是单态模式,则整个容器中只有一个实例  
   //对象,每次请求都返回同一个实例对象  
    boolean isSingleton();  
} 

FactoryBean接口的作用,就是让我们可以封装自己定制的实例化逻辑。(如果想用工厂模式来实例化)然后让Spring进行统一管理。

具体方式就是,我们写一个实现类,实现FactoryBean的方法,那么我们就可以在getObject()方法里实现我们的自定义实例化逻辑。


3)BeanPostProcessor 后置处理器

BeanPostProcessor 接口作用:
如果我们想在Spring容器中,完成Bean实例化,配置,以及其他初始化方法前后,加一些自定义处理逻辑。那就要自己定义BeanPostProcessor 实现类,然后注册到 Spring IoC 容器中。
此处注意:BeanPostProcessor的作用范围是整个Spring容器

Spring源码如下:

package org.springframework.beans.factory.config;  
import org.springframework.beans.BeansException;  
public interface BeanPostProcessor {  
        //实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
        Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;  
        //实例化、依赖注入、初始化完毕时执行  
        Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;  
} 

这两个回调的入口都是和容器管理的Bean的生命周期事件紧密相关,可以为用户提供在 Spring IoC 容器初始化Bean过程中自定义的处理操作。
由API可以看出:
1)后置处理器的postProcessorBeforeInitailization方法,是在容器实例化Bean(实例化和初始化的区别:实例化是为Bean对象在内存中开辟空间,初始化是完成对依赖属性的注入,通过setter等方式),完成依赖的注入之后;显示的调用初始化方法之前调用(afterPropertiesSet和init-method之前)。
2)后置处理器的postProcessorAfterInitailization方法是在bean实例化、依赖注入及自定义初始化方法之后调用。

调用顺序简单示意如下:

--> Spring IOC容器实例化Bean
--> 调用BeanPostProcessor的postProcessBeforeInitialization方法
--> 调用bean实例的初始化方法
--> 调用BeanPostProcessor的postProcessAfterInitialization方法

具体使用方式:

//自定义后置处理器
public class MyPostProcessor implements BeanPostProcessor {
    //do something

    @Override
    Object postProcessBeforeInitialization(...){...}

    @Override
    Object postProcessAfterInitialization(...){...}
}

//然后将自定义的后置处理器配置到xml文件中
<!-- Spring后置处理器 -->
<bean id="myPostProcessor" class="com.test.spring.MyPostProcessor "/>

如果要定义多个BeanPostProcessor实现类,在xml中依次注册即可,默认情况下Spring会依据后置处理器的顺序依次调用。

<bean id="postProcessor" class="com.test.spring.PostProcessor"/>
<bean id="postProcessorB" class="com.test.spring.PostProcessorB"/>

Bean的构造方法,BeanPostProcessor 和 InitializingBean @PostConstruct @PreDestory 的执行关系。在Bean的生命周期中细讲。


4)IoC容器 @Autowired 自动装配

@Autowired 注解提供了 Spring IoC 容器的依赖自动装配功能,不需要对Bean属性的依赖关系在配置文件中显示声明。通过配置该注解,Spring容器会通过反射根据类型查找并注入。
(如果有相同类型,则需要加一个@Qualifier("beanName")根据beanName查找指定的Bean)

原理:Spring利用反射,获取到标记了@Autowired的方法或者参数,然后调用AutowiredAnnotationBeanPostProcessor中的方法,对参数进行注入。(具体源码就补贴了,可以去自行搜索,理解反射即可)

@Autowried 注解可以用于 字段或者 setter上。也可以用于构造方法和普通方法上(前提是方法至少有一个参数)。不过并不建议作用到普通方法上,因为Spring会在初始化该Bean时就调用该方法。

@Autowired 自动装配功能搭配其他注解使用 例如:@Component,@Controller、@Service、@Repository,等。
注意要在Spring配置文件里打开注解,配置扫描路径:

<!-- 支持注解配置 -->
<context:annotation-config/>

<!--自动扫描所有注解路径, 只扫描服务 排除Controller -->
<context:component-scan base-package="com.antony.springdemo">
        <context:exclude-filter type="regex" expression=".*Controller$" />
</context:component-scan>

<!-- 把标记了@Controller注解的类转换为bean,单独转换控制层 -->
<context:component-scan base-package="com.antony.springdemo" use-default-filters="false">
        <!-- 后缀匹配 -->
        <context:include-filter type="regex" expression=".*Controller$"/>
</context:component-scan>

@Autowired 和 @Resource 注解的相同和区别

  • 两者都可以用来装配Bean,都可以用在字段上或者setter上。

  • @Autowired 默认按类型装配,这个注解属于Spring
    默认情况下要求依赖对象必须存在。如果要允许null值,可以设置其属性required=false。例如:@Autowired(required=false)。如果想指定名称。
    可以配合@Qualifier注解来使用。用于相同类型注入了多个Bean时进行区分。@Qualifier("beanName")

  • @Resource 默认按名称装配,是JDK1.6支持的注解。名称可以根据name属性指定。
    如果没有指定name属性,默认取字段名按名称查找,当使用字段名找不到时,按类型查找。
    注意:如果显式指定了name属性@Resource(name="beanName"),那就只会按name指定的名称进行匹配。



(如果有什么错误或者建议,欢迎留言指出)
(本文内容是对各个知识点的转载整理,用于个人技术沉淀,以及大家学习交流用)


参考资料:
简书——Spring IOC详解
源码解读Spring IOC原理
Spring IoC的原理
Spring原理机制

Spring IoC容器高级特性
Spring BeanFactory和FactoryBean区别
Spring中后置处理器BeanPostProcessor详解
BeanPostProcessor与InitializingBean接口的关系和应用
@Autowired和@Resource注解的区别
基于Annotation的依赖注入实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,832评论 25 707
  • 转角 文/苍洱小小生 拥挤的人群,错乱的街道 我在茫茫人海中寻找 寻找有你的转角 你的容颜 文/苍洱小小生 我想用...
    苍洱小小生阅读 374评论 2 4
  • 在之前的章节中,爬取的都是静态页面中的信息,随着越来越多的网站开始用JS在客户端浏览器动态渲染网站,导致很多需要的...
    小怪聊职场阅读 8,192评论 0 11
  • 今天是2018年的元旦,2018来了!昨天已经过去,明天还没有到来,珍惜今天!一切从今天开始! 新的工作还没有消息...
    青青河边草_366a阅读 196评论 0 1
  • 巳二、辨住學(分十六科)午一、不樂利敬【若有成就如是諸法;愛樂正法愛樂功德。】這是第二科。這個「沙門」的「莊嚴」是...
    德虔阅读 233评论 0 1