这些不知道,别说你熟悉 Spring

宅哥聊构架 前端 2022-12-06

我们大多数 Java 程序员的日常工作基本都是在做业务开发,俗称 crudboy。

作为 crudboy 的你有没有这些烦恼呢?

  1. 随着业务的迭代,新功能的加入,代码变得越来越臃肿,可维护性越来越低,慢慢变成了屎山

  2. 遇到一些框架层的问题不知道怎么解决

  3. 面试被问到使用的框架、中间件原理、源码层东西,不知道怎么回答

  4. 写了 5 年代码了,感觉自己的技术没有理想的长进

如果你有上述这些烦恼,我想看优秀框架的源码会是一个很好的提升方式。通过看源码,我们能学到业界大佬们优秀的设计理念、编码风格、设计模式的使用、高效数据结构算法的使用、魔鬼细节的巧妙应用等等。这些东西都是助力我们成为一个优秀工程师不可或缺的。

如果你打算要看源码了,优先推荐 Spring、Netty、Mybatis、JUC 包。

这些不知道,别说你熟悉 Spring

Spring 扩展

我们知道 Spring 提供了很多的扩展点,第三方框架整合 Spring 其实大多也都是基于这些扩展点来做的。所以熟练的掌握 Spring 扩展能让我们在阅读源码的时候能快速的找到入口,然后断点调试,一步步深入框架内核。

这些扩展包括但不限于以下接口:

BeanFactoryPostProcessor:在 Bean 实例化之前对 BeanDefinition 进行修改

BeanPostProcessor:在 Bean 初始化前后对 Bean 进行一些修改包装增强,比如返回代理对象

Aware:一个标记接口,实现该接口及子接口的类会收到 Spring 的通知回调,赋予某种 Spring 框架的能力,比如 ApplicationContextAware、EnvironmentAware 等

ApplicationContextInitializer:在上下文准备阶段,容器刷新之前做一些初始化工作,比如我们常用的配置中心 client 基本都是继承该初始化器,在容器刷新前将配置从远程拉到本地,然后封装成 PropertySource 放到 Environment 中供使用

ApplicationListener:Spring 事件机制,监听特定的应用事件(ApplicationEvent),观察者模式的一种实现

FactoryBean:用来自定义 Bean 的创建逻辑(Mybatis、Feign 等等)

ImportBeanDefinitionRegistrar:定义@EnableXXX 注解,在注解上 Import 了一个 ImportBeanDefinitionRegistrar,实现注册 BeanDefinition 到容器中

InitializingBean:在 Bean 初始化时会调用执行一些初始化逻辑

ApplicationRunner/CommandLineRunner:容器启动后回调,执行一些初始化工作

上述列出了几个比较常用的接口,但是 Spring 扩展远不于此,还有很多扩展接口大家可以自己去了解。

Spring SPI 机制

在讲接下来内容之前,我们先说下 Spring 中的 SPI 机制。Spring 中的 SPI 主要是利用 META-INF/spring.factories 文件来实现的,文件内容由多个 k = list(v) 的格式组成,比如:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\   com.dtp.starter.adapter.dubbo.autoconfigure.ApacheDubboTpAutoConfiguration,\   com.dtp.starter.adapter.dubbo.autoconfigure.AlibabaDubboTpAutoConfiguration org.springframework.boot.env.EnvironmentPostProcessor=\   com.dtp.starter.zookeeper.autoconfigure.ZkConfigEnvironmentProcessor 复制代码

这些 spring.factories 文件可能是位于多个 jar 包中,Spring 容器启动时会通过 ClassLoader.getResources() 获取这些 spring.factories 文件的全路径。然后遍历路径以字节流的形式读取所有的 k = list(v) 封装到到一个 Map 中,key 为接口全限定类名,value 为所有实现类的全限定类名列表。

上述说的这些加载操作都封装在 SpringFactoriesLoader 类里。该类很简单,提供三个加载方法、一个实例化方法,还有一个 cache 属性,首次加载到的数据会保存在 cache 里,供后续使用。

