Spring IOC原理总结
依赖注入流程
这个博主通过磨咖啡的故事,生动讲解了注入流程,将生产咖啡总结为两个阶段:
- 采摘和收集“咖啡豆”(bean)
- 研磨和烹饪咖啡
阶段一:收集和注册
第一阶段可以认为是构建和收集bean定义的阶段,在这个阶段中,我们可以通过XML或者Java代码的方式定义一些bean,然后通过手动组装或者让容器基于某些机制自动扫描的形式,将这些bean定义收集到IOC容器中。
假设我们以XML配置的形式来收集并注册单一bean,一般形式如下:
<bean id="mockService" class="..MockServiceImpl"> ... </bean>
如果嫌逐个收集bean定义麻烦,想批量地收集并注册到IOC容器中,我们也可以通过XML Schema形式的配置进行批量扫描并采集和注册:
<context:component-scan base-package="com.keevol">
阶段二:分析和组装
当第一阶段工作完成后,我们可以先暂且认为IOC容器中充斥着一个个独立的bean,它们之间没有任何关系。但实际上,它们之间是有依赖关系的,所以,IOC容器在第二阶段要干的事情就是分析这些已经在IOC容器之中的bean,然后根据它们之间的依赖关系先后组装它们。如果IOC容器发现某个bean依赖另一个bean,它就会将这另一个bean注入给依赖它的那个bean,直到所有到bean的依赖都注入完成,所有bean都“整装待发”,整个IOC容器都工作即算完成。
至于分析和组装的依据,Spring框架最早是通过XML配置文件的形式来描述bean与bean之间的关系,随着Java业界研发技术和理念都转变,基于Java代码和Annotation元信息的描述方式也日渐兴盛(比如@Autowired和@Inject),但不管使用哪种方式,都只是为了简化绑定逻辑描述的各种“表象”,最终都是为本阶段都最终目的服务。
这个讲解只是给大家对IOC有一个初步的认识,下面才是核心的部分。
IOC容器的原理
IOC容器其实就是一个大工厂,它用来管理我们所有的对象以及依赖关系。
- 原理就是通过Java的反射技术来实现的!通过反射我们可以获取类的所有信息(成员变量、类名等等等)!
- 再通过配置文件(xml)或者注解来描述类与类之间的关系
- 我们就可以通过这些配置信息和反射技术来构建出对应的对象和依赖关系了!
上面描述的技术只要学过点Java的都能说出来,我们简单来看看实际Spring IOC容器是怎么实现对象的创建和依赖的:
- 根据Bean配置信息在容器内部创建Bean定义注册表
- 根据注册表加载、实例化bean、建立Bean与Bean之间的依赖关系
- 将这些准备就绪的Bean放到Map缓存池中,等待应用程序调用
Spring容器(Bean工厂)可简单分成两种:
- BeanFactory:这是最基础、面向Spring的
- ApplicationContext:这是在BeanFactory基础之上,面向使用Spring框架的开发者。提供了一系列的功能!
几乎所有的应用场合都是使用ApplicationContext!
BeanFactory vs ApplicationContext
BeanFactory
BeanFactory 是 Spring 的“心脏”。它就是 Spring IoC 容器的真面目。Spring 使用 BeanFactory 来实例化、配置和管理 Bean。
BeanFactory:是IOC容器的核心接口, 它定义了IOC的基本功能,我们看到它主要定义了getBean方法。getBean方法是IOC容器获取bean对象和引发依赖注入的起点。方法的功能是返回特定的名称的Bean。
BeanFactory 是初始化 Bean 和调用它们生命周期方法的“吃苦耐劳者”。注意,BeanFactory 只能管理单例(Singleton)Bean 的生命周期。它不能管理原型(prototype,非单例)Bean 的生命周期。这是因为原型 Bean 实例被创建之后便被传给了客户端,容器失去了对它们的引用。
BeanFactory有着庞大的继承、实现体系,有众多的子接口、实现类。来看一下BeanFactory的基本类体系结构(接口为主):
下面写了一大堆,我觉得仅供了解即可:
- BeanFactory作为一个主接口不继承任何接口,暂且称为一级接口。
- 有3个子接口继承了它,进行功能上的增强。这3个子接口称为二级接口。
- ConfigurableBeanFactory可以被称为三级接口,对二级接口HierarchicalBeanFactory进行了再次增强,它还继承了另一个外来的接口SingletonBeanRegistry
- ConfigurableListableBeanFactory是一个更强大的接口,继承了上述的所有接口,无所不包,称为四级接口。 (这4级接口是BeanFactory的基本接口体系。继续,下面是继承关系的2个抽象类和2个实现类:)
- AbstractBeanFactory作为一个抽象类,实现了三级接口ConfigurableBeanFactory大部分功能。
- AbstractAutowireCapableBeanFactory同样是抽象类,继承自AbstractBeanFactory,并额外实现了二级接口AutowireCapableBeanFactory
- DefaultListableBeanFactory继承自AbstractAutowireCapableBeanFactory,实现了最强大的四级接口ConfigurableListableBeanFactory,并实现了一个外来接口BeanDefinitionRegistry,它并非抽象类。
- 最后是最强大的XmlBeanFactory,继承自DefaultListableBeanFactory,重写了一些功能,使自己更强大。
再来看一下BeanFactory的源码:
public interface BeanFactory { /** * 用来引用一个实例,或把它和工厂产生的Bean区分开,就是说,如果一个FactoryBean的名字为a,那么,&a会得到那个Factory */ String FACTORY_BEAN_PREFIX = "&"; /* * 四个不同形式的getBean方法,获取实例 */ Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; Object getBean(String name, Object... args) throws BeansException; boolean containsBean(String name); // 是否存在 boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否为单实例 boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否为原型(多实例) boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException;// 名称、类型是否匹配 Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 获取类型 String[] getAliases(String name);// 根据实例的名字获取实例的别名 }
具体:
- 4个获取实例的方法。getBean的重载方法。
- 4个判断的方法。判断是否存在,是否为单例、原型,名称类型是否匹配。
- 1个获取类型的方法、一个获取别名的方法。根据名称获取类型、根据名称获取别名。一目了然!
总结:这10个方法,很明显,这是一个典型的工厂模式的工厂接口。
看一个简单的示例:
@Data public class Cat implements Animal { private String catName = "罗小黑"; } @Data public class Pets { @Resource private Cat cat; public static void main(String args[]) { ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); org.springframework.core.io.Resource res = resolver.getResource("classpath:applicationContext.xml"); BeanFactory factory = new XmlBeanFactory(res); Pets pets=factory.getBean("pets", Pets.class); System.out.println(pets.toString()); } } // 输出: // Pets(cat=Cat(catName=罗小黑))
这里需要到applicationContext.xml添加一下配置:
<bean id="pets" class="com.java.annotation.spring.bean.test4.Pets" > <property name="cat" ref="cat" /> </bean> <bean id="cat" class="com.java.annotation.spring.bean.test4.Cat" />
解读一下:
- XmlBeanFactory通过Resource装载Spring配置信息冰启动IoC容器,然后就可以通过factory.getBean从IoC容器中获取Bean了。
- 通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一个调用时。
- 对于单实例(singleton)的Bean来说,BeanFactory会缓存Bean实例,所以第二次使用getBean时直接从IoC容器缓存中获取Bean。
这种方式非常不建议使用,因为我用这种方式,发现添加的@Service注解不能生效.
ApplicationContext
如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置实现。
BeanFactorty接口提供了配置框架及基本功能,但是无法支持spring的aop功能和web应用。而ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。而且ApplicationContext还在功能上做了扩展,相较于BeanFactorty,ApplicationContext还提供了以下的功能:
- MessageSource, 提供国际化的消息访问
- 资源访问,如URL和文件
- 事件传播特性,即支持aop特性
- 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层
ApplicationContext:是IOC容器另一个重要接口, 它继承了BeanFactory的基本功能, 同时也继承了容器的高级功能,如:MessageSource(国际化资源接口)、ResourceLoader(资源加载接口)、ApplicationEventPublisher(应用事件发布接口)等。
ApplicationContext 继承了 HierarchicalBeanFactory 和 ListableBeanFactory 接口,在此基础上,还通过多个其他的接口扩展了 BeanFactory 的功能:
下面都是八股文,也是仅作了解即可:
- ClassPathXmlApplicationContext:默认从类路径加载配置文件
- FileSystemXmlApplicationContext:默认从文件系统中装载配置文件 ApplicationEventPublisher:让容器拥有发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件 , 并对事件进行响应处理 。在 ApplicationContext 抽象实现类AbstractApplicationContext 中,我们可以发现存在一个 ApplicationEventMulticaster,它负责保存所有监听器,以便在容器产生上下文事件时通知这些事件监听者。
- MessageSource:为应用提供 i18n 国际化消息访问的功能;
- ResourcePatternResolver :所 有 ApplicationContext 实现类都实现了类似于PathMatchingResourcePatternResolver 的功能,可以通过带前缀的 Ant 风格的资源文件路径装载 Spring 的配置文件。
- LifeCycle:该接口是 Spring 2.0 加入的,该接口提供了 start()和 stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现, ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。
- ConfigurableApplicationContext 扩展于 ApplicationContext,它新增加了两个主要的方法:refresh()和 close(),让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用 refresh()即可启动应用上下文,在已经启动的状态下,调用 refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文。这些接口方法为容器的控制管理带来了便利,但作为开发者,我们并不需要过多关心这些方法。
还是看一下ClassPathXmlApplicationContext的使用姿势:
ApplicationContext context =new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); //ApplicationContext context =new ClassPathXmlApplicationContext("file:/Users/mengloulv/java-workspace/Demo5/src/main/resources/applicationContext.xml"); Pets pets=context.getBean("pets", Pets.class); System.out.println(pets.toString());
再看一下FileSystemXmlApplicationContext的使用姿势:
ApplicationContext context =new FileSystemXmlApplicationContext("file:/Users/mengloulv/java-workspace/Demo5/src/main/resources/applicationContext.xml"); Pets pets=context.getBean("pets", Pets.class); System.out.println(pets.toString());
个人还是偏向使用ClassPathXmlApplicationContext,可以使用相对路径。
后面还有个WebApplicationContext,反正我是用的少,就不介绍了。
BeanFactory和ApplicationContext的区别
1.BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化,这样,我们就不能发现一些存在的Spring的配置问题。而ApplicationContext则相反,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误。相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
BeanFacotry延迟加载,如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常;而ApplicationContext则在初始化自身是检验,这样有利于检查所依赖属性是否注入;所以通常情况下我们选择使用 ApplicationContext。应用上下文则会在上下文启动后预载入所有的单实例Bean。通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。
2.BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。(Applicationcontext比 beanFactory 加入了一些更好使用的功能。而且 beanFactory 的许多功能需要通过编程实现而 Applicationcontext 可以通过配置实现。比如后处理 bean , Applicationcontext 直接配置在配置文件即可而 beanFactory 这要在代码中显示的写出来才可以被容器识别。)
3.beanFactory主要是面对与 spring 框架的基础设施,面对 spring 自己。而 Applicationcontex 主要面对与 spring 使用的开发者。基本都会使用 Applicationcontex 并非 beanFactory 。