Spring详解2.理解IoC容器

45次阅读

共计 14321 个字符,预计需要花费 36 分钟才能阅读完成。

点击进入我的博客
1 如何理解 IoC
1.1 依然是 KFC 的案例
interface Burger {
int getPrice();
}
interface Drink {
int getPrice();
}

class ZingerBurger implements Burger {
public int getPrice() {
return 10;
}
}
class PepsiCola implements Drink {
public int getPrice() {
return 5;
}
}

/**
* 香辣鸡腿堡套餐
*/
class ZingerBurgerCombo {
private Burger burger = new ZingerBurger();
private Drink drink = new PepsiCola();

public int getPrice() {
return burger.getPrice() + drink.getPrice();
}
}
上述案例中我们实现了一个香辣鸡腿堡套餐,在 ZingerBurgerCombo 中,我们发现套餐与汉堡、套餐与饮品产生了直接的耦合。要知道肯德基中的套餐是非常多的,这样需要建立大量不同套餐的类;而且如果该套餐中的百事可乐如果需要换成其他饮品的话,是不容易改变的。
class KFCCombo {
private Burger burger;
private Drink drink;

public KFCCombo(Burger burger, Drink drink) {
this.burger = burger;
this.drink = drink;
}
}

class KFCWaiter {
public KFCCombo getZingerBurgerCombo() {
return new KFCCombo(new ZingerBurger(), new PepsiCola());
}
// else combo…
}
为了防止套餐和汉堡、饮品的耦合,我们统一用 KFCCombo 来表示所有的套餐组合,引入了一个新的类 KFCWaiter,让她负责所有套餐的装配。
1.2 控制反转与依赖注入
控制反转 IoC
IoC 的字面意思是控制反转,它包括两部分的内容:

控制:在上述案例中,控制就是选择套餐中汉堡和饮品的控制权。
反转:反转就是把该控制权从套餐本身中移除,放到专门的服务员手中。

对于 Spring 来说,我们通过 Spring 容器管理来管理和控制 Bean 的装配。
依赖注入
由于 IoC 这个重要的概念比较晦涩隐讳,Martin Fowler 提出了 DI(Dependency Injection,依赖注入)的概念用以代替 IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。
Spring 容器
Spring 就是一个容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入的工作。让开发着可以从这些底层实现类的实例化、依赖关系装配等工作中解脱出来,专注于更有意义的业务逻辑开发。
2 IoC 的类型
从注入方法上看,IoC 可以分为:构造函数注入、属性注入、接口注入
构造函数注入
class KFCCombo {
private Burger burger;
private Drink drink;

// 在此注入对应的内容
public KFCCombo(Burger burger, Drink drink) {
this.burger = burger;
this.drink = drink;
}
}
属性注入
class KFCCombo {
private Burger burger;
private Drink drink;

// 在此注入对应的内容
public void setBurger(Burger burger) {
this.burger = burger;
}

// 在此注入对应的内容
public void setDrink(Drink drink) {
this.drink = drink;
}
}
接口注入
interface InjectFood {
void injectBurger(Burger burger);
void injectDrink(Drink drink);
}

