控制反转(Inversion of Control,缩写为IoC),是一种设计模式,用来解耦组件之间的耦合度。
在Spring IoC容器的设计中,有两种主要的容器系列:BeanFactory与ApplicationContext。
BeanFactory,IoC容器的接口定义,该系列容器提供的是最基本的IoC容器的功能。ApplicationContext,容器的高级形态,它通过继承 MessageSource,ResourceLoader,ApplicationEventPublisher接口,在简单容器的基础上,增加了许多对高级容器的支持。
在Spring提供的最基本的IoC容器的接口定义和实现的基础上,Spring通过定义BeanDefinition来管理基于Spring的应用中的各种对象以及他们直接的相互依赖关系。BeanDefinition 抽象了我们对 Bean的定义,是让容器起作用的主要数据类型。对 IOC 容器来说,BeanDefinition 就是对依赖反转模式中管理的对象依赖关系的数据抽象。也是容器实现依赖反转功能的核心数据结构。
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的加载。
我们以常用的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在线】,查阅更多精彩历史!!!