Spring IOC 容器源码分析 01 - IOC 容器特性介绍

1. 简介

对于一个 Java 开发人员来说,Spring 框架是再熟悉不过的了。但我们很多时候都只停留在会使用的层面,或者能大概说出 IOC 的原理,但是对具体的实现细节不是很清楚。对于一个我们经常打交道的框架,还是很有必要搞清楚其中的原理。

另外,本系类的源码分析文章源码是基于 Spring 5.1.7.RELEASE 版本编写的。本系列文章是对 Spring 源码的分析,适合使用过 Spring 框架的开发人员,不适合初学者阅读。

本篇文章是 Spring IOC 容器源码分析系列的第一篇文章,主要是对 Spring IOC 容器的一些特性的介绍,为后面的章节做铺垫。好了,简介就说到这里,我们来看下面的内容吧。

2. Spring 模块结构

首先,我们一起来看下 Spring 框架的总体结构。
图片来源:Spring官方文档
Spring 是分模块开发的,从图中可以看到我们熟悉的 AOP、Web、Data 等模块都是基于 Core Container 之上的(忽略 Test 模块)。可以看出,Spring IOC 容器是 Spring 框架最核心的部分,很多功能都基于它。所以,阅读 Spring 源码当然要从最核心的 IOC 容器看起。那么接下来,我先给大家介绍一下 IOC 的一些特性,为后面的详细分析做个准备。

3. IOC 容器部分特性介绍

3.1 FactoryBean

FactoryBean 名字和 BeanFactory 很像,估计很多人在面试的时候,有被问过两者有什么区别?根据主谓语的原则来分析两者(前面是谓语,后面是主语),FactoryBean 是一个 Bean,而 BeanFactory 是一个 Factory,就是这么简单。那么 FactoryBean 和普通的 Bean 有什么区别,下面我们来做一个实验试试就知道了。

public class CarFactory implements FactoryBean<Car> {
    @Override
    public Car getObject() throws Exception {
        return new Car();
    }
    @Override
    public Class<?> getObjectType() {
        return Car.class;
    }
    @Override
    public boolean isSingleton() {
        return true;
    }
}

我们写了一个类 CarFactory 实现了 FactoryBean 接口,并重写了 getObject()getObjectType()isSingleton() 方法,接下来我们将 Bean 配置到容器中。

<bean id="carFactory" class="com.haoyanbing.spring.CarFactory" />

接下来,我们写测试代码:

@Test
public void test() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
    System.out.println("carFactory => " + context.getBean("carFactory"));
    System.out.println("&carFactory => " + context.getBean("&carFactory"));
}

运行结果如下:
程序运行结果
从运行结果我们可以看出,使用 carFactory 这个名称去 IOC 容器中获取对象时获取到的是 CarFactory 类的 getObject() 方法返回的对象,而不是 CarFactory 对象本身。当我们需要获取 CarFactory 本身时,只需要在对象 id 前加个 & 符号即可。当我们需要向容器中注册一些复杂的对象时就可以使用 FactoryBean 这种方式。

3.2 Bean 的作用域

Spring IOC 容器中的 Bean 有5种类型的作用域,分别是 singleton、prototype、request、session、global session。后面三种作用域在 Web 项目中才会使用到,这个到后面讲到 SpringMVC 的时候再介绍。

singleton 是默认的作用域,当定义 bean 的时候没有指定作用域,则会默认的设置为 singleton。singleton 是单例模式,也就是在系统运行期间,整个上下文环境中只有该 bean 的一个实例。我们将一个 bean 配置为 singleton:

<bean id="car" class="com.haoyanbing.spring.Car" scope="singleton" />

下面是测试代码:

@Test
public void test2() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
    Car car1 = (Car) context.getBean("car");
    Car car2 = (Car) context.getBean("car");
    System.out.println(car1 == car2);
}

测试结果:
测试结果
从结果可以看出,两次从 IOC 容器中获取的 car 都是同一个对象。

prototype 作用域类型的 bean 每次从 IOC 容器获取都是新的实例,我们把上面 bean 的配置修改为 prototype 试试:

<bean id="car" class="com.haoyanbing.spring.Car" scope="prototype" />

测试代码还是原来的代码,我们重写运行下,看测试结果:

从结果看出,两次从 IOC 容器中获取实例是不相同的,说明每次从容器中获取的都是新的实例。

3.3 alias 别名

alias 是别名的意思,我们可以给 bean 配置一个或多个别名,我们来给 car 配置别名试试:

<bean id="car" class="com.haoyanbing.spring.Car"/>
<alias name="car" alias="myCar"/>
<alias name="myCar" alias="myCar2"/>

我们使用 alias 标签给 car 配置了别名 myCar,又给别名 myCar 配置了别名 myCar2,下面我们通过别名在容器中获取实例试试:

