Spring源码分析之IoC(一)

什么是IoC

控制反转(Inversion of Control,缩写为IoC),是一种设计模式,用来解耦组件之间的耦合度。

IoC容器系列的设计与实现

在Spring IoC容器的设计中,有两种主要的容器系列:BeanFactory与ApplicationContext。
BeanFactory,IoC容器的接口定义,该系列容器提供的是最基本的IoC容器的功能。ApplicationContext,容器的高级形态,它通过继承 MessageSource,ResourceLoader,ApplicationEventPublisher接口,在简单容器的基础上,增加了许多对高级容器的支持。

在Spring提供的最基本的IoC容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及他们直接的相互依赖关系。BeanDefinition 抽象了我们对 Bean的定义,是让容器起作用的主要数据类型。对 IOC 容器来说,BeanDefinition 就是对依赖反转模式中管理的对象依赖关系的数据抽象。也是容器实现依赖反转功能的核心数据结构。

BeanFactory接口定义的方法:


BeanFactory容器的设计原理

BeanFactory接口提供了使用IoC容器的规范,我们以XmlBeanFactory的实现为例来说明简单IoC容器的设计原理。XmlBeanFactory类继承关系图:


XmlBeanFactory继承自DefaultListableBeanFactory类,DefaultListableBeanFactory包含了基本IoC容器所具有的重要功能,XmlBeanFactory在继承了DefaultListableBeanFactory容器的功能的同时,增加了新的功能,它是一个可以读取以XML文件定义的BeanDefinition的IoC容器,来看下具体实现:

public class XmlBeanFactory extends DefaultListableBeanFactory {

    private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);

    public XmlBeanFactory(Resource resource) throws BeansException {
        this(resource, null);
    }

    public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
        super(parentBeanFactory);
        this.reader.loadBeanDefinitions(resource);
    }
}
复制代码

在XmlBeanFactory中,初始化了一个XmlBeanDefinitionReader对象,该对象用于对XML文件定义信息的处理。XmlBeanFactory构造函数需要传入一个Resource参数,该参数封装了BeanDefinition的来源信息。在构造方法中,XmlBeanDefinitionReader会调用loadBeanDefinitions方法来完成BeanDefinition的加载。

ApplicationContext容器的设计原理


我们以常用的FileSystemXmlApplicationContext为例,debug源码来看下ApplicationContext容器的设计原理。
测试代码如下:

@Test
public void testApplicationContext() {
    ApplicationContext context = new FileSystemXmlApplicationContext("src/main/resources/bean.xml");
    context.getBean("helloWorld",HelloWorld.class).sayHello();
}
复制代码

进入 FileSystemXmlApplicationContext 的构造方法:

public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
    this(new String[] {configLocation}, true, null);
}
复制代码

该方法调用重载方法,传入三个参数:数组类型的configLocation,默认属性为true,父容器为null:

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}
复制代码

该方法首先会调用父类的构造方法,参数为null,在跟进去之前,我们先看下FileSystemXmlApplicationContext类的继承结构:


super(parent)方法会一直往上调用其父类构造方法,直到AbstractApplicationContext:

public AbstractApplicationContext(ApplicationContext parent) {
    this();
    setParent(parent);
}
复制代码

this方法调用默认构造函数,为属性resourcePatternResolver赋值:

public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}
复制代码
protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}
复制代码

回到setConfigLocations(configLocations)方法,看下该方法的具体实现:

public void setConfigLocations(String... locations) {
    if (locations != null) {
        Assert.noNullElements(locations, "Config locations must not be null");
        this.configLocations = new String[locations.length];
        for (int i = 0; i < locations.length; i++) {
            this.configLocations[i] = resolvePath(locations[i]).trim();
        }
    }
    else {
        this.configLocations = null;
    }
}
复制代码

先画出方法时序图,跟着图一步步跟下去:


该方法会调用resolvePath方法,解析路径,跟进去看下:

protected String resolvePath(String path) {
    return getEnvironment().resolveRequiredPlaceholders(path);
}
复制代码
public ConfigurableEnvironment getEnvironment() {
    if (this.environment == null) {
        this.environment = createEnvironment();
    }
    return this.environment;
}
复制代码
protected ConfigurableEnvironment createEnvironment() {
    return new StandardEnvironment();
}
复制代码

getEnvironment方法返回的是StandardEnvironment对象,该对象是标准环境,会自动注册System.getProperties() 和 System.getenv()到环境。

接下来,回到resolvePath方法,该方法会调用AbstractEnvironment类的resolveRequiredPlaceholders方法:

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    return this.propertyResolver.resolveRequiredPlaceholders(text);
}
复制代码
private final ConfigurablePropertyResolver propertyResolver =
        new PropertySourcesPropertyResolver(this.propertySources);
复制代码

此时propertyResolver属性已经有值了,紧接着会调用propertyResolver属性的resolveRequiredPlaceholders方法,我们先看下ConfigurablePropertyResolver继承结构:


进入resolveRequiredPlaceholders方法看下,该方法在AbstractPropertyResolver中:

public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
    if (this.strictHelper == null) {
        this.strictHelper = createPlaceholderHelper(false);
    }
    return doResolvePlaceholders(text, this.strictHelper);
}
复制代码

该方法做了两件事:1.调用createPlaceholderHelper方法为属性strictHelper赋值;2.调用doResolvePlaceholders方法,该方法是具体解析方法。

其中属性strictHelper,类型为PropertyPlaceholderHelper,持有要解析的前缀后缀,我们看下该类属性


继续跟进,会调用doResolvePlaceholders方法:

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
    return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
        @Override
        public String resolvePlaceholder(String placeholderName) {
            return getPropertyAsRawString(placeholderName);
        }
    });
}
复制代码

具体不在深入,讲了半天,我们来总结下setConfigLocations方法干了什么事情:解析FileSystemXmlApplicationContext构造函数中参数存在的占位符,并替换为真实值。如:

@Test
public void testApplicationContext() {
    Properties properties = System.getProperties();
    properties.setProperty("aaaa", "bbbb");
    properties.setProperty("bbbb", "src/main/resources/bean.xml");
    ApplicationContext context = new FileSystemXmlApplicationContext("${${aaaa}}");
    context.getBean("helloWorld",HelloWorld.class).sayHello();
}
复制代码

setConfigLocations方法会将${${aaaa}},递归解析为bbbb,然后查找对应的系统属性存在aaaa为key的value bbbb,然后再根据bbbb查找系统属性,替换为applicationContext.xml。

如果读完觉得有收获的话,欢迎点赞、关注、加公众号【Java在线】,查阅更多精彩历史!!!


猜你喜欢

转载自juejin.im/post/5bb0c222e51d450e5e0c95d1