class KFCCombo implements InjectFood {
private Burger burger;
private Drink drink;

// 在此注入对应的内容
public void injectBurger(Burger burger) {
this.burger = burger;
}
// 在此注入对应的内容
public void injectDrink(Drink drink) {
this.drink = drink;
}
}
接口注入和属性注入并无本质的区别,而且还增加了一个额外的接口导致代码增加,因此不推荐该方式。
3 资源访问
JDK 提供的访问资源的类(如 java.net.URL、File 等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者 Web 容器上下文中获取资源的操作类。因此,Spring 提供了 Resource 接口,并由此装载各种资源,包括配置文件、国际化属性文件等资源。
3.1 Resource 类图

WritableResource:可写资源接口,Spring3.1 新增的接口,有 2 个实现类:FileSystemResource 和 PathResource。
ByteArrayResource:二进制数组表示的资源,二进制数组资源可以在内存中通过程序构造。
ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示。
FileSystemResouce:文件系统资源,资源以文件系统路径的方式表示。
InputStreamResource:以输入流返回标识的资源
ServletContextResource:为访问 Web 容器上下文中的资源而设计的类,负责以相对于 Web 应用根目录的路径来加载资源。支持以流和 URL 的形式访问,在 war 包解包的情况下,也可以通过 File 方式访问。该类还可以直接从 JAR 包中访问资源。
UrlResource:封装了 java.net.URL,它使用户能够访问任何可以通过 URL 表示的资源,如文件系统的资源、HTTP 资源、FTP 资源
PathResource:Spring 4.0 提供的读取资源文件的新类。PathResource 封装了 java.net.URL、java.nio.file.Path、文件系统资源,它使用户能够访问任何可以通过 URL、Path、系统文件路径标识的资源,如文件系统的资源、HTTP 资源、FTP 资源

3.2 资源加载
为了访问不同类型的资源,Resource 接口下提供了不同的子类,这造成了使用上的不便。Spring 提供了一个强大的加载资源的方式,在不显示使用 Resource 实现类的情况下,仅通过不同资源地址的特殊标示就可以访问对应的资源。

地址前缀
实例
释义

classpath:
classpath:com/ankeetc/spring/Main.class
从类不经中加载资源,classpath: 和 classpath:/ 是等价的,都是相对于类的根路径,资源文件可以在标准的文件系统中,也可以在 jar 或者 zip 的类包中

file:
file:/Users/zhengzhaoxi/.gitconfig
使用 UrlResource 从文件系统目录中装载资源,可以采用绝对路径或者相对路径

http://
http://spiderlucas.github.io/…
使用 UrlResource 从 web 服务器中加载资源

ftp://
ftp://spiderlucas.github.io/atom.xml
使用 UrlResource 从 FTP 服务器中装载资源

没有前缀
com/ankeetc/spring/Main.class
根据 ApplicationContext 的具体实现类采用对应类型的 Resource

classpath: 与 classpath*:
假设有多个 Jar 包或者文件系统类路径下拥有一个相同包名(如 com.ankeetc):

classpath:只会在第一个加载的 com.ankeetc 包的类路径下查找
classpath*:会扫描到所有的这些 JAR 包及类路径下出现的 com.ankeetc 类路径。

这对于分模块打包的应用非常有用,假设一个应用分为 2 个模块,每一个模块对应一个配置文件,分别为 module1.yaml、module2.yaml,都放在了 com.ankeetc 的目录下,每个模块单独打成 JAR 包。可以使用 classpath*:com/ankeetc/module*.xml 加载所有模块的配置文件。
Ant 风格的资源地址通配符

? 匹配文件名中的一个字符

* 匹配文件名中的任意字符

** 匹配多层路径

资源加载器

ResourceLoader 接口仅有一个 getResource(String loacation) 方法,可以根据一个资源地址加载文件资源。不过,资源地址仅支持带资源类型前缀的表达式,不支持 Ant 风格的资源路径表达式。

ResourcePatternResolver 扩展 ResourceLoader 接口,定义了一个新的接口方法 getResource(String locationPattern),改方法支持带资源类型前缀及 Ant 风格的资源路径表达式。

PathMatchingResourcePatternResolver 是 Spring 提供的标准实现类。

4 BeanFactory

BeanFactory 是 Spring 框架最核心的接口,它提供了高级 IoC 的配置机制。我们一般称 BeanFactory 为 IoC 容器。
BeanFactory 是 Spring 框架等基础设施,面向 Spring 本身。
BeanFactory 是一个类工厂,可以创建并管理各种类的对象。
所有可以被 Spring 容器实例化并管理的 Java 类都可以成为 Bean。
BeanFactory 主要方法就是 getBean(String beanName);
BeanFactory 启动 IoC 容器时,并不会初始化配置文件中定义的 Bean,初始化动作在第一个调用时产生。
对于单实例的 Bean 来说,BeanFactory 会缓存 Bean 实例,在第二次使用 geBean() 获取 Bean 时,将直接从 IoC 容器的缓存中获取 Bean 实例。

4.1 BeanFactory 体系结构

BeanFactory:BeanFactory 接口位于类结构树的顶端,它最主要的方法就是 getBean(String beanName),该方法从容器中返回特定名称的 Bean,BeanFactory 的功能通过其他接口而得到不断扩展。
ListableBeanFactory:该接口定义了访问容器中 Bean 基本信息的若干方法,如:查看 Bean 的个数、获取某一类型 Bean 的配置名、或查看容器中是否包含某一个 Bean。
HierarhicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器。
ConfigurableBeanFactory:该接口增强了 IoC 容器的可定制性,它定义了设置类装载器、属性 编辑器、容器初始化后置处理器等方法。
AutowireCapableBeanFactory:定义了将容器中的 Bean 按某种规则(如:按名称匹配、按类型匹配)进行自动装配的方法。
SingletonBeanFactory:定义了允许在运行期间向容器注册单实例 Bean 的方法。
BeanDefinitionRegistry:Spring 配置文件中每一个 Bean 节点元素在 Spring 容器里都通过一个 BeanDefinition 对象表示,它描述了 Bean 的配置信息。而 BeanDefinitionRegistry 接口提供了向容器手工注册 BeanDefinition 对象的方法。

4.2 初始化 BeanFactory
BeanFactory 有多种实现,最常用的是 XmlBeanFactory,但在 Spring 3.2 时已被废弃。目前建议使用 XmlBeanDefinitionReader 与 DefaultListableBeanFactory。
<?xml version=”1.0″ encoding=”utf-8″ ?>
<beans xmlns=”http://www.springframework.org/schema/beans”
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-4.0.xsd”>
<bean id=”car” class=”com.ankeetc.spring.Car”></bean>
</beans>
public static void main(String[] args) throws Exception {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource(“beans.xml”);

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);

beanFactory.getBean(“”);
}
4.3 BeanFactory 中 Bean 的生命周期