@Test
public void test3() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
    System.out.println("car => " + context.getBean("car"));
    System.out.println("myCar => " + context.getBean("myCar"));
    System.out.println("myCar2 => " + context.getBean("myCar2"));
}

运行结果:
运行结果
从运行结果可以看出,根据 id 和别名都能获取到相同的 bean 实例。

3.4 factory-method

上面我们介绍了 FactoryBean,我们再来看下跟工厂相关的另外一个特性 factory-method。它可以表示工厂类的静态工厂方法,下面我们来写一个示例:

public class CarStaticFactory {
    public static Car getInstance() {
        return new Car();
    }
}
<bean id="carStaticFactory" class="com.haoyanbing.spring.CarStaticFactory" factory-method="getInstance"/>

我们写了一个 CarStaticFactory 类,里面有个静态的工厂方法 getInstance() 。然后我们在配置文件中配置了该 bean,并指定了 factory-method,下面我们写个代码测试下该功能:

@Test
public void test4() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
    System.out.println("carStaticFactory => " + context.getBean("carStaticFactory"));
}

测试结果:
运行结果

3.5 depends-on

在很多场景我们某个 bean 直接依赖另外一个 bean 的时候,我们直接使用 ref 引用。但在某些时候,某个 bean 并不直接依赖另外一个 bean,但是又需要另外的 bean 先实例化好再实例化自己,这个时候我们就可以使用 depends-on 特性了,下面我贴一段配置,因为该特性比较简单,我就不写示例代码了:

<bean id="beanA" class="com.haoyanbing.spring.BeanA" depends-on="beanB"/>
<bean id="beanB" class="com.haoyanbing.spring.BeanB" />

3.6 BeanPostProcessor

BeanPostProcessor 是 bean 初始化时的后置处理器,我们来看一下源码:

public interface BeanPostProcessor {
	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

BeanPostProcessor 是 Spring 的一个拓展点,通过实现该接口,我们就可以插手 Bean 的实例化过程了,postProcessBeforeInitializationpostProcessAfterInitialization 方法分别在 Bean 的初始化前后调用。下面我来写个示例来演示一下:

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("===> before " + beanName + " initialization");
        return bean;
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("===> after " + beanName + " initialization");
        return bean;
    }
}

我们写了一个类 MyBeanPostProcessor 实现了 BeanPostProcessor 接口,并实现了 postProcessBeforeInitializationpostProcessAfterInitialization 两个方法。接下来,我们把该 bean 配置到 IOC 容器中:

<bean id="car" class="com.haoyanbing.spring.Car"/>
<bean id="myBeanPostProcessor" class="com.haoyanbing.spring.MyBeanPostProcessor"/>

下面写个测试代码:

@Test
public void test5() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
}

测试结果:
测试结果
可以看到,我们写的 BeanPostProcessor 中的两个方法,在 Bean 初始化前后调用了。通过该特性我们可以修改 bean 实例,AOP 就是通过这种方式去生成代理的 Bean 实例替代原来的 Bean 实例。

3.7 Aware

Spring 是一个零侵入的框架,虽然我们使用了 IOC 容器,但是对我们的代码没有任何耦合,假设我们需要换一个 IOC 的实现框架,代码也是不用修改的。但是当我们需要感知 Spring IOC 容器中的一些功能的时候,可以实现以 Aware 结尾的接口,这样 Spring 在实例化该 Bean 的时候会自动将你需要的依赖注入进来。下面我以 ApplicationContextAware 为例写个示例:

public class Car implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("===> " + applicationContext);
        this.applicationContext = applicationContext;
    }
}

配置信息:

<bean id="car" class="com.haoyanbing.spring.Car"/>

测试代码:

@Test
public void test5() {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:application.xml");
}

测试结果:
测试结果
从结果可以看到,IOC 容器在实例化 Car 这个对象的时候,自动把 applicationContext 传给了我们。Spring 中还有很多的这种 Aware 接口。

4. 总结

本篇文章主要对 Spring IOC 容器的一些特性做了个简单的介绍,有的还写了一些示例代码,希望能让读者更好的理解。这些特性我们在实际工作中或多或少的使用过,了解这些特性的读者就当这篇文章带你复习了一遍这些特性。因为如果不了解这些特性,后面分析源码的时候,看到与这些特性相关的源码的时候,会不容易理解。这也是我在本系列文章的第一篇就先给大家把这些特性统一介绍一遍的原因。

另外,由于作者水平有限, 如果本文中有分析错的地方,欢迎大家指正。好了,本文就写到这里了,最后感谢大家的阅读!

发布了8 篇原创文章 · 获赞 7 · 访问量 709

猜你喜欢

转载自blog.csdn.net/hao_yan_bing/article/details/102864988