我的自定义Spring框架 | Spring IoC相关接口分析
Posted 李阿昀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的自定义Spring框架 | Spring IoC相关接口分析相关的知识,希望对你有一定的参考价值。
在本讲,我们来对Spring IoC功能相关的接口逐一进行分析,分析这些接口的原因就是为了我们自己定义Spring IoC功能提前做好准备。
Spring IoC相关接口分析
BeanFactory接口解析
对于BeanFactory接口,我之前只是稍微提到过,并且将就着用了一下它。这里,我将会对BeanFactory接口进行一个具体讲解。
Spring中bean的创建是典型的工厂模式,这一系列的bean工厂,即IoC容器,为开发者管理对象之间的依赖关系提供了很多便利和基础服务,在Spring中有许多IoC容器的实现可供用户选择,其相互关系如下图所示。
这里说到了Spring中bean的创建是典型的工厂模式,那么你知道到底是哪种工厂模式吗?其实,这里面用的是简单工厂+配置文件的形式,相信大家都知道简单工厂+配置文件会大大降低对象和对象之间的耦合。
上面还说到了在Spring中有许多IoC容器的实现可供用户选择,这句话怎么理解呢?我们在创建IoC容器时,创建的肯定是BeanFactory接口的子实现类对象,那么我们就要想了,到底有哪些子实现类可供咱选择呢?嘿嘿!这不用你操心,因为Spring提供了很多该接口的子实现类供我们去选择。
从以上类图中可以看到,BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,而且BeanFactory有三个重要的子接口,分别是ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。但是从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口,这就意味着如果我们想要用的话,那么直接用该子实现类就行了!
看完上面这段话,有两点需要引起我们的注意,一是BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范,那么到底它定义了哪些最基本的功能规范呢?其实,大家想一想就知道了,工厂本身就是用来生产对象的,那么在Spring里面,bean工厂生产的就是bean对象了,所以BeanFactory里面肯定是要提供获取bean对象的方法的,这个我后面就会详细地讲到;二是BeanFactory属于延时加载,也就是说对于bean对象Spring进行了一个延时加载。
问题来了,为何要定义这么多层次的接口呢?定义的少一点,整个架构看起来不就更加简单吗?原因如下:
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。例如,
- ListableBeanFactory接口:表示bean可列表化。什么意思啊?我给大家解释解释,它说的是该接口可以通过列表的方式对bean对象进行一个存储。
- HierarchicalBeanFactory接口:表示bean是有继承关系的,也就是每个bean可能有父bean。
- AutowireCapableBeanFactory接口:定义bean的自动装配规则。依赖注入就属于自动装配规则里面的。
以上这三个接口共同定义了bean的集合、bean之间的关系及bean的行为。不过,最基本的IoC容器接口还是BeanFactory,下面我们就来看一下它的源码,看它里面到底定义了哪些最基本的功能规范。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
// 根据bean的名称获取IoC容器中的bean对象
Object getBean(String var1) throws BeansException;
// 根据bean的名称获取IoC容器中的bean对象,并指定获取到的bean对象的类型,这样我们使用时就不需要进行类型强转了
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
// 判断容器中是否包含指定名称的bean对象
boolean containsBean(String var1);
// 根据bean的名称判断是否是单例
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
看到那一系列的getBean方法没,它们都是用来获取bean对象的,当然了,BeanFactory接口里面还定义了一些其他的基本功能规范,这里我就不再细说了。
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,而不关心工厂是怎么生产这些产品的一样。当然,bean的定义以及加载是要交由给BeanFactory接口的子实现类去做的。
BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要是来规范容器中的bean对象是非延时加载的,即在创建容器对象的时候就对bean对象进行初始化,并存储到一个容器中。大家不妨来看一下下面这张图。
可以看到最顶层就是BeanFactory接口,它下面有一个子接口叫ApplicationContext,而该子接口下面又有三个比较重要的子实现类,还记得上面我说过在Spring中有许多IoC容器的实现可供用户选择吗?这仨子实现类就是。如果要想知道工厂是如何产生对象的,那么我们就需要查看具体的IoC容器实现了,Spring提供了许多IoC容器实现,比如:
- FileSystemXmlApplicationContext:根据系统路径加载XML配置文件,并创建IoC容器对象。
- ClassPathXmlApplicationContext:根据类路径加载XML配置文件,并创建IoC容器对象。
- AnnotationConfigApplicationContext:加载注解类配置,并创建IoC容器。
注意了,我们在后面自己去定义Spring IoC功能时,我们只针对ClassPathXmlApplicationContext类来实现,也就是只关注类路径下XML配置文件的解析与对应IoC容器的创建。
关于BeanFactory接口的分析,我们就分析至此。
BeanDefinition接口解析
Spring IoC容器管理的是我们定义的各种bean对象及其相互关系,而bean对象在Spring实现中是以BeanDefinition来描述的。
来看一下下面配置文件中的bean配置,如果你用过Spring或者Spring MVC框架的话,那么相信你对这段配置肯定不会陌生,注意了,在<bean>
标签内我们还可以设置很多属性,例如scope、init-method、destory-method等,只是在这里我们并没有全部列举出来。
<bean id="userDao" class="com.meimeixia.dao.impl.UserDaoImpl"></bean>
现在对于Spring来说的话,它就得解析这个<bean>
标签了,解析时,必然就要把该<bean>
标签对应的属性的值进行一个封装,那Spring会封装成什么样的一个对象呢?会封装成BeanDefinition对象,又由于BeanDefinition是一个接口,所以最终Spring会封装成一个该接口的子实现类对象。
接下来,我们就来看看BeanDefinition接口的继承体系,如下图所示。
可以看到,BeanDefinition确实是一个接口,而且它下面有一个具体的子实现类,即RootBeanDefinition。
BeanDefinitionReader接口解析
刚才我们讲解完了BeanDefinition接口,知道了该接口的作用就是对XML配置文件里面<bean>
标签相关的属性进行封装。那么接下来我们就来思考一个问题,就是XML配置文件到底是由谁来解析的呢?既然提到解析了,那么我们就要来看一看BeanDefinitionReader接口了。
bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。bean的解析主要就是对Spring配置文件的解析,这个解析过程主要通过BeanDefinitionReader来完成。下面我们就来看看Spring中BeanDefinitionReader的类结构图,如下图所示。
当然了,你也可以回到IDEA里面去查看一下BeanDefinitionReader接口的继承体系,如下图所示。
可以看到,BeanDefinitionReader接口有三个子实现类,这里我只讲一下上面红框框住的两个子实现类。
- PropertiesBeanDefinitionReader:主要解析properties格式的配置文件。但是,在实际开发中,你会发现很少会用到properties格式的配置文件,用的更多的是XML格式的配置文件。
- XmlBeanDefinitionReader:主要解析XML格式的配置文件。
BeanDefinitionReader既然是一个接口的话,那么它里面定义的便是最基本的功能规范,这些规范针对不同的子实现类会有不同的实现,从上图中我们也看到了BeanDefinitionReader接口确实是有不同的子实现类。这些子实现类会来决定到底解析什么样的配置文件,究竟是properties格式的呢,还是XML格式的,所以你会发现Spring底层设计的还是比较全面的。
接下来,我们就来看一下BeanDefinitionReader接口的源码,看它里面到底定义了哪些最基本的功能规范。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory.support;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.lang.Nullable;
public interface BeanDefinitionReader {
// 获取BeanDefinitionRegistry注册器对象
BeanDefinitionRegistry getRegistry();
@Nullable
ResourceLoader getResourceLoader();
@Nullable
ClassLoader getBeanClassLoader();
BeanNameGenerator getBeanNameGenerator();
/*
* 下面这些重载的loadBeanDefinitions方法都是从指定的资源中加载bean定义
*/
int loadBeanDefinitions(Resource var1) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... var1) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String var1) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... var1) throws BeanDefinitionStoreException;
}
可以看到,BeanDefinitionReader接口里面定义了很多很多的方法,不过我们重点关注两类方法:
- getRegistry方法:获取BeanDefinitionRegistry注册器对象。
- loadBeanDefinitions方法:从不同指定的资源中加载bean定义,也就是加载配置文件。
我相信,从BeanDefinitionReader接口定义的功能中你已经理解了它具体的一个作用。
BeanDefinitionRegistry接口解析
接下来,我们来分析一下BeanDefinitionRegistry接口。其实,刚才我们在去分析BeanDefinitionReader接口的时候就见过,还记得吗?BeanDefinitionReader接口里面的getRegistry方法的返回值类型就是BeanDefinitionRegistry。
我们都知道,BeanDefinitionReader是用来解析bean定义,并将其(指的就是bean定义)封装成BeanDefinition对象的。还有,我想大家也知道我们定义的配置文件中会定义很多bean标签,那么这里就存在一个问题了,就是解析出来的BeanDefinition对象到底存储到哪儿了呢?答案就是BeanDefinition的注册中心,而该注册中心顶层接口就是BeanDefinitionRegistry。
接下来,我们就来看一下BeanDefinitionRegistry接口的源码,看它里面到底定义了哪些最基本的功能规范。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory.support;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.AliasRegistry;
public interface BeanDefinitionRegistry extends AliasRegistry {
// 往注册表中注册bean
void registerBeanDefinition(String var1, BeanDefinition var2) throws BeanDefinitionStoreException;
// 从注册表中删除指定名称的bean
void removeBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
// 获取注册表中指定名称的bean
BeanDefinition getBeanDefinition(String var1) throws NoSuchBeanDefinitionException;
// 判断注册表中是否已经注册了指定名称的bean
boolean containsBeanDefinition(String var1);
// 获取注册表中所有的bean的名称
String[] getBeanDefinitionNames();
int getBeanDefinitionCount();
boolean isBeanNameInUse(String var1);
}
以上就是注册中心顶层接口BeanDefinitionRegistry里面定义的最基本的功能规范。
由于BeanDefinitionRegistry是一个接口,所以我们在使用的时候肯定就是使用它的子实现类了。接下来,我们就来看一下Spring中BeanDefinitionRegistry的类结构图,如下图所示。
当然了,你也可以回到IDEA里面去查看一下BeanDefinitionRegistry接口的继承体系,如下图所示。
从上面可以看到BeanDefinitionRegistry接口的子实现类主要有以下几个:
-
SimpleBeanDefinitionRegistry
从名字上来看,它就是一个简单的BeanDefinition的注册中心,由于解析出来的BeanDefinition对象就存储在BeanDefinition的注册中心,所以它必然是一个容器。在这里我要给大家提个醒,相比另外两个类,该类是我们要更加要关注的。
接下来,我们就要看看该类里面有没有定义什么容器来存储BeanDefinition对象了。查看该类的源码,如下图所示,你会发现在其成员位置处定义了一个Map集合,而且Map集合的键是String类型的,值是BeanDefinition类型的。其实,从这里就可以看出,该Map集合就是用来注册BeanDefinition对象的,其中,键就是要注册的BeanDefinition对象的名称,值就是要注册的BeanDefinition对象,不知我这样说,大家明白了没?
-
DefaultListableBeanFactory
该类我们在分析BeanFactory接口的时候就见过,还记得吗?不记得的话,再回头去看一下BeanFactory的类结构图。你会发现该类不仅实现了BeanFactory接口,还实现了BeanDefinitionRegistry接口,所以该类既是容器,也是注册表。
接下来,我们也是要看看该类里面有没有定义什么容器来存储BeanDefinition对象。查看该类的源码,如下图所示,你会发现在其成员位置处也定义了一个Map集合来注册BeanDefinition对象。
-
GenericApplicationContext
该类间接地实现了ApplicationContext接口,这点你通过查阅源码就能知道了,所以该类同上,既是容器,也是注册表。
创建容器
刚才我们分析了一下与Spring IoC功能相关的一些接口,分析完这些接口之后,大家要明确的就是每一个接口,它的作用是什么,以及该接口下面比较常用的子实现类有哪些。
明确了之后,接下来我们再来分析一个问题,就是创建容器的时候,到底做了些什么事?
我们都知道BeanFactory是Spring IoC容器最顶层的一个接口,但咱们现在写的程序用的却是ApplicationContext这个子接口及其下面的ClassPathXmlApplicationContext子实现类,这是为什么呢?我不说,想必大家也知道,无非就是ApplicationContext属于非延时加载,也就是说在创建容器对象的时候,就会去实例化bean对象,并存储在容器里面了。
下面我们就以ClassPathXmlApplicationContext这个容器类来分析一下创建容器的时候,到底都做了些什么事。
首先,查看一下ClassPathXmlApplicationContext类的源码,如下图所示,可以看到它里面提供了很多构造方法,有无参的,有有参的,反正是有很多,三岁小孩都知道当我们去创建这个类的对象时,必然是要调用它里面的构造方法的。
然后,我们就来看一下咱们平时调用的有参构造到底做了哪些事情。你会发现该有参构造又调用了另外一个有参构造,那这个有参构造又是谁呢?看,是它!
可以看到,在这个构造方法里面会先判断refresh变量是否为true,若为true则调用refresh方法。很显然,该refresh变量的值就是true,因为从上一个有参构造跳转到该有参构造时,第二个参数传递的就是true。既然refresh变量的值为true,那么肯定就会去调用refresh方法。
那么,refresh方法又做了些什么呢?点击进入该方法去看看不就得了,你会发现此时跳转到父类中了,如下图所示。
可以看到,该refresh方法做了很多很多事情,这里我就做一个简短说明,refresh方法做的事就是加载配置文件并去初始化bean对象,然后将bean对象存储在容器里面。注意,该方法的具体源代码,我们就不逐行去分析了,后续我们自己去实现Spring IoC功能时,再详细的去说一下它底层的一个实现。
最后,我给大家做个总结吧!也不知道大家看不看得懂。
ClassPathXmlApplicationContext对bean配置资源的载入是从refresh方法开始的。refresh方法是一个模板方法,规定了IoC容器的启动流程,因为有些逻辑是要交给其子类去实现的。那它是如何对bean配置资源进行载入的呢?ClassPathXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh方法启动整个IoC容器对bean定义的载入过程。
以上是关于我的自定义Spring框架 | Spring IoC相关接口分析的主要内容,如果未能解决你的问题,请参考以下文章
我的自定义Spring框架 | 自定义Spring IoC功能
我的自定义Spring框架 | 自定义Spring IoC功能