Spring各模块说明和IoC容器启动源码简析

Spring5模块纵览

Spring各模块说明和IoC容器启动源码简析

找了好久才找到Spring5的这张图。。

Spring的github地址: https://github.com/spring-projects/spring-framework

从github可以看到,spring的源码都是按模块进行划分设计的,共有20余个小模块。

Spring的官方文档:https://docs.spring.io/spring/docs/current/spring-framework-reference/index.html

在上面这张图中,可以看到Spring将这些模块又按功能大致分成了几类:

核心容器

Spring最核心的四个模块,就是核心容器中的这四个:

  • spring-beans:Spring Bean相关,如依赖查找,依赖注入。
  • spring-core:Spring基础API模块,如资源管理,泛型处理。
  • spring-context:事件驱动、注解驱动,模块驱动等。
  • spring-expression:Spring 表达式语言模块(SpEL)。

当然,这部分还包括一些辅助模块,例如:

  • spring-context-indexer:Spring的类管理组件和Classpath扫描组件。
  • spring-context-support:对Spring IoC容器及IoC子容器的扩展支持。

尽管在图上没有,可以在github上面找到。

注意很多人以为Spring依赖注入是写在context这个包下的,实际上Spring Bean、依赖注入、依赖查找的核心还是beans这个包下的,而context中的ApplicationContext接口拓展了beans中的BeanFactory接口,增加了一些功能。

官方文档中是这样描述的:

The org.springframework.beans and org.springframework.context packages are the basis for Spring Framework’s IoC container. The BeanFactory interface provides an advanced configuration mechanism capable of managing any type of object. ApplicationContext is a sub-interface of BeanFactory. It adds:

+ Easier integration with Spring’s AOP features
+ Message resource handling (for use in internationalization)
+ Event publication
+ Application-layer specific contexts such as the WebApplicationContext for use in web applications.

In short, the BeanFactory provides the configuration framework and basic functionality, and the ApplicationContext adds more enterprise-specific functionality. The ApplicationContext is a complete superset of the BeanFactory and is used exclusively in this chapter in descriptions of Spring’s IoC container. For more information on using the BeanFactory instead of the ApplicationContext, see The BeanFactory.

这么长一段英文是什么意思呢?大致是说org.springframework.beansorg.springframework.context是IoC容器的基础。BeanFactory接口已经提供了一种管理任何类型的对象的高级配置机制,而ApplicationContextBeanFactory的一个子接口,它增加了一些功能,例如:
+ 与Spring AOP的整合
+ 消息资源管理(用于国际化)
+ 事件发布
+ 应用级别的上下文

简而言之,ApplicationContext增加了更多针对企业的功能,是BeanFactory的超集。

AOP和设备支持

Spring两大利器除了IoC就是AOP了。这部分模块包括:

  • spring-aop:支持Spring AOP
  • spring-aspects:支持aspects,集成自AspectJ框架,主要是为Spring提供多种AOP实现方法。
  • spring-instrument:基于Java SE中的java.lang.instrument进行设计的,应该算AOP的一个支援模块,主要作用是在JVM启用时生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现AOP。

数据访问/集成

这部分模块包括:
+ spring-jdbc:是Spring提供的JDBC抽象框架的主要实现模块,用于简化JDBC操作。
+ spring-orm:支持ORM(对象关系映射)
+ spring-oxm:xml编列,OXM是Object-to-XML-Mapping的缩写,将Java对象映射成XML数据,或者将XML数据映射成Java对象。
+ spring-jms:JMS(java message service)支持
+ spring-tx:事务支持
+ spring-messaging:可以说是spring想统一jms、kafka、rocketmq不同消息协议操作方式的一种野心。

Web

Spring5.0后将Web划分为了两个技术栈:

Spring各模块说明和IoC容器启动源码简析
  • Web Servlet:Spring MVC, WebSocket, SockJS, STOMP Messaging.
  • Web Reactive:Spring WebFlux, WebClient, WebSocket.

Web Servlet是传统的基于内嵌tomcat、servlet api的web组件,由于基于同步阻塞、一请求一线程的模型,效率较低。
Web Reactive基于Netty和Servlet3.1+容器,使用异步非阻塞模型,且提供了近几年大火的响应式编程模型Reactive支持,效率较高。

这里面的模块包括:
+ spring-web:为Spring提供了最基础的Web支持,主要建立在核心容器之上,通过Servlet或者Listeners来初始化IoC容器,也包含一些与Web相关的支持。
+ spring-webmvc:是一个基于servlet容器实现Spring MVC的模块
+ spring-webflux:是一个新的非堵塞响应式Reactive Web框架,可以用来建立异步的、非阻塞的、事件驱动的服务,并且扩展性非常好。
+ spring-websocket:对websocket的支持

