面试

Spring 底层基本原理

2020-08-24  本文已影响0人  坚果jimbowhy

Spring 底层基本原理

H

Java 标注技术参考 https://github.com/jimboyeah/demo/blob/java/demos/AnnotationsDemo.java

学习一个框架的最佳姿势就是 READ THE FXXK SOURCE CODE!

但是,如果细读每一行代码,去跟踪程序的执行流程,这是不可取的,新手通常都会犯这种不是错误的错误,但又确实比较要命,很容易将自己的脑袋丢进代码丛林,只见森林不见树木,迷途不归路。

所以,我更愿意使用一种蜻蜓点水的态度去做这件事,首先,立下两个目标:

为了解密以上两个问题,首先需要搞到 Spring 的源代码,我使用的是 Spring-5.1.0.RELEASE-sources,下载到的发行包目录里有各个模块的源码独立打包,解压到同一目录下。

代码阅读工具使用 Sublime Text,它的多功能跳转、文件快速定位打开、符号跳转、光标位置跳转等功能十分适合用来快速定位源代码的关注点。

Spring 框架源代码以模块化管理:

如果按配置文件的使用方式来划分,Spring 应用可以简单按以下方式分类:

根据目前 Web 开发的流行,或者也可以将 Spring 应用分为 Web 应用与其它类型应用,这个分类是比较实用的。

还可以根据 Refreshable 特性进行分类:

ApplicationContext 接口是所用 Spring 应用的基础,在此之上是 ConfigurableApplicationContext,和 WebApplicationContext Web 应用是扩展接口:

AbstractXmlApplicationContext 
 => 🧡 AbstractRefreshableConfigApplicationContext 
     |=> 🧡 AbstractRefreshableApplicationContext
     |    |=> 🧡 AbstractApplicationContext
     |         |=> 🧡 DefaultResourceLoader
     |         |=> 🤍 ConfigurableApplicationContext
     |              |=> 🤍 ApplicationContext
     |              |    |=> 🤍 EnvironmentCapable
     |              |    |=> 🤍 ListableBeanFactory
     |              |    |=> 🤍 HierarchicalBeanFactory
     |              |    |=> 🤍 MessageSource
     |              |    |=> 🤍 ApplicationEventPublisher
     |              |    |=> 🤍 ResourcePatternResolver
     |              |=> 🤍 Lifecycle
     |              |=> 🤍 Closeable
     |=> 🤍 BeanNameAware
     |=> 🤍 InitializingBean

作为一个基本的抽象类实现,AbstractApplicationContext 通过 LifecycleProcessor 接口为各种应用实现了基本的应用生命周期管理,应用的启停方法。

以 XML 配置运行的常规 Spring 应用有很多,以下是继承自 AbstractXmlApplicationContext 的其中两个,分别从 classpath 和指定配置文件路径加载:

new ClassPathXmlApplicationContext("beans-config.xml");
new FileSystemXmlApplicationContext("beans-config.xml");

典型的 Web 应用有三种,XmlWebApplicationContext 默认以 /WEB-INFO 为配置目录:

XmlWebApplicationContext 
 => 🧡 AbstractRefreshableWebApplicationContext
     |=> 🧡 AbstractRefreshableConfigApplicationContext
     |=> 🤍 ConfigurableWebApplicationContext
     |    |=> 🤍 WebApplicationContext
     |    |    |=> 🤍 ApplicationContext
     |    |=> 🤍 ConfigurableApplicationContext
     |=> 🤍 ThemeSource

默认配置属性设置:

public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";

public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";

public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";

除了 XmlWebApplicationContext,还有另外两个 Web 应用类型,AnnotationConfigWebApplicationContext, GroovyWebApplicationContext,看名字大概猜到应用类型和标注、Groovy 脚本有密切关。

Spring 4.0 的一个重要特征就是完全支持 Groovy,这是 Spring 主导的一门基于 JVM 的脚本语言。在 Spring 2.x,脚本语言通过 Java scripting engine 得到支持。而在 Spring 4.0 中,Groovy 的变得更重要,Groovy 可以替换 XML 和注解用来作为 bean 配置。所以 GroovyWebApplicationContext 和 GenericGroovyApplicationContext 在使用 Groovy 脚本进行开发时非常好用。

另外一支是通用弄 Spring 应用,以 GenericApplicationContext 为基础类,以静态站点应用为例,它的类层次结构如下:

StaticWebApplicationContext
|=> 🧡 StaticApplicationContext
|    |=>🧡 GenericApplicationContext
|        |=> 🧡 AbstractApplicationContext
|        |    |=> 🧡 DefaultResourceLoader
|        |    |    |=> 🤍 ResourceLoader
|        |    |=> 🤍 ConfigurableApplicationContext
|        |=> 🤍 BeanDefinitionRegistry
|             |=> 🤍 AliasRegistry
|=> 🤍 ConfigurableWebApplicationContext
|=> 🤍 ThemeSource

除了以上的 StaticApplicationContext,通用类型的应用还有以下这些,它们都各自实现了对应的接口:

Java 的反射和起源于 JDK 1.5 的标注编程技术在 Spring 有重度的应用,管理注解配置的两个容器 AnnotationConfigApplicationContext、AnnotationConfigWebApplicationContext 是我比较关注的,它们可以实现基于 Java 配置类和各种注解配置加载 Spring 的应用上下文,避免使用 application.xml 进行配置带来的繁琐,相比 XML 配置,标注配置更加便捷。

