本文主要是介绍Spring源码核心知识点凝练总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Spring源码核心知识点凝练总结
- 全局篇
- 个人对Spring的理解
- IOC理解
- DI理解
- Spring总结概括
- ApplicationContext与BeanFactory关系
- 生命周期篇
- Spring应用程序上下文生命周期
- SpringBoot应用程序上下文生命周期
- Bean的生命周期
- Bean常见的作用域
- BeanPostProcessor和BeanFactoryPostProcessor的区别
- 依赖注入和依赖查找来源是否相同
- ObjectFactory,FactoryBean和BeanFactory的区别
- Setter方法产生的循环依赖如何处理
- AOP篇
- 注解篇
- Controller ,Service,Repository 和 Component 的区别
- 持续更新中...
本文针对与Spring有关的常见问题进行整理。
全局篇
站在全局的角度,一览Spring框架的架构设计。
个人对Spring的理解
-
Spring框架作为IOC容器的落地实现,提供了一个灵活的"插座",其他组件只需要简单的"插上"即可享受Spring提供的基础设施支持- ,并且结合Spring一起使用。
-
Spring的核心在于它的IOC容器设计,我们可以通过
Spring应用程序上下文生命周期
和Spring Bean的生命周期
中提供的扩展点来个性化定制IOC容器,或者插手各个Bean的创建过程,对我们感兴趣的bean进行定制化处理。 -
Spring面向模块开发的,spring大家族中各个模块小模块之间都依附于Spring IOC这个核心底层模块,各个小模块之间也不存在强耦合关系,可以随插随用。
IOC理解
IOC全称 Inversion Of Control ,意为控制反转,通过IOC容器,我们把对象或者组件的创建过程透明化;我们无需关注创建细节,我们只需要把创建对象,属性赋值,这些工作交给IOC容器完成即可。
在复杂的系统,对象之间的依赖关系可能是错综复杂的,在这种情况下,由于IOC容器帮助我们屏蔽了对象构造和初始化的细节,我们只需要关注对象的应用即可。
IOC容器的职责:
- 依赖处理: 依赖查找和依赖注入
- 托管资源(bean或其他资源)的生命周期
- 配置管理(容器自身配置,外部化配置,托管的资源配置等)
DI理解
依赖查找和依赖注入是IOC的实现策略。
依赖查找既可以是用户主动调用IOC提供的接口进行查找,也可以IOC内部进行属性注入前,IOC自己调用进行依赖查找。依赖注入就是在依赖查找结束后,IOC容器将找到的依赖对象通过构造器,字段或者setter方法等方式注入到当前bean的属性中。
Spring总结概括
这里对Spring IOC容器做个小总结:
- Spring框架是对IOC容器的实现,提供依赖查找和依赖注入对依赖关系进行处理,同时负责管理Bean等资源的生命周期,并在Spring应用程序上下文生命周期和Bean生命周期中提供相关扩展接口,用于针对全局粒度和bean粒度进行扩展。
ApplicationContext与BeanFactory关系
- BeanFactory为我们提供了完整的IOC服务支持。
- ApplicationContext结合使用了装饰器模式和门面模式,装饰器模式对BeanFactory进行功能增强,门面模式向用户屏蔽多个模块之间协调工作的复杂性,主要包括: 环境配置管理模块,事件发布模块,资源管理模块,国际化模块。
BeanFactory作为底层的IOC容器,ApplicationContext 将底层IOC与其他各个模块结合使用,一起构建成为Spring应用上下文。
生命周期篇
Spring应用程序上下文生命周期
Spring应用程序上下文生命周期模板过程体现在AbstractApplicationContext的refresh方法中
- Spring应用程序上下文启动准备阶段: 设置相关属性,例如: 启动时间,状态标识,环境上下文配置对象Environment
- BeanFactory初始化阶段: 初始一个BeanFactory对象,根据ApplicationContext子类实现不同,采用不同方式从各自支持的属性源加载Bean原数据信息,并解析为对应的BeanDefinition,注册进BeanDefinitionRegistry中。
- BeanFactory准备阶段: 设置相关组件: 加载用户bean的类加载器,默认为线程上下文类加载器(可打破双亲委派机制);表达式语言解析器;属性编辑器;添加相关后置处理器和需要忽略依赖注入的相关接口配置。
- BeanFactory后置处理阶段: 在beanFactory实例化并准备完毕后,允许子类覆写该空回调接口,对IOC容器进行一些后置处理,如: 添加一些BeanPostProcessor。 web环境下的applicationContext子类会覆写此方法针对web环境进行一些特殊配置、
- 调用BeanFactoryPostProcessors相关回调接口: 主要是执行BeanDefinitionRegistry和BeanFactoryPostProcessor相关后置处理回调接口,先调用用户手动设置,再调用从容器中获取的,这其他还涉及到优先级顺序,具体可以参考这部分源码。通常使用BeanFactoryPostProcessor往容器中注入额外一些BeanDefinition。
- 注册BeanPostProcessor阶段: 从BeanDefinition集合中查询出所有类型为BeanPostProcessor的bean,并进行提前初始化,然后放入BeanFactory的BeanPostProcessor集合中集合管理。
- 初始化相关内建Bean: 初始化MessageSource用于国际化的Bean,ApplicationEventMulticaster事件多播器对象,ThemeSource对象。
- OnRefresh回调接口: web环境下的ApplicationContext会重写该钩子方法,用于在此刻启动web服务器。
- 事件监听器注册: 获取BeanDefinition集合中所有类型为ApplicationListener的事件监听器,然后注册到事件多播器中。
- BeanFactory初始化完成阶段: 核心是初始化所有Bean(除了部分提前已经初始化好的,如: 相关后置处理器),当然还要排除那些抽象bean,非单例bean,懒加载的bean。
- 所有bean初始化完成阶段: 在所有bean(非抽象,非单例,非懒加载)初始化后,Spring会再次遍历所有初始化好的单例bean对象,如果当前bean是SmartInitializingSingleton类型,则调用其afterSingletonsInstantiated回调方法。
- Spring应用上下文刷新阶段: 清除当前Spring应用上下文中的缓存,例如: 通过ASM扫描处理的元数据。发布上下文刷新事件。
SpringBoot应用程序上下文生命周期
- SpringApplication 构造函数内完成webApplicationType和mainClass的探测,以及通过SPI加载所有ApplicationContextInitializer和ApplicationListener的实现类,最后我们进入初始化的核心方法run。
- 通过SPI加载监听事件派发器EventPublishingRunListener,在该类构造函数中会注入得到SpringApplication中保存的ApplicationListener实现类集合。
- 事件派发器发布boot程序启动事件,事件派发器将该事件通知给ApplicationListener集合中所有实现类。
- 初始化环境上下文模块,同时由事件派发器发布环境上下文模块就绪事件
- 根据webApplicationType创建对应的ApplicationContext实例
- 对创建得到的ApplicationContext执行初始化准备过程,包括:
- 环境上下文,资源加载器等资源的设置;
- ApplicationContextInitializer相关实现类接口的回调;
- 事件派发器发布应用程序上下文就绪事件
- 遍历所有数据源,根据数据源类型不同,调用不同的Bean扫描器完成Bean资源的扫描和Bean的注册过程
- 此时所有Bean均已被扫描得到,并以BeanDefintion的形式保存在了BeanDefinitionRegistry中,然后由事件派发器发布Bean加载完成的事件
- 调用ApplicationContext的refersh方法,下面进入Spring应用程序上下文生命周期流程,参考上节
- 事件派发器发布上下文启动成功事件
- 寻找出容器出类型为ApplicationRunner和CommandLineRunner的bean集合,依次回调他们的启动监听方法
- 事件派发器发布上下文运行中事件
Bean的生命周期
Bean的生命周期模板过程体现在AbstractBeanFactory的createBean方法中
- Bean配置和扫描注册阶段:
- 元信息配置阶段: 面向资源(xml,properties) ,面向注解或者面向API(配置类)进行配置
- 元信息解析阶段: 将元信息统一解析为BeanDefinition对象,该对象包含定义Bean的所有信息,并且采用不同方式加载的bean,会对应不同的BeanDefinition实现。具体参考: BeanDefinition体系结构
- 元信息注册阶段: 将BeanDefinition配置原信息保存到BeanDefinitionRegistry中。
- bean实例化阶段(省略缓存检查和bean提前暴露等阶段):
- BeanDefinition合并阶段: 定义的bean可能存在父子关系,需要进行属性合并,存在相同配置则覆盖父属性,并且不同来源的bean,采用不同BeanDefinition进行存储,这里需要统一转换为RootBeanDefintion。
- 实例化阶段: 从BeanDefinition中获取bean的全类名,从ClasUtils中获取默认的线程上下文类加载器,利用线程上下文类加载器去加载用户的bean,然后实例化出一个bean实例对象。这个过程涉及构造器注入,实例化前后两个扩展点:
InstantiationAwareBeanPostProcessor:
- postProcessBeforeInstantiation 方法
- postProcessAfterInstantiation 方法
- Bean属性赋值阶段:
- 属性赋值阶段: 将实例化完成的bean包装为BeanWrapper,并利用BeanWrapper完成setter方式的依赖注入。依赖注入前首先需要获取该对象所有属性与属性值的映射关系,也就是PropertyValues,其中一部分可能是我们通过配置文件指定的,在元信息解析阶段就已经放入BeanDefinition中,这部分属性借助BeanWrapper完成依赖注入。还有一部分是通过注解方式指定的,需要通过相关BeanPostProcessor解析各自支持的注解完成依赖注入:
属性依赖注入过程中涉及到的核心扩展接口:
InstantiationAwareBeanPostProcessor:
- postProcessProperties 方法涉及到的常见bean后置处理器有:
- CommonAnnotationBeanPostProcessor(@Resource、@PostConstruct、@PreDestroy)
- AutowiredAnnotationBeanPostProcessor(@Autowired、@Value)注意: 如果我们既在配置文件中声明了属性依赖注入的配置,又通过注解形式指定了依赖注入配置,那么最终只会执行一次依赖注入,具体源码为:
InjectedElement类中的inject方法,相关bean后置处理器会调用该方法完成最终的属性注入,该方法在进行最终注入前,
会调用checkPropertySkipping方法判断我们是否已经通过配置文件指明了依赖关系,如果是跳过注入,交由BeanWrapper完成最终的依赖注入
- Bean初始化阶段:
- Aware接口回调阶段: 如果当前bean实现了相关Aware接口(例如: BeanNameAware,ApplicationContextAware),这里会通过回调接口完成依赖注入。
- 初始化方法调用阶段: 调用当前bean配置了相关初始化方法,如: @PostConstruct标注方法,实现InitiallizingBean接口的afterPropertiesSet方法,和自定义初始化方法。在调用初始化方法前后,还提供了两个扩展点:
BeanPostProcessor:
- postProcessBeforeInitialization
- postProcessAfterInitialization
- Bean初始化完成阶段:
- 当前bean初始化完毕后,还会进行循环依赖检查,判断是否出现提前暴露的bean和最终放入容器bean不一致的问题,主要是因为提前暴露的bean没有进行代理,而最终注入容器中的bean是被代理过的。
- 为当前bean注册相关销毁方法,如: @PreDestory标注的方法,DisposableBean接口提供的destroy方法,destroy-method自定义的销毁方法。
Bean常见的作用域
- singleton: 默认作用域,一个BeanFactory只有一个bean实例
- prototype: 原型作用域,每次依赖查找和依赖注入都生成新的bean对象
- request: 将Spring Bean存储在ServletRequest上下文中
- session: 将Spring Bean存储在HttpSession中
- application: 将Spring Bean存储在ServletContext中
BeanPostProcessor和BeanFactoryPostProcessor的区别
-
工作时机不同
- BeanFactoryPostProcessor工作时机: Spring应用程序上下文生命周期中对初始化完毕的BeanFactory进行后置处理
- BeanPostProcessor工作时机: 每个Bean的生命周期涉及到的相关生命周期回调接口
-
作用不同
- BeanFactoryPostProcessor主要用于给初始化完毕的BeanFactory中添加一些额外的BeanDefinition
- BeanPostProcessor主要用于在每个Bean的生命周期回调接口中进行拦截,针对自己感兴趣的bean进行定制化处理
-
调用者不同:
- BeanFactoryPostProcessor只能由ApplicationContext进行调用
- BeanPostProcessor保存于BeanFactory中,由BeanFactory进行调用
依赖注入和依赖查找来源是否相同
- 依赖查找(getBean)的来源仅限于BeanDefinition集合和单例对象集合
- 依赖注入的来源还包括Resolvable Dependency,即Spring应用上下文定义的可以处理的注入对象,以及@Value所标注的外部化配置
ObjectFactory,FactoryBean和BeanFactory的区别
- ObjectFactory可关联某一类型的bean,通过提供一个getObject方法返回目标bean对象。在Spring中ObjectFactory最出名的应用莫过于
延迟依赖查找
。- 通过该特性,Spring处理setter方法产生的循环依赖时,可以在某个bean实例化完毕后,先缓存一个ObjectFactory对象(调用getObject方法可返回当前正在初始化的Bean对象),如果初始化过程中依赖的对象又依赖于当前Bean,会先通过缓存的ObjectFactory对象获取当前正在初始化的Bean,这样一来就解决了setter方法产生的循环依赖问题。
- FactoryBean也可以关联一个Bean对象,通过getObject方法返回目标bean对象。FactoryBean对象在被依赖注入或依赖查找时,得到的Bean是通过getObject方法返回的目标对象。如果我们想要获取FactoryBean这个对象本身,需要在beanName前面加上&。
- 我们可以通过FactoryBean实现复杂的初始化逻辑,例如: 在Spring集成MyBaits项目中,会为每个Mapper接口生成一个MapperFactoryBean对象,当我们注入Mapper接口时,实际上就是通过调用MapperFactoryBean的getObject方法返回一个代理对象,关于数据库的操作都是通过该代理对象完成的。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {private Class<T> mapperInterface;@Overridepublic T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}@Overridepublic Class<T> getObjectType() {return this.mapperInterface;}@Overridepublic boolean isSingleton() {return true;}...
}
- BeanFactory是Spring底层的IOC容器,里面保存了所有单例Bean,负责提供完整的IOC服务支持。
Setter方法产生的循环依赖如何处理
循环依赖是指Bean字段注入出现的循环依赖,构造器注入产生的循环依赖对于Spring来说无法自动解决,可以通过延迟初始化来处理,并且Spring只解决单例模式下的循环依赖。
Spring解决循环依赖主要借助于3个Map集合:
- singletonObjects (一级缓存) : 里面保存了所有已经初始化好的单例Bean
- earlySingletonObjects(二级缓存): 里面保存从
三级缓存
中获取到的正在初始化的Bean - singletonFactories(三级缓存): 里面保存了正在初始化的Bean对应的ObjectFactory,通过调用ObjectFactory的getObject方法,我们能够获取到正在初始化的Bean对象,然后将其放入二级缓存中,并从三级缓存移除。
为什么需要三级缓存,二级缓存不就能够解决setter造成的循环依赖问题了吗?
- 三级缓存中的ObjectFactory的getObject方法中会调用SmartInstantiationAwareBeanPostProcessor#getEarlyBeanReference(…) 方法尝试对当前bean进行代理,然后返回一个代理对象,确保提前暴露的bean如果需要被代理,也是被代理过的。
- 对于不需要代理的Bean,singletonFactories三级缓存确实没必要,但是AOP是Spring体系中核心一员,如果没有singletonFactories三级缓存,意味着Bean在实例化后就需要完成AOP代理,这违背了Spring的设计原则。
- Spring是通过AbstractAutoProxyCreator这个自动代理创建器在Bean完成属性注入和初始化方法调用后,才会对bean尝试进行代理,而不是实例化后里面进行AOP代理。
- 如果出现了循环依赖,那么只有给Bean先创建代理,但是在没有出现循环依赖的情况下,设计之初就是让Bean在完成创建好后才进行AOP代理。
如果产生了循环依赖,那么自动代理创建器的getEarlyBeanReference方法中,会对bean尝试进行代理,并进行标记 ,在postProcessAfterInitialization方法中发现getEarlyBeanReference方法已经被调用过,那么此时就会跳过代理尝试。
@Transactional注解底层就是借助自动代理创建器完成的对象代理所以不存在循环依赖问题,但是@Async注解底层使用的是AsyncAnnotationBeanPostProcessor后置处理器,没有实现getEarlyReference方法,所以在循环依赖场景下,会报错。
三级缓存完整参考此文
AOP篇
AOP 规范主要包括四个核心概念:
- PointCut 指明我们需要对容器中的哪些Bean进行代理
- JoinPoint 指明我们需要在哪个时机点织入拦截逻辑,常见的时机点有方法调用前后,属性读取前后,对象构造前后等
- Advice 指明具体需要织入的拦截逻辑的实现,比如方法执行前拦截,进行日志打点记录
- AspectJ 作为一个切面,包含上面三个模块 ,负责完成对象过滤,对象织入的完整过程
在Spring AOP模块中,PointCut(切点),Advice(增强器),Advisor(切面) 都有各自单独的一套继承体系,而由于Spring只支持在方法执行前后这个时机点进行切入,所以就无需为JointPoint单独声明一套继承体系了。
Spring AOP 实现有两种方式,一种是较为久远的编程式,和目前最常用的声明式;
编程式:
- 编程式实现是借助代理工厂ProxyFactory完成对象代理,我们需要指定被代理的目标对象和能够切入当前目标对象的切面列表。
- ProxyFactory生成得到的代理对象我们称之为AdvisedBean(被增强了的Bean),并且根据目标对象是否实现接口等情况,采用不同的代理方式,同时为不同的代理方式提供不同的InvocationHandler实现,例如JDK动态代理提供的JdkDynamicAopProxy。
- 最后ProxyFactory会将保存了代理对象上下文信息的AdvisedSupport设置到对应的InvocationHandler实现中去;AdvisedSupport保存了目标对象,能应用在目标对象上的增强器链集合和代理对象实现的所有接口;
- 代理对象方法被调用时,会从AdvisedSupport中取出增强器链集合,然后借助AdvisorChainFactory过滤出能够切入当前方法的增强器集合,最后再通过AdvisorAdapterRegistry将所有Advice统一适配为MethodInterceptor类型,然后返回。
声明式:
- . 编程式实现的麻烦点在于需要手动依次指定哪些对象需要进行代理,以及需要应用哪些切面到哪些对象上;我们可以将该判断过程使用模版方法模式固定下来,同时借助BeanPostProcessor提供的回调埋点,拦截所有Bean的创建过程,让每一个Bean都走一遍固定下来的模版方法,最终就完成了对每个Bean的判断和代理过程。
- Spring AOP模块为我们提供了一个现成的抽象自动代理创建器实现,即AbstractAutoProxyCreator,其固定下来了一套模版代理流程,位于wrapIfNecessary方法中。
- 抽象自动代理创建器会在wrapIfNecessary中对传入的每个Bean依次尝试进行代理判断,此过程分为两个阶段:
- 获取所有切中当前Bean的Advisor列表,具体如何获取和过滤,由子类实现
- 如果该列表不为空,则使用ProxyFactory对当前Bean执行代理,传入的增强器集合就是一阶段获取到的
注解篇
Controller ,Service,Repository 和 Component 的区别
Controller ,Service,Repository 都 " 继承 " 了 Component 注解,同时 Spring 在启动阶段会启动Bean扫描器来扫描basePackage路径下所有的Bean:
- Bean扫描器此处指ClassPathBeanDefinitionScanner,其负责根据传入的basePackage数组为起始路径进行扫描
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();// 1. 依次扫描每个包路径for (String basePackage : basePackages) {// 2. 扫描当前包路径下的类,过滤得到所有满足要求的BeanSet<BeanDefinition> candidates = findCandidateComponents(basePackage);// 3. 对后选Bean进行处理加注册的逻辑...}return beanDefinitions;}public Set<BeanDefinition> findCandidateComponents(String basePackage) {... return scanCandidateComponents(basePackage);}private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;// 1. 获取当前包路径下所有类Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);...// 2. 依次处理每个类for (Resource resource : resources) {if (resource.isReadable()) {MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);// 3. 判断当前类上是否存在对应的注解if (isCandidateComponent(metadataReader)) {// 4. 符合要求,添加进候选Bean集合...}}} ...} return candidates;}protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {// 1. 哪些类需要过滤掉 for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}// 2. 哪些类需要保留下来for (TypeFilter tf : this.includeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return isConditionMatch(metadataReader);}}return false;}
ClassPathBeanDefinitionScanner 在默认初始化情况下,会在includeFilters集合中添加一个类型过滤器,用于过滤出所有标注了Component注解的类:
protected void registerDefaultFilters() {// AnnotationTypeFilter会递归向上查找Component注解,因此那些"继承"了Component注解的注解也会生效this.includeFilters.add(new AnnotationTypeFilter(Component.class));...}
上面讲述了上述几个注解的共同点,就是都会被Bean扫描器扫描到,然后注册到IOC中,下面来看看他们之间的不同点。
Controller ,Service,Repository 第一个不同点是分别用来标识MVC三层中不同的层,用于实现Bean所属层级的区分,我们可以针对某个层级以对应注解作为标识,单独进行处理。例如spring对Repository就单独提供了PersistenceExceptionTranslationPostProcessor后置处理器,用于拦截所有标注了@Repository注解的Bean,然后当这些Bean方法执行出现异常时,会进行异常翻译,统一转换为DataAccessException。
第二个不同点主要是Controller注解在springmvc父子容器存在的情况下,会由子容器单独进行扫描,同时子容器也只会扫描Controller这个注解,而由父容器负责扫描Service等其他注解。这个隔离操作可以通过在子容器对应的ClassPathBeanDefinitionScanner中添加IncludeFileters完成,同时在父容器对应的ClassPathBeanDefinitionScanner中添加ExcludeFilters完成。
由于Spring设定的Bean查找流程,会优先查找父容器,所以此处子容器中的Controller Bean可以注入父容器中的Service Bean,但是反过来就不行了,因此父容器无法访问子容器中的Bean。
同时默认只会在父容器中添加事务相关的后置处理器,而不会在子容器中添加,所以即使我们让子容器也可以扫描Service注解,但是Service Bean是无法享受到Spring的声明式事物支持的,这一点需要注意。
springmvc中,当我们使用的Handler类型是基于注解实现的,此时Handler和请求映射关系会由RequestMappingHandlerMapping保存,其会在启动初始化时,扫描容器中所有Bean,过滤出所有标注了@Controller或者@RequestMapping注解的Bean作为Handler。
持续更新中…
由于最近比较繁忙,所以每周会不定期更新内容…
这篇关于Spring源码核心知识点凝练总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!