Spring 核心组件原理解析

尽管希腊哲学家赫拉克利特(Heraclitus)并不做为一名软件开发人员而闻名,但他彷佛深谙此道。他的一句话常常被引用:“惟一不变的就是变化”,这句话抓住了软件开发的真谛。spring

咱们如今开发应用的方式和1年前、5年前、10年前都是不一样的,更别提15年前了,当时RodJohnson的图书 Expert One-on-One J2EE Design and Development 介绍了Spring框架的初始形态。当时,最多见的应用形式是基于浏览器的Web应用,后端由关系型数据库做为支撑。尽管这种形式的开发依然有它的价值,Spring也为这种应用提供了良好的支持,可是咱们如今感兴趣的还包括如何开发面向云的由微服务组成的应用,这些应用会将数据保存到各类类型的数据库中。数据库

另一个崭新的关注点是反应式编程,它致力于经过非阻塞操做提供更好的扩展性并提高性能。随着软件开发的发展,Spring框架也在不断变化,以解决现代应用开发中的问题,其中就包括微服务和反应式编程。Spring还经过引入Spring Boot简化本身的开发模型。编程

Spring 的核心

任何实际的应用程序都是由不少组件组成的,每一个组件负责整个应用功能的一部分,这些组件须要与其余的应用元素进行协调以完成本身的任务。当应用程序运行时,须要以某种方式建立并引入这些组件。后端

 

 

Spring Framework 总共有十几个组件,但真正核心的组件只有三个:Spring Core,Spring Context 和 Spring Bean,它们奠基了 Spring 的基础并撑起了 Spring 的框架结构。Spring 的其它功能特性例如 Web、AOP、JDBC 等都是在其基础上发展实现的。浏览器

Spring之中最重要的当属Bean了,Spring实际上就是面向Bean的编程,Bean对于Spring的意义就比如Object对于OOP的意义同样。那么,三个核心组件之间是如何协同工做的呢?若是把Bean比做一场演出中的演员,那么Context就是这场演出的舞台,Core就是演出的道具,至于演出的节目,就是Spring的一系列特点功能了。框架

咱们知道Bean包裹的是Object,而Object中必然有数据,Context就是给这些数据提供生存环境,发现每一个Bean之间的关系,为他们创建并维护好这种关系。这样来讲,Context就是一个Bean关系的集合,这个关系集合就是咱们所说的IOC容器。那么Core又有什么做用呢?Core就是发现、创建和维护每一个Bean之间的关系所需的一系列工具,就是咱们常常说的Util。ide

Bean 组件

Bean组件在Spring的org.springframework.beans包下,主要完成了Bean的建立、Bean的定义以及Bean的解析三件事。函数

SpringBean的建立是典型的工厂模式,其工厂的继承层次关系如图所示:微服务

 

 

Spring 使用工厂模式来管理程序中使用的对象(Bean),Bean 工厂最上层的接口为 BeanFactory,简单来看,工厂就是根据须要返回相应的 Bean 实例。工具

public interface BeanFactory {
    //...        
    Object getBean(String name);
}

在工厂模式中,在工厂的实现类中生成 Bean 返回给调用客户端,这就要求客户端提供生成本身所需类实例的工厂类,增长客户负担。Spring 结合控制反转和依赖注入为客户端提供所需的实例,简化了客户端的操做。具体的实现方式大体以下。

public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
        implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
    /** Map of bean definition objects, keyed by bean name */
    private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>;
    public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){     
        //...
    }
}

beanDefinitionMap 做为具体的 Bean 容器,Spring 建立的对象实例保存其中。客户端须要时,使用工厂的 getBean 方法去试图获得相应的实例,若是实例已存在,则返回该实例;若是实例不存在,则首先产生相应实例并经过 registerBeanDefinition 方法将其保存在 beanDefinitionMap 中(Lazy Initialization),而后返回该实例给客户端。

 

 