集成测试

为了在测试时提供Spring环境的支持,便有了这个模块:

  • spring-test:集成测试

日志支持

  • spring-jcl:JCL(java commons logging)支持

Spring中的设计思想和编程模型

设计思想

  • Object-Oriented Programming (OOP) :面向对象编程
  • IoC(控制翻转)/ DI(依赖注入)
  • Domain-Driven Development (DDD) :领域驱动开发
  • Test-Driven Development (TDD) :测试驱动开发
  • Event-Driven Programing (EDP) :事件驱动编程
  • Functional Programing (FP): 函数式编程

编程模型

面向对象编程

举例:
+ 契约接口:Aware、BeanPostProcessor
+ 设计模式:观察者模式、组合模式、模板模式……
+ 对象继承:Abstract*

面向切面编程

动态代理: JdkDynamicAopProxy

字节码提升: ASM、CGLib、AspectJ...

面向元编程

  • 注解: 模式注解(@Component@Service@Respository...)
  • 配置: Environment 抽象、PropertySources、 BeanDefinition
  • 泛型: GenericTypeResolver、 ResolvableType

函数驱动

  • 函数接口: ApplicationEventPublisher
  • Reactive: Spring WebFlux

模块驱动

  • Maven Artifacts
  • OSGI Bundles(Spring4炒得火热,现在已经不支持了)
  • Java 9 Automatic Modules
  • Spring @Enable*注解

如何理解Spring IoC?

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,顾名思义为控制流的反转:在传统的编程模型中,用户通过手写代码中显式调用去寻找库中的资源,而通过控制反转,程序可以从通用框架中接收控制流。

说到底,IoC的思想最核心的地方在于资源交给不使用资源的第三方管理,这可以带来很多好处:
+ 资源集中管理,实现资源的可配置和易管理
+ 降低了使用资源双方的依赖程度,也就是我们说的耦合度。

著名的好莱坞原则提到:don't call us, we'll call you。(即把简历交给我之后就不要打电话来找我,我们会打电话给你)。它强调高层对低层的主动作用,即低层应该只管好自己的工作(具体实现),而高层自有它自己的工作。在Spring中,IoC容器就是这个“高层”,负责资源(为了准确,这里不只是Bean)的管理与维护,依赖查找与依赖注入。

实现IoC最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

类型 依赖处理 实现便利性 代码侵入性 API依赖性 可读性
依赖查找 主动获取 相对繁琐 侵入业务逻辑 依赖容器API 良好
依赖注入 被动提供 相对便利 低侵入性 不依赖容器API 一般

Spring IoC容器启动源码简要分析

本次实验环境为Spring 5.2.5.RELEASE

我们直接通过xml文件配置元数据(Spring的术语),实际上就是配置Spring IoC容器要管理Bean的信息和Bean之间的关系。最后总是会变成先解析配置文件内容,然后调用底层API进行配置。

配置元数据
public class ApplicationSpring {
    public static void main(String[] args) {
        System.out.println("application spring");
        //在此处设置断点
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        MessagePrinter printer = context.getBean(MessagePrinter.class);
        printer.printMessage();
    }
}

我们先不启动应用,直接一路按着Idea的F4跟踪一下new ClassPathXmlApplicationContext的过程中Spring干了些什么。

//调用了一个新的构造方法
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[] {configLocation}, true, null);
    }
//configLocations:配置文件的所处位置,可以有多个,这里只有我们配置的一个
//refresh:在加载所有的bean定义和创建单例后是否自动refresh上下文,这里为true
//parent:父context,可见上下文可以有层级关系,这里为空
public ClassPathXmlApplicationContext(
        String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

注意这里就兵分三路了:首先再进行父类的构造方法的调用,然后是setConfigLocations,看名字大概知道是设置配置文件的位置,最后,如果refresh为true,则调用refresh方法,而我们之前传过来的refresh就等于true,所以会执行该方法。

该顺序来,首先是父类的构造方法:

//继续super构造方法
public AbstractXmlApplicationContext(@Nullable ApplicationContext parent) {
    super(parent);
}

public AbstractRefreshableConfigApplicationContext(@Nullable ApplicationContext parent) {
    super(parent);
}

public AbstractRefreshableApplicationContext(@Nullable ApplicationContext parent) {
    super(parent);
}

public AbstractApplicationContext(@Nullable ApplicationContext parent) {
    this();
    setParent(parent);
}

可以看到最后是在AbstractApplicationContext这个类停了下来并调用它的构造方法。我们先看一下ClassPathXmlApplicationContext的层级关系:

Spring各模块说明和IoC容器启动源码简析

好的,插曲之后继续回到AbstractApplicationContext的构造方法:

//这里是设置了一个资源解析器
public AbstractApplicationContext() {
    this.resourcePatternResolver = getResourcePatternResolver();
}

protected ResourcePatternResolver getResourcePatternResolver() {
    return new PathMatchingResourcePatternResolver(this);
}

然后是setParent()

@Override
public void setParent(@Nullable ApplicationContext parent) {
    this.parent = parent;
    if (parent != null) {
        Environment parentEnvironment = parent.getEnvironment();
        if (parentEnvironment instanceof ConfigurableEnvironment) {
            getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
        }
    }
}

简单的设置了一下父级的context,由于这里是null,所以也没发生什么。

那我们回到ClassPathXmlApplicationContext构造方法中的setConfigLocations,继续看下去:

public void setConfigLocations(@Nullable 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;
    }
}

