在Spring中,它会认为一切Java类都是资源,而资源都是Bean,容纳这些Bean的是Spring所提供的IoC容器(所以Spring是一种基于Bean的编程)。
Spring IoC概述
控制反转是一种通过描述(在Java中可以是XML或者是注解)并通过第三方去产生或获取特定对象的方式。而在Spring中实现控制反转的是IoC容器,其实现方式是依赖注入。
控制反转的思想在理解上是有一定的困难的,接下来我们通过一个现实中的例子来尝试解释一下:
现实系统的开发者是一个团队,团队由许多开发者组成。现在假设你在一个电商网站负责开发工作,你熟悉商品交易流程,但是对财务处理却不怎么熟悉,而团队中有些成员对于财务处理十分熟悉,在交易的过程中,商品交易流程需要调度财务的相关接口,才能得以实现,那么你期望的应该是:
- 熟悉财务流程的成员开发对应的接口。
- 接口逻辑尽量简单,内部复杂的业务逻辑并不需要自己去了解,你只要通过简单的调用就能使用。
- 通过简单的描述就能获取这个接口实例,且描述应该尽量简单。
到这里有一个事实需要注意,财务接口对象的创建并不是自己的行为,而是财务开发同事的行为,但也完全达到了你的要求,而在潜意识里你会觉得对象应该由你主动创建,但事实上这并不是你真实的需要,也许你对这一领域并不精通,这个时候可以把创建对象的主动权转交别人,这就是控制反转的概念。
这理念的一个坏处是理解上的困难,但是它最大的好处在于降低对象之间的耦合,在一个系统中有些类,具体如何实现并不需要去理解,只需要知道它有什么用就可以了。只是这里对象的产生依靠于IoC容器,而不是开发者主动的行为。
Spring IoC的原理实现
原生的 JavaEE 技术中各个模块之间的联系较强,即耦合度较高
。而 Spring 框架的核心–IoC(控制反转)很好的解决了这一问题。
接下来我们通过在web层创建业务层的一个类,讲述一下Spring IoC的原理实现。
开始我们先直接通过UserService创建一个类:
1
UserService us = new UserService();
但我们都知道这种方式不好,因为它没有面向接口编程,所以接下来我们选择面向接口编程:
1
UserService us = new UserServiceImpl();
但由于我们在web层直接创建了接口的实现类,那这样业务层就和web层产生耦合了。这时候我们就需要提及ocp原则(开闭原则)了
open-close原则:对程序扩展是open的,对修改程序代码是close的。(尽量不修改程序的源码,实现对程序的扩展)
- 我们可以想到使用一个设计模式:工厂模式。
现在我们就可以通过工厂类来创建UserService的实例对象:1
2
3
4
5
6class FactoryBean{
public static UserService getUs(){
return new UserServiceImpl();
}
//...
}
这样我们的接口和实现类就没有耦合了,但接口和工厂类就会产生耦合1
UserService us = FactoryBean.getUs();
- 那么这里我们就可以通过工厂+反射+配置文件来实现解耦合:
1
<bean id="us" class="com.Kyrie.UserServiceImpl"/>
1 | class FactoryBean(){ |
我们在getBean中传入一个id,那就会返回一个class,接着我们通过反射去生成我们一个实例化对象。
这就是Spring来完成解耦合的思想。
IoC容器
通过上面我们知道了Spring IoC容器的作用,它可以容纳我们所开发的各种Bean,并且我们可以从中获取各种发布在Spring IoC容器里的Bean,并且通过描述可以得到它。
IoC容器的设计
Spring IoC容器的设计主要是基于Bean Factory和ApplicationContext两个接口,其中
ApplicationContext是Bean Factory的子接口之一,ApplicationContext对Bean Factory功能做了很多有用的拓展,所以大多数的工作场景下,都会使用ApplicationContext作为Spring IoC容器。
下图展示的是Spring相关的IoC容器接口的主要设计。
我们可以清晰地看到BeanFactory位于设计的最底层,它提供了Spring IoC最底层的设计,所以我们来看看它的源码:
1 | package org.springframework.beans.factory; |
接下来我们来认识一个ApplicationContext的子类–ClassPathXmlApplicationContext,先创建一个applicationContext.xml文件:1
2
3
4
5
6
7
8<beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="com.Kyrie.ioc.demo1.UserServiceImpl">
<property name="name" value="李四"/>
</bean>
</beans>
这里定义了一个Bean,这样Spring IoC在初始化的时候就能找到它,然后使用ClassPathXmlApplicationContext容器就可以将其初始化,代码如下:1
2
3
4
5
6
7//创建工厂类
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
//通过工厂获得类
UserService userService = (UserService) beanFactory.getBean("userService");
userService.sayHello();
这样就会使用Application的实现类ClassPathXmlApplicationContext去初始化Spring IoC容器,然后开发者就可以通过IoC容器获得资源了。
Spring IoC容器的初始化和依赖注入
Bean的定义和初始化在Spring IoC容器中是两大步骤,它是先定义,然后初始化和依赖注入的。
Bean的定义分为3步
1.Resource定位,Spring IoC根据开发者的配置,进行资源定位。在Spring的开发中,通过XML或者注解都是十分常见的形式,定位的内容由开发者提供。
2.BeanDefinition的载入,这个时候只是将Resource定位到的信息,保存到Bean定义(BeanDefinition)中,此时并不会创建Bean的实例。
3.BeanDefinition的注册,这个过程就是将BeanDefinition的信息发布到Spring IoC中,要注意的是,此时仍旧没有对应的Bean实例创建。
初始化和依赖注入
做完上述3步,Bean就在Spring IoC中被定义了,而没有被初始化。对于初始化和依赖注入,Spring Bean还有一个配置选项–lazy-init,其含义是是否初始化Spring Bean。在没有任何配置的情况下,它的默认值为default,实际值为false,也就是Spring IoC默认会初始化Bean。如果将其设置为true,那么只有我们使用Spring IoC容器的getBean方法获取它时,它才会进行Bean的初始化,完成依赖注入。
Spring Bean的生命周期
生命周期主要是为了了解Spring IoC容器初始化和销毁Bean的过程,通过对它的学习就可以知道如何在初始化和销毁的时候加入自定义方法。