Java 自定义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定义的载入过程。
到此这篇关于Java 自定义Spring框架与Spring IoC相关接口分析的文章就介绍到这了,更多相关Java 自定义Spring框架内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!