当调用者通过 getBean() 向容器请求一个 Bean 时,如果容器注册了 InstantiationAwareBeanPostProcessor 接口,则在实例化 Bean 之前,调用 postProcessBeforeInstantiation() 方法。
根据配置调用构造方法或者工厂方法实例化 Bean。
调用 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()。
调用 InstantiationAwareBeanPostProcessor#postProcessPropertyValues() 方法。
设置属性值。
如果 Bean 实现了 BeanNameAware 接口,则将调用 BeanNameAware#setBeanName() 接口方法,将配置文件中该 Bean 对应的名称设置到 Bean 中。
如果 Bean 实现了 BeanFactoryAware 接口,将调用 BeanFactoryAware#setBeanFactory() 接口方法。
如果容器注册了 BeanPostProcessor 接口,将调用 BeanPostProcessor#postProcessBeforeInitialization() 方法。入參 Bean 是当前正在处理的 Bean,BeanName 是当前 Bean 的配置名,返回的对象为处理后的 Bean。BeanPostProcessor 在 Spring 框架中占有重要的地位,为容器提供对 Bean 进行后续加工处理的切入点,AOP、动态代理都通过 BeanPostProcessor 来实现。
如果 Bean 实现了 InitializingBean 接口,则将调用 InitializingBean#afterPropertiesSet() 方法。
如果 <bean> 中定义了 init-method 初始化方法,则执行这个方法。
调用 BeanPostProcessor#postProcessAfterInitialization() 方法再次加工 Bean。
如果 <bean> 中指定了 Bean 的作用范围为 scope=’prototype’,则将 Bean 返回给调用者,Spring 不再管理这个 Bean 的生命周期。如果 scope=’singleton’,则将 Bean 放入 Spring IoC 容器的缓存池中,并返回 Bean。
对于 scope=’singleton’ 的 Bean,当容器关闭时,将触发 Spring 对 Bean 的后续生命周期的管理工作。如果 Bean 实现了 DisposableBean 接口,将调用 DisposableBean#destroy() 方法。
对于 `scope=’singleton’ 的 Bean,如果通过 <bean> 的 destroy-method 属性指定了 Bean 的销毁方法,那么 Spring 将执行这个方法。

Bean 方法的分类

Bean 自身的方法:构造方法、Setter 设置属性值、init-method、destroy-method。

Bean 级生命周期接口方法:如上图中蓝色的部分。BeanNameAware、BeanFactoryAware、InitializingBean、DisposableBean,这些由 Bean 本身直接实现。

容器级生命周期接口方法:如上图中的红色的部分。InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 这两个接口,一般称它们的实现类为后处理器。后处理器接口不由 Bean 本身实现,实现类以容器附加装置的形式注册到 Spring 容器中。当 Spring 容器创建任何 Bean 的时候,这些后处理器都会起作用,所以这些后处理器的影响是全局的。如果需要多个后处理器,可以同时实现 Ordered 接口,容器将按照特定的顺序依此调用这些后处理器。

工厂后处理器接口方法:AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor 等方法,工厂后处理器也是容器级的,在应用上下文装配配置文件后立即使用。

4.4 BeanFactory 生命周期案例
public class Main {
public static void main(String[] args) {
Resource resource = new PathMatchingResourcePatternResolver().getResource(“classpath:beans.xml”);

DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);

beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
System.out.println(“InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()”);
return null;
}

public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
System.out.println(“InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()”);
return true;
}

public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
System.out.println(“InstantiationAwareBeanPostProcessor.postProcessPropertyValues()”);
return pvs;
}

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println(“BeanPostProcessor.postProcessBeforeInitialization()”);
return bean;
}

public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println(“BeanPostProcessor.postProcessAfterInitialization()”);
return bean;
}
});

MyBean myBean = beanFactory.getBean(“myBean”, MyBean.class);
beanFactory.destroySingletons();
}
}

class MyBean implements BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean {
private String prop;

public MyBean() {
System.out.println(“MyBean: 构造方法 ”);
}

public String getProp() {
System.out.println(“MyBean:get 方法 ”);
return prop;
}

public void setProp(String prop) {
System.out.println(“MyBean:set 方法 ”);
this.prop = prop;
}

public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println(“MyBean:BeanFactoryAware.setBeanFactory()”);
}

public void setBeanName(String name) {
System.out.println(“MyBean:BeanNameAware.setBeanName()”);
}

public void destroy() throws Exception {
System.out.println(“MyBean:DisposableBean.destroy()”);
}

public void afterPropertiesSet() throws Exception {
System.out.println(“MyBean:InitializingBean.afterPropertiesSet()”);
}

// 配置文件中 init-method
public void myInit() {
System.out.println(“MyBean:myInit()”);
}

// 配置文件中 destroy-method
public void myDestroy() {
System.out.println(“MyBean:myDestroy()”);
}
}
<beans xmlns=”http://www.springframework.org/schema/beans”
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-4.0.xsd”>

<bean class=”com.ankeetc.spring.MyBean” name=”myBean” init-method=”myInit” destroy-method=”myDestroy”>
<property name=”prop” value=”prop”/>
</bean>
</beans>
4.5 关于 Bean 生命周期接口的探讨

Bean 生命周期带来的耦合:通过实现 Spring 的 Bean 生命周期接口对 Bean 进行额外控制,虽然让 Bean 具有了更细致的生命周期阶段,但也带来了一个问题,Bean 和 Spring 框架紧密绑定在一起了,这和 Spring 一直推崇的“不对应用程序类作任何限制”的理念是相悖的。业务类本应该完全 POJO 化,只实现自己的业务接口,不需要和某个特定框架(包括 Spring 框架)的接口关联。

init-method 和 destroy-method:可以通过 <bean> 的 init-method 和 destroy-method 属性配置方式为 Bean 指定初始化和销毁的方法,采用这种方式对 Bean 生命周期的控制效果和通过实现 InitializingBean、DisposableBean 接口所达到的效果是完全相同的,而且达到了框架解耦的目的。

BeanPostProcessor:BeanPostProcessor 接口不需要 Bean 去继承它,可以像插件一样注册到 Spring 容器中,为容器提供额外功能。

5 ApplicationContext
5.1 Application 体系结构

ApplicationEventPublisher:让容器拥有了发布应用上下文事件的功能,包括容器启动事件、关闭事件等。实现了 ApplicationListener 事件监听接口的 Bean 可以接收到容器事件,并对容器事件进行响应处理。在 ApplicationContext 抽象实现类 AbstractApplicationContext 中存在一个 ApplicationEventMulticaster,它负责保存所有的监听器,以便在容器产生上下文事件时通知这些事件监听者。
MessageSource:为容器提供了 i18n 国际化信息访问的功能。
ResourcePatternResolver:所有 ApplicationContext 实现类都实现了类似于 PathMatchingResourcePatternResolver 的功能,可以通过带前缀 Ant 风格的资源类文件路径来装载 Spring 的配置文件。
LifeCycle:它提供了 start() 和 stop() 两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被 ApplicationContext 实现及具体 Bean 实现,ApplicationContext 会将 start/stop 的信息传递给容器中所有实现了该接口的 Bean,以达到管理和控制 JMX、任务调度等目的。
ConfigurableApplicationContext:它扩展了 ApplicationContext,让 ApplicationContext 具有启动、刷新和关闭应用上下文的能力。上下文关闭时,调用 refresh() 即可启动上下文;如果已经启动,则调用 refresh() 可清除缓存并重新加载配置信息;调用 close() 可关闭应用上下文。

5.2 初始化 ApplicationContext

ClassPathXmlApplicationContext:从类路径中加载 XML 配置文件,也可以显示的使用带资源类型前缀的路径。
FileSystemXmlApplicationContext:从文件系统路径下加载 XML 配置文件,也可以显示的使用带资源类型前缀的路径。
AnnotationConfigApplicationContext:一个标注 @Configuration 注解的 POJO 即可提供 Spring 所需的 Bean 配置信息。
GenericGroovyApplicationContext:从 Groovy 配置中提取 Bean。

public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext classPathXmlApplicationContext =
new ClassPathXmlApplicationContext(“beans.xml”);
FileSystemXmlApplicationContext fileSystemXmlApplicationContext =
new FileSystemXmlApplicationContext(“file:/Users/zhengzhaoxi/Git/spring/src/main/resources/beans.xml”);
AnnotationConfigApplicationContext annotationConfigApplicationContext =
new AnnotationConfigApplicationContext(Config.class);
}
5.3 WebApplicationContext 体系结构
见后续章节
5.4 ApplicationContext 中 Bean 的生命周期

不同点
Bean 在 ApplicationContext 和 BeanFactory 中生命周期类似,但有以下不同点

如果 Bean 实现了 ApplicationContextAware 接口,则会增加一个调用该接口方法的 ApplicationContextAware.setApplicationContext 的方法。
如果在配置文件中生命了工厂后处理器接口 BeanFactoryPostProcessor 的实现类,则应用上下文在装在配置文件之后、初始化 Bean 实例之前会调用这些 BeanFactoryPostProcessor 对配置信息进行加工处理。Spring 提供了多个工厂容器,如 CustomEditorConfigure、PropertyPlaceholderConfigurer 等。工厂后处理器是容器级的,只在应用上下文初始化时调用一次,其目的是完成一些配置文件加工处理工作。
ApplicationContext 可以利用 Java 反射机制自动识别出配置文件中定义的 BeanPostProcessor、InstantiationAwareBeanPostProcessor、BeanFactoryPostProcessor,并自动将它们注册到应用上下文中(如下所示);而 BeanFactory 需要通过手工调用 addBeanPostProcessor() 方法注册。

<beans xmlns=”http://www.springframework.org/schema/beans”
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-4.0.xsd”>

<bean class=”com.ankeetc.spring.MyBean” name=”myBean” init-method=”myInit” destroy-method=”myDestroy”>
<property name=”prop” value=”prop”/>
</bean>

<!– 工厂后处理器 –>
<bean id=”myBeanPostProcessor” class=”com.ankeetc.spring.MyBeanPostProcessor”/>

<!– 注册 Bean 后处理器 –>
<bean id=”myBeanFactoryPostProcessor” class=”com.ankeetc.spring.MyBeanFactoryPostProcessor”/>
</beans>
6 BeanFactory 与 ApplicationContext 区别

一般称 BeanFactory 为 IoC 容器:Bean 工厂(com.springframework.beans.factory.BeanFactory)是 Spring 框架中最核心的接口,它提供了高级 IoC 的配置机制。BeanFactory 使管理不同类型的 Java 对象成为可能。BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身。

一般称 ApplicationContext 为应用上下文或 Spring 容器:应用上下文(com.springframework.context.ApplicationContext)建立在 BeanFactory 基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都可以直接使用 ApplicationContext。
BeanFactory 在初始化容器的时候,并未实例化 Bean,直到第一次访问某个 Bean 时才实例化 Bean;ApplicationContext 在初始化应用上下文的时候就实例化全部单实例 Bean。
ApplicationContext 可以利用 Java 反射机制自动识别出配置文件中定义的 BeanPostProcessor、InstantiationAwareBeanPostProcessor、BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而 BeanFactory 需要通过手工调用 addBeanPostProcessor() 方法注册。

7 父子容器

通过 HierarchicalBeanFactory 接口,Spring 的 IoC 容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的 Bean。
在容器内,Bean 的 id 必须是唯一的,但子容器可以拥有一个和父容器 id 相同的 Bean。
父子容器层级体系增强了 Spring 容器架构的扩展性和灵活性,因此第三方可以通过编程的方式,为一个已经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外的功能。
Spring 使用父子容器的特性实现了很多能力,比如在 Spring MVC 中,展现层 Bean 位于一个子容器中,而业务层和持久层的 Bean 位于父容器中。这样展现层 Bean 就可以引用业务层和持久层的 Bean,而业务层和持久层的 Bean 则看不到展现层的 Bean。

正文完
 0