这里显然也只是设置了一下配置路径而已,看来真正的魔王还在refresh方法里面啊:

@Override
public void refresh() throws BeansException, IllegalStateException {
    //上锁,保证只有一个线程进入
    synchronized (this.startupShutdownMonitor) {
        // 1. 准备要刷新的上下文
        prepareRefresh();

        // 2. 告诉子类去刷新内部的BeanFactory
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // 3. 准备当前上下文要用的BeanFactory
        prepareBeanFactory(beanFactory);

        try {
            // 4. 在子类中允许BeanFactory的Post-processing处理
            postProcessBeanFactory(beanFactory);

            // 5. 调用所有以Bean身份注册的BeanFactoryPostProcessor
            invokeBeanFactoryPostProcessors(beanFactory);

            // 6. 注册拦截Bean创建的processors
            registerBeanPostProcessors(beanFactory);

            // 7. 初始化上下文的消息来源
            initMessageSource();

            // 8. 初始化上下文的事件多播器
            initApplicationEventMulticaster();

            // 9. 在特定的容器子类中初始化特殊的Bean
            onRefresh();

            // 10. 检查并注册监听器Bean对象
            registerListeners();

            // 11. 实例化所有的(非懒加载)单例对象
            finishBeanFactoryInitialization(beanFactory);

            // 12. 最后一步:发布对应的事件
            finishRefresh();
        }
        //捕获 BeansException 异常
        catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                        "cancelling refresh attempt: " + ex);
            }

            // 销毁已创建的单例对象
            destroyBeans();

            // 重置“active”标识
            cancelRefresh(ex);

            // Propagate exception to caller.
            throw ex;
        }

        finally {
            // 重置内省缓存.
            resetCommonCaches();
        }
    }
}

(中文注释全是我按照源码中的英文注释翻译替换了)

需要注意到,refresh方法的实现是写在AbstractApplicationContext中的,子类指的是的子类。可见这个refresh方法的信息量非常大,并且涉及了诸如事件、消息(国际化)等机制,可以说是我研究个几个月都没有问题,我这篇博客暂且不继续深入查看,但是有一个问题需要提出:

第二步中子类内部的BeanFactory是什么? ApplicationContextBeanFactory不应该是父子接口的关系吗?

带着这个问题,我们先简要看一下obtainFreshBeanFactory这个方法:

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
    refreshBeanFactory();
    return getBeanFactory();
}

可见,通过getBeanFactory()返回了一个ConfigurableListableBeanFactory对象,并且getBeanFactory最顶层是定义在ConfigurableApplicationContext接口中的。那么是不是以为着BeanFactoryConfigurableApplicationContext接口的子类有着组合的关系呢?我们查看其一个子类AbstractRefreshableApplicationContext的实现:

@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
    synchronized (this.beanFactoryMonitor) {
        if (this.beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - " +
                    "call 'refresh' before accessing beans via the ApplicationContext");
        }
        return this.beanFactory;
    }
}

可见,其内部真的有一个类型为ConfigurableListableBeanFactoryBeanFactory实例对象,那是不是可以猜想ApplicationContext是通过组合的方式来拓展的BeanFactory的功能呢?我们把这个问题留到以后再谈。

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/spring%e5%90%84%e6%a8%a1%e5%9d%97%e8%af%b4%e6%98%8e%e5%92%8cioc%e5%ae%b9%e5%99%a8%e5%90%af%e5%8a%a8%e6%ba%90%e7%a0%81%e7%ae%80%e6%9e%90/

发表评论

电子邮件地址不会被公开。