Spring Bean 工厂的继承关系beanDefinitionMap 并不直接保存实例自己,而是将实例封装在 BeanDefinition 对象后进行保存。BeanDefinition 包含了实例的全部信息,其简化版的定义以下。

public class BeanDefinition {
    private Object bean;

    private Class<?> beanClass;
    private String beanClassName;

    // Bean 属性字段的初始化值
    private BeanPropertyValues beanPropertyValues;

    //...
}

Spring Bean 工厂生产 Bean 时

  1. 先将实例的类型参数保存到 beanClass 和 beanClassName,将须要初始化的字段名和值保存到 beanPropertyValues 中,这个过程 Spring 经过控制反转来实现,本文第二小节将予以简要说明
  2. 生成 bean 实例,并利用反射机制将须要初始化的字段值写入 bean 实例,将实例保存在 bean 中,完成 BeanDefinition 的构建。 
    假设咱们已经完成了步骤 1) 的操做,以后的过程用代码表述以下所示。
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition){
    //生成 bean 实例,并完成初始化
    Object bean = createBean(beanDefinition);
    //将 bean 实例保存在 beanDefinition 中
    beanDefinition.setBean(bean);
    //将 beanDefinition 实例保存在 Spring 容器中
    beanDefinitionMap.put(beanName, beanDefinition);
}

protected Object createBean(BeanDefinition beanDefinition) {
    try{
        Object bean = beanDefinition.getBeanClass().newInstance();
        try {
            setBeanPropertyValues(bean, beanDefinition);
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException e) {
            e.printStackTrace();
        }
        return bean;
    }catch(InstantiationException e){
        e.printStackTrace();
    }catch(IllegalAccessException e){
        e.printStackTrace();
    }

    return null;
}

protected void setBeanPropertyValues(Object bean, BeanDefinition beanDefinition) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
    for(PropertyValue pv : beanDefinition.getBeanPropertyValues().getBeanPropertyValues()){
        Field beanFiled = bean.getClass().getDeclaredField(pv.getName());
        beanFiled.setAccessible(true);
        beanFiled.set(bean, pv.getValue());
    }
}

Context 组件

前面说到,Context组件的做用是给Spring提供一个运行时的环境,用以保存各个对象的状态,咱们来看一下与Context相关的类结构图。

 

 

从图中能够看出,Context类结构的顶级父类是ApplicationContext,它除了能标识一个应用环境的基本信息之外,还继承了5个接口,这5个接口主要是扩展了Context的功能。ApplicationContext的子类主要包含两个方向,图中已做说明。再往下就是构建Context的文件类型,接着就是访问Context的方式。

通常地,传统的程序设计中,不管是使用工厂建立实例,或是直接建立实例,实例调用者都要先主动建立实例,然后才能使用。控制反转(Inverse of Control) 将实例的建立过程交由容器实现,调用者将控制权交出,是所谓控制反转。
依赖注入(Dependence Injection) 在控制反转的基础上更进一步。若是没有依赖注入,容器建立实例并保存后,调用者须要使用 getBean(String beanName) 才能获取到实例。使用依赖注入时,容器会将 Bean 实例自动注入到完成相应配置的调用者,供其进一步使用。Context 组件借助上述的控制反转和依赖注入,协助实现了 Spring 的 Ioc 容器。下面咱们以一个 Service 类做为所需的 Bean 实例进行说明。实际应用中,咱们会须要 Spring 管理不少 Bean 实例。

public class SampleService {
    private String service;
        public String getService() {
        return service;
    }
    public void setService(String service) {
        this.service= service;
    }
}

 

在程序运行过程当中,须要一个 SampleService ,咱们不让调用者 new 一个实例,而是在配置文件中代表该 SampleService 的实例交由 Spring 容器进行管理,并指定其初始化参数。配置文件即资源,其内容以下。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="sampleService " class="com.service.SampleService ">
        <property name="service" value="This is a service"></property>
    </bean>
