如果对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里,只对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的依赖注入实现