两者虽然实现方式略有差别,但处理注解的逻辑一样,先扫描 scan(String... basePackages) 类路径 classpath 收集 bean 类信息,然后一一注册 register(annotatedClasses)

Variable number of arguments 是 Java 5 带来的新功能,使用省略号 ... 代替多参数。

这里给两段摘抄的示范代码:

SCENARIO 1

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;

public class MainApp {
   public static void main(String[] args) {
      ApplicationContext ctx = 
         new AnnotationConfigApplicationContext(HelloWorldConfig.class);

      HelloWorld helloWorld = ctx.getBean(HelloWorld.class);
      helloWorld.setMessage("Hello World!");
      helloWorld.getMessage();
   }
}

SCENARIO 2:

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class WebServletConfiguration implements WebApplicationInitializer{
    public void onStartup(ServletContext ctx) throws ServletException {
        AnnotationConfigWebApplicationContext webCtx = new AnnotationConfigWebApplicationContext();
        webCtx.register(SpringConfig.class);
        webCtx.setServletContext(ctx);
        ServletRegistration.Dynamic servlet = ctx.addServlet("dispatcher", new DispatcherServlet(webCtx));
        servlet.setLoadOnStartup(1);
        servlet.addMapping("/");
    }
}

配置类 HelloWorldConfig 或 SpringConfig 可以使用 Spring 的标注,如 @Configuation、@Bean、@Component、@ComponentScan 等进行修饰。

import org.springframework.context.annotation.Configuration;

@Configuration
public class HelloWorldConfig {
    public HelloWorldConfig() {
        System.out.println("HelloWorldConfig ...");
    }
}

每一种 Bean 类型在 Spring 中存在着不同的 scope,默认是 singleton,还有 prototype、request 等等。

直接使用 BeanFactory 容器对于 Spring 的使用来说并不多见,因为在企业级的应用中大多数都会使用 ApplicationContext。这里只是用于测试,简单实例化一个 bean 用于分析 Spring 的内部原理:

BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring/spring-test.xml"));
MySpringBean bean = (MySpringBean) beanFactory.getBean("mySpringBean");

单例在 Spring 的同一个容器内只会被创建一次,后续再获取 bean 直接从单例缓存中获取,当然这里也只是尝试加载,首先尝试从缓存中加载,然后再次尝试从 singletonFactorry 加载。因为在创建单例 bean 的时候会存在依赖注入的情况,而在创建依赖的时候为了避免循环依赖,Spring 会先行创建 bean 的 ObjectFactory 提早曝光加入到缓存中,一旦下一个 bean 创建时需要依赖上个 bean,则直接使用 ObjectFactory;就算没有循环依赖,只是单纯的依赖注入,如 B 依赖 A,如果 A 已经初始化完成,B 进行初始化时,需要递归调用 getBean 获取 A,这是 A 已经在缓存里了,直接可以从这里取到。

加载 Bean 使用到的几个重要的相关类或接口:

前面是 XML 配置文件的解析,接下来将会面临更大的挑战,就是 bean 加载的探索。bean 加载的功能实现远比 bean 的解析要复杂得多。

对于加载 bean 实例,可以在 Spring 中调用以下方法:

MySpringBean bean = (MySpringBean) beanFactory.getBean("mySpringBean");

那么这一过程中涉及的重要接口 BeanFactory Bean 工厂接口,加载 bean 定义以及获取实例;

AbstractAutowireCapableBeanFactory
    |=> AbstractBeanFactory
    |    |=> FactoryBeanRegistrySupport
    |    |    |=> DefaultSingletonBeanRegistry
    |    |         |=> SimpleAliasRegistry
    |    |         |    |=> implements AliasRegistry
    |    |         |=> implements SingletonBeanRegistry
    |    |=> implements ConfigurableBeanFactory
    |         |=> HierarchicalBeanFactory
    |         |    |=> BeanFactory
    |         |=> SingletonBeanRegistry
    |=> implements AutowireCapableBeanFactory {

在抽象类 AbstractBeanFactory 封装了一个相当复杂的 doGetBean,它就是实际加载 bean 实例的方法:

一般情况下,Spring 通过反射机制根据 bean 配置的 class 属性指定的实现类来实例化 bean。在某些情况下,实例化 bean 过程比较复杂,如果按照传统的方式,则需要在 bean 配置文件节点中提供大量 的配置信息,配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。 Spring 为此提供了 FactoryBean 的工厂类接口,注意名字和 BeanFactory 是相反的单词组合,用户可以通过实现该接口定制实例化 bean 的逻辑。

FactoryBean 接口地位很重要,Spring 自身就提供了 70 多个 FactoryBean 的实现。它们隐藏了实例化一些复杂 bean 的细节,给上层应用带来了便利。从 Spring 3.0 开始,FactoryBean 开始支持泛型,即接口声明改为 FactoryBean<T> 的形式:

Spring 容器初始化流程大致流程如下:

Spring Bean的生命周期:

Spring Bean的自动装配过程:

Spring AOP执行流程:

参考

上一篇 下一篇

猜你喜欢

热点阅读