</beans>

 

Spring Core 组件提供 ResourceLoader 接口,便于读入 xml 文件或其余资源文件。其核心功能代码应该提供以下方法。

public class ResourceLoader {
    public Resource getResource(String location){
        URL resource = this.getClass().getClassLoader().getResource(location);
        return new UrlResource(resource);
    }
}

// UrlResource 的功能代码
public class UrlResource implements Resource {
    private final URL url;

    public UrlResource(URL url){
        this.url = url;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

}

即加载资源文件,并以数据流的形式返回。Context 根据资源中的定义,生成相应的 bean 并保存在容器中,bean 的名字是 sampleService ,供程序进一步使用。这样就完成了控制反转的工做。接下来就须要把 sampleService 注入到须要使用它的地方,亦即完成依赖注入操做。如今假设 SampleController 中使用 SampleService 的对象,Spring 提供三种依赖注入的方式,构造器注入、setter 注入和注解注入。

public class SampleController {
    /**
     * 3\. 注解注入
    **/
    /* @Autowired */
    private SampleService sampleService;

    /**
     * 1\. 构造器注入
    **/
    public SampleController(SampleService sampleService){
        this.sampleService = sampleService;
    }
    //无参构造函数
    public SampleController(){}

    // 类的核心功能
    public void process(){
        System.out.println(sampleService.getService());
    }
    /**
     * 2\. setter 注入
    **/
    /*public void setService(SampleService service) {
        this.service= service;
    }*/
}

 

三种注入方式在配置文件中对应不一样的配置方式,在前面 xml 文件的基础上,咱们能够分别实现这三种注入方式。须要注意的是,这里 SampleController 也是使用 Spring 的 Ioc 容器生成管理的。

<?xml version="1.0" encoding="UTF-8"?>
<beans>
    <bean name="sampleService " class="com.service.SampleService ">
        <property name="service" value="This is a service"></property>
    </bean>

<!-- 1\. 构造器注入方式为SampleContorller 的 bean 注入 SampleService -->
    <bean name="sampleContorller" class="com.controller.SampleContorller">
        <!-- index 是构造方法中相应参数的顺序 -->
        <constructor-arg index="0" ref="sampleService"></constructor-arg>
    </bean>

<!-- 2\. setter 注入方式为SampleContorller 的 bean 注入 SampleService -->
<!--
    <bean name="sampleContorller" class="com.controller.SampleContorller">
        <property name="sampleService " ref="sampleService"></property>
    </bean>
-->

<!-- 3\. 注解注入方式为SampleContorller 的 bean 注入 SampleService -->
<!--
    <bean name="sampleContorller" class="com.controller.SampleContorller">

    <!-- 不须要配置,Spring 自动按照类型注入相应的 bean -->
    </bean>
-->
</beans>

Core组件

Core组件一个重要的组成部分就是定义了资源的访问方式。Core组价把全部的资源都抽象成一个接口,这样,对于资源使用者来讲,不须要考虑文件的类型。对资源提供者来讲,也不须要考虑如何将资源包装起来交给别人使用(Core组件内全部的资源均可以经过InputStream类来获取)。另外,Core组件内资源的加载都是由ResourceLoader接口完成的,只要实现这个接口就能够加载全部的资源。

 

 

那么,Context和Resource是如何创建关系的呢?经过前面Context的介绍咱们知道,Context组件里面的类或者接口最终都实现了ResourcePatternResolver接口,ResourcePatternResolver接口的做用就是加载、解析和描述资源。这个接口至关于Resource里面的一个接头人,它把Resource里的资源加载、解析和定义整合到一块儿,便于其余组件使用。

前面介绍了三大核心组件的结构与相互关系,那么,这三大组件是如何让Spring完成诸如IOC和AOP等各类功能的呢?敬请期待下一篇文章!

最后留一个问题:Spring 为何可以长盛不衰?

相关文章
相关标签/搜索