SpringBoot 核心要点

上面讲的 SPI 其实就是我们 SpringBoot 自动装配的核心。

何为自动装配?

自动装配对应的就是手动装配,在没 SpringBoot 之前,我们使用 Spring 就是用的手动装配模式。在使用某项第三方功能时,我们需要引入该功能依赖的所有包,并测试保证这些引入包版本兼容。然后在 XML 文件里进行大量标签配置,非常繁琐。后来 Spring4 里引入了 JavaConfig 功能,利用 @Configuration + @Bean 来代替 XML 配置,虽然对开发来说是友好了许多,但是这些模板式配置代码还是很繁琐,会浪费大量时间做配置。Java 重可能也就是这个时候给人留的一种印象。

在该背景下出现了 SpringBoot,SpringBoot 可以说是稳住了 Java 的地位。SpringBoot 提供了自动装配功能,自动装配简单来说就是将某种功能(如 web 相关、redis 相关、logging 相关等)打包在一起,统一管理依赖包版本,并且约定好相关功能 Bean 的装配规则,使用者只需引入一个依赖,通过少量注解或简单配置就可以使用第三方组件提供的功能了。

在 SpringBoot 中这类功能组件有一个好听的名字叫做 starter。比如 spring-boot-starter-web、spring-boot-starter-data-redis、spring-boot-starter-logging 等。starter 里会通过 @Configuration + @Bean + @ConditionalOnXXX 等注解定义要注入 Spring 中的 Bean,然后在 spring.factories 文件中配置为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的实现,就可以完成自动装配了。

具体装配流程怎么样的呢?

其实也很简单,基本都是 Spring 中的知识,没啥新颖的。主要依托于@EnableAutoConfiguration 注解,该注解上会 Import 一个 AutoConfigurationImportSelector,看下继承关系,该类继承于 DeferredImportSelector。

主要方法为 getAutoConfigurationEntry()

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {       // 1       if (!isEnabled(annotationMetadata)) {         return EMPTY_ENTRY;       }       AnnotationAttributes attributes = getAttributes(annotationMetadata);       // 2       List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);       configurations = removeDuplicates(configurations);       // 3       Set<String> exclusions = getExclusions(annotationMetadata, attributes);       checkExcludedClasses(configurations, exclusions);       configurations.removeAll(exclusions);       // 4       configurations = getConfigurationClassFilter().filter(configurations);       fireAutoConfigurationImportEvents(configurations, exclusions);       return new AutoConfigurationEntry(configurations, exclusions); } 复制代码

方法解读

  1. 通过 spring.boot.enableautoconfiguration 配置项判断是否启用自动装配,默认为 true

  2. 使用上述说的 SpringFactoriesLoader.loadFactoryNames() 加载所有 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的实现类的全限定类名,借助 HashSet 进行去重

  3. 获取 @EnableAutoConfiguration 注解上配置的要 exclude 的类,然后排除这些特定类

  4. 通过 @ConditionalOnXXX 进行过滤,满足条件的类才会留下,封装到 AutoConfigurationEntry 里返回

那 getAutoConfigurationEntry() 方法在哪儿调用呢?

public void refresh() throws BeansException, IllegalStateException {     // Allows post-processing of the bean factory in context subclasses.     postProcessBeanFactory(beanFactory);     StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");     // Invoke factory processors registered as beans in the context.     invokeBeanFactoryPostProcessors(beanFactory);     // Register bean processors that intercept bean creation.     registerBeanPostProcessors(beanFactory);     beanPostProcess.end();     // Initialize message source for this context.     initMessageSource();     // Initialize event multicaster for this context.     initApplicationEventMulticaster();     // Initialize other special beans in specific context subclasses.     onRefresh();     // Check for listener beans and register them.     registerListeners();     // Instantiate all remaining (non-lazy-init) singletons.     finishBeanFactoryInitialization(beanFactory);     // Last step: publish corresponding event.     finishRefresh(); }
Apipost 私有化火热进行中

评论