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 容器。

Spring_IOC_02——原理解析_第1张图片
继承关系

BeanFactory

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


Spring_IOC_02——原理解析_第2张图片
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的配置选项 元素配置了 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 根节点 节点提供了一个配置参数 default-lazy-init 默认为false。也就是说,默认时候是不进行延迟加载的,这样的话,在 Spring 容器初始化的时候,就会对bean进行注入。如果设置为true,那整个下面的节点默认进行延迟加载。

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


2)FactoryBean 的实现

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

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

源码如下:

//工厂Bean,用于产生其他对象  
public interface FactoryBean {  
   //获取容器管理的对象实例  
    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文件中


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



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配置文件里打开注解,配置扫描路径:






        




        
        

@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的依赖注入实现

你可能感兴趣的:(Spring_IOC_02——原理解析)