spring ioc原理浅析

ioc控制反转

传统创建对象,需要在用到的地方new对象,如果一个类的创建方式发生改变,那么所有调用到的地方都要改动。
spring基于di依赖倒置的思想,通过ioc控制反转将bean的创建、初始化、销毁生命周期都交给spring容器,高层不再直接依赖底层,而是通过第三方获取。调用类需要用到什么对象,只需要通过spring获取。

好处
提高了代码的可维护性,对象的管理只需要一个地方配置,比如一个被很多类依赖的对象,某个配置项需要变动,那么只需要修改配置这个对象的地方,其他通过spring注入这个对象的都可以直接生效,不用重新设置。

AbstractApplicationContext的refresh方法

spring启动执行的核心源码,bean注册逻辑

容器加载流程概述

1 预准备阶段,设定容器开始时间,标记容器已开启等
2 加载配置文件,将bean封装成beandefinion,注册到beanfactory(BeanDefinitionRegistry
3 设置 BeanFactory 的类加载器,添加几个 特殊的BeanPostProcessor,手动注册几个特殊的 bean。预准备beanfactory阶段。
4 注册BeanFactoryPostProcessor
5 执行BeanFactoryPostProcessor
6 注册BeanPostProcessor
7 初始化当前 ApplicationContext 的 MessageSource
8 初始化事件广播器
9 onfresh方法。子类可以实现这个类,在bean初始化前做一些处理
10 注册监听器(实现了ApplicationListener接口的类)
11 初始化bean,bean的生命周期
12 广播事件,容器初始化完成。

扫描类,为扫描到符合条件的bean创建对象,加入容器

ClassPathScanningCandidateComponentProvider
spring通过该类扫描指定包下的类和配置文件(spring通过dom框架解析xml),扫描的类可以通过@ComponentScan配置includeFilters包含的类型和excludeFilters排除的类型,该类会通过匹配获得满足的类,通过asm获取这些类文件的字节码信息,将类的基本信息封装成BeanDefinition类存储到一个集合中(这时还没创建对象,类还未加载)。

ClassPathBeanDefinitionScanner
该类继承自 ClassPathScanningCandidateComponentProviderd,除了扫描包之外会对BeanDefinition做一些加工,设置bean的作用域,设置bean的代理模式(默认是jdk代理,如果被代理类没有实现接口则用cglib),注册到spring容器。

后置处理器

BeanFactoryPostProcessor - bean工厂后置处理器
作用:实现这个接口,可以让spring在类被加载后,bean被实例化前,修改bean对应类的定义属性,比如修改bean的作用域、修改类的属性值。
原理:实现工厂后置处理器的类会先被实例化,保存到AbstractApplicationContext的beanFactoryPostProcessors列表,在创建bean会先执行该列表的执行方法。
例子:通过PropertyPlaceholderConfigurer进行配置替换,实现了BeanFactoryPostProcessor.

BeanPostProcessor - bean后置处理器
作用:spring提供的扩展bean的类,在bean被实例化之后修改bean的内容。可以在后置处理器中,为该bean生成代理对象,在bean的方法前后加上日志等信息。
原理:在实例化后会通过AbstractBeanFactory维护的beanPostProcessors列表。创建bean后,遍历所有的处理器对bean进行加工。如果符合则会经过该处理器会处理。比如方法上加了事务注解,会通过后置处理器生成代理类,返回通过代理类生成的bean,在方法初始化前后加上事务开启和提交。
运用:可以自定义类实现BeanPostProcessor类,加入spring容器,这样bean的初始化前后就会经过处理器处理。
例子:ApplicationContextAwareProcessor用于为实现了ApplicationContextAware接口的类设置spring上下文,最先被注入到BeanPostProcessor注册器。

**需要重写两个方法:
postProcessBeforeInitialization方法,在bean被初始化之前调用。初始化前指的是bean实例化之后,如果类实现了InitializingBean接口会调用的afterPropertiesSet的进行初始化、还有init-method的初始化
postProcessAfterInitialization
在初始化之后被调用。

一些内置的BeanPostProcessor
CommonAnnotationBeanPostProcessor 支持@Resource注解
RequiredAnnotationBeanPostProcessor 支持@Required注解
AutowiredAnnotationBeanPostProcessor 支持@Autowired注解
PersistenceAnnotationBeanPostProcessor 支持@PersistenceUnit和@PersistenceContext注解
ApplicationContextAwareProcessor 支持注入ApplicationContext等容器对象

BeanDefinitionRegistryPostProcessor
实现该接口,spring在初始化时,会调用该接口,postProcessBeanDefinitionRegistry方法提供了BeanDefinitionRegistry参数,通过这个参数可以往spring中添加BeanDefinition

后置处理器的注册顺序
调用registerBeanPostProcessors之前就注册的>实现了PriorityOrdered>实现Ordered>没有实现排序接口。(省略了一些,具体看源码)
源码PostProcessorRegistrationDelegate::registerBeanPostProcessors
需要注意的是有一些加工类在开始注册加工类前就先注册了,比如ApplicationContextAware的加工类ApplicationListenerDetector在没注册加工类前就已经加进去了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w3tG7Oor-1585213898176)(evernotecid://EA56B0F8-13B5-4B11-8191-1E827D9FC2A6/appyinxiangcom/22897236/ENResource/p22)]

生命周期

执行顺序如下:

  1. 执行beanFactoryPostProcessor类的postProcessBeanFactory方法,影响bean的实例化。
    2.实例化bean
  2. 如果类实现了BeanNameAware接口,调用setBeanName设置bean的名字。
  3. 如果类实现了BeanClassLoaderAware接口,调用setBeanClassLoader设置当前bean的类加载器。
  4. 如果类实现了BeanFactoryAware将调用setBeanFactory方法注入beanFactory。
  5. 遍历所有当前容器中的beanPostProcessor,执行beanPostProcessorpostProcessBeforeInitialization,在bean开始初始化前修改这个bean的内容。
  6. 初始化bean。如果类实现了InitializingBean则调用afterPropertiesSet方法,调用bean声明的init-method方法,详见下面的初始化对象部分。
  7. 执行PostProcessorpostProcessAfterInitialization方法,在对象初始化之后修改bean的内容。
  8. bean的销毁,如果类实现了DisposableBean的destory方法,会先执行该方法。再执行bean上声明的destroy-method方法。

提要

  • 第三步开始可以参考AbstractAutowireCapableBeanFactory类的initializeBean方法
  • 实现beanFactoryPostProcessor、beanPostProcessor的类都会被提前实例化放到容器中。之后所有bean的创建都会经过这个两种处理器处理。
  • spring内置很多beanPostProcessor类,比如实现ApplicationContextAware可以获取到applicationContext就是通过实现了beanPostProcessor的ApplicationContextAwareProcessor处理器处理的
初始化对象

spring提供3种对象创建后的初始化方法
执行顺序

  1. 构造函数
  2. @PostConstruct注解的方法(jsr250规范约定的注解)
  3. 实现InitializingBean类的afterPropertiesSet方法
  4. @bean注解或xml的bean标签上init-method方法

PostConstruct
原理:通过bean后置处理器InitDestroyAnnotationBeanPostProcessor处理,在初始化之前将类中被@PostConstruct修饰的方法加入到待初始化的方法集合中,将@PreDestroy修饰的方法加到销毁前待执行的方法集合。(包括父类的方法)。自底向上遍历到object类结束。最后遍历集合中的方法,父类的PostConstruct修饰的方法会优先执行,同个类中多个PostConstruct修饰的方法没有明确的前后顺序。(jsr250约定PostConstruct同个类的方法只能有一个修饰,spring没有遵从)

注意不要在这里依赖实现了ApplicationContextAware设置applicationContext的方法,可能拿不到。都是通过实现BeanPostProcessor去处理bean的。

InitializingBean
原理:创建bean之后会用instanceof判断当前类是否为InitializingBean的子类,如果是则调用该类的afterPropertiesSet方法。

init-method
原理:判断完InitializingBean后会根据类加载进spring被封装成BeanDefinition,获取类存储的init-method方法,调用。

源码参考AbstractAutowireCapableBeanFactory##initializeBean

依赖注入、循环依赖

注入的方式

  1. 在set方法上注入
  2. 在字段上注入
  3. 在构造函数上注入

循环依赖与注入方式

  • set方法和在字段上注入spring都能自动解决循环依赖。
  • 构造函数上注入不能解决,在编译期间就会报循环依赖错误。
  • 非单例Bean的循环依赖是不能解决的。

注入方式选择

  • spring官方建议使用构造函数注入,因为可以在编译期就发现循环依赖的问题,且能确保bean加载后是一个完整的bean,即bean依赖的对象是被注入成功的。
  • 通过构造函数注入也并非就一定会编译期就报错循环依赖,需要相互依赖的双方都通过构造函数注入。假设类a通过字段注入b、b通过构造函数注入a,则如果加载顺序是先加载a,则注入b时会需要一个a对象,此时a因为非构造依赖b,可以创建一个空对象a赋给b,b创建后再赋给a,则可以创建成功。如果加载顺序反过来则不行。
  • Spring不支持依赖注入static静态变量。如果需要注入静态变量,可以通过set方法注入后,手动赋值。
@Autowired 
set(A o){
  B.a = o;
}

或者另外放一个非静态的注入后再设置过去。
不建议这么使用,静态变量扩大了这个变量的使用范围

spring解决循环依赖原理
假设a依赖b

  1. 先创建空的对象a,封装对象a的工厂类,放入singletonFactories(即如果需要获取一个还没初始化的对象,可以通过这个bean工厂获取)。
  2. 设置a依赖的对象b时(设置对象依赖通过Autowired和Resource的后置处理器处理),先创建对象b,将对象b放入singletonFactories,设置依赖时发现依赖对象a。
  3. 从singletonFactories中获取到对象a,将对象a设置给对象b,并将对象a放入earlySingletonObjects。
  4. b创建完成,获取到b对象设给对象a。初始化结束。

循环依赖遇到的坑
类a在初始化后通过applicationContext.getBeansOfType()获取某个类的所有实现类时,因为实现类b最终依赖了类a,导致循环依赖,这个时候如果配置类a懒加载类b,类b会被懒加载成一个NullBean(NullBean会被getBeansOfType忽略掉),然后类a就获取到的实现类就缺少了类b。
改成如果类b是懒加载a,b就能正常创建返回。

存储bean的类 DefaultSingletonBeanRegistry的关键字段
singletonsCurrentlyInCreation:存放正在创建中的对象
singletonFactories:存放封装了还未初始化对象的对象工厂。
earlySingletonObjects:存放还没初始化的对象
singletonObjects:存放初始化结束的对象

源码参考: AbstractAutowireCapableBeanFactory##createBean

为什么要用singletonFactories?直接把空对象缓存到earlySingletonObjects不可以吗?
因为返回该Factory返回bean的时候,会遍历所有后置处理器中实现了SmartInstantiationAwareBeanPostProcessor的后置处理器,即让用户可以通过实现该处理器在返回未初始化的bean时做一些修改bean内容的操作,如果直接放到earlySingletonObjects中就少了这一步。

作用域

创建bean的时候会根据BeanDefinition先判断作用域是否为单例模式,不是再判断是否为prototype模式,再不是则根据当前scope名获取Scope封装类,根据该封装类获取bean。(代码逻辑在AbstractBeanFactorydoGetBean方法)

singleton:单例,spring为该类在spring容器中只会为该类创建一个对象。单例用beanName做map的key,对象作为value。DefaultListableBeanFactory类的
preInstantiateSingletons负责初始化单例对象,通过调用DefaultSingletonBeanRegistrygetSingleton方法获取单例对象,该类通过map类型singletonObjects字段存储已经生成的单例对象。

prototype
该类型每次客户端获取对象都会重新创建对象,即对象不共享。

request
对象只在当前http请求共享,每次新的请求都会重新创建。
原理:

  1. spring在开始创建bean之前会往beanFactory注册request、session作用域获取对象的封装类Scope类的子类。代码逻辑在(WebApplicationContextUtils类的registerWebApplicationScopes方法)
  2. request作用域用封装类的是RequestScope类,继承自AbstractRequestAttributesScope,通过get方法获取对象。该方法通过类的ThreadLocal获取当前线程中存储的RequestAttributes类。request作用域通过RequestAttributes保存的HttpServletRequest设置存取属性值。
  3. RequestContextHolder中存储的
    RequestAttributes是什么时候设置进ThreadLocal的?这就涉及到springmvc中的FrameworkServlet类,该类继承自httpServlet,在接受http请求的时候会将httpServletRequest封装成ServletRequestAttributes存放到 RequestContextHolder中。

session
对象只在session共享,不同的session使用不同的bean。
原理:与request相似,不同的是session用的scope是SessionScope,存取对象时会通过ServletRequestAttributes存储的HttpServletRequest获取到session,通过session进行对象存取。
可能存在的问题:如果使用session作用域,在客户端第一次访问时,如果出现并发访问的情况,由于session的生成需要根据cookie携带的sessionId判断是否需要创建,第一次访问由于请求都没带上sessionId可能会导致两个请求都去创建bean,存放到各自的session对象中。由于最终cookie只有一个sessionId,所以会有一个请求创建的对象丢失。如果对象中包含一个计数器之类的参数,可能会存在计数丢失。

application
将对象存储在ServletContext,在整个web应用程序中是唯一的。存取操作通过ServletContextScope进行操作,与session方式一样会将该封装类注册到beanFactory。

proxyMode:session、requeset、
application这三种作用域由于并不是在spring容器加载时创建,因此如果单例对象依赖这三种作用域的对象时,是拿不到对象的。因此需要在@scope注解指定代理模式proxyMode,当单例对象依赖这三种作用域的独享时,会创建依赖的类的代理对象,代理对象会在用户发起请求时再去获取真实的对象。

@Autowired、@Resource

Autowired
按照类型装配bean,如果同个类型有两个bean,需要用@Qualifie执行bean的。由spring提供的注解。

原理:创建对象后,为对象赋值时,通过AutowiredAnnotationBeanPostProcessor寻找到用@Autowired注解修饰的字段,利用反射通过将实例化好的对象赋值到对象上。主要逻辑在AbstractAutowireCapableBeanFactory##populateBean,调用了AutowiredAnnotationBeanPostProcessor##postProcessPropertyValues.最终是该后置处理器的内部类AutowiredFieldElement的inject方法负责注入属性,通过beanFactory进行getBean。

[源码解析参考](https://www.nmyswls.com/article/103/1)
Resource
默认按照名字装配bean,如果名字匹配不到则按照类型匹配。由JSR-250规范的注解。如果指定了名字,则只会按照名字装配。
其中ScopeProxyMode.INTERFACES表示使用jdk代理,ScopeProxyMode.TAGER_CLASS表示使用cglib代理,当该被指定为这三种作用域的类不是接口,则只能用cglib。

原理:通过CommonAnnotationBeanPostProcessor后置处理器处理,与Autowired类似。

BeanFactory FactoryBean

BeanFactory 和 FactoryBean 是不一样的,BeanFactory是用于生成bean的工厂类,ApplicationContext就是BeanFactory的实现类。而FactoryBean是一种特殊的bean,实现了该接口的的类,spring在创建bean时会进行判断是否为FactoryBean类,如果是会通过调用该类的getObject方法获取bean,并将该bean放入spring容器。

通过这一个点,可以实现FactoryBean接口,在getObject方法返回一个自定义的代理对象,或者返回另外一个类的实例。比如mybatis的…

如果想要获取FactoryBean本身的bean,只要通过在bean名字前面加上&,则会返回FactoryBean本身,而不是通过getObject获取bean。比如context.getBean("&beanName"),getBean方法判断包含前缀则会返回bean本身,不包含则通过getObject获取bean。

原理:

  1. 注入bean时,会把实现了FactoryBean的类及上&前缀注入到bean容器中。源码逻辑在DefaultListableBeanFactory::preInstantiateSingletons.
  2. 获取bean时,如果是FactoryBean类型,判断beanName是否包含&,包含则返回bean本身,不包含则通过getObject方法获取。源码逻辑在AbsractBeanFactory::doGetBean方法,getObjectForBeanInstance方法**
总结

创建空对象放入缓存工厂(获取一次后放入早期单例缓存) -> 通过Autowired、Resource的后置处理器扫描类中待注入的对象 -> 通过bean工厂获取对象(绕回第一步) -> 通过反射设置对象的值 -> 放入单例对象缓存。

spring使用的设计模式

简单工厂
BeanFactory。使用ConcurrentHashMap管理spring管理的对象。

  1. 通过简单工厂来生成bean,避免直接new创建,松散耦合
  2. 统一管理每个bean的生命周期,在spring启动的每个阶段为bean做一些处理。

工厂方法
FactoryBean。实现这个接口都要重写getObject()方法,spring的getBean()在获取该bean时,会调用这个bean的getObject方法获取bean.比如spring mybatis的sqlSessionFactoryBean。
通过这个可以自定义bean的生成方式,比如返回代理的bean等。

单例模式
spring默认创建bean的方式都是单里模式,通过双重判断锁生成。
保证bean不会重复创建,并提供全局访问点。

适配器模式
HandlerAdapter.springmvc handler的适配器(HandlerMethod)。通过support方法决定是否匹配这个handler,匹配就可以用这个适配器处理handler。
意义:方便外部扩展,外部只要实现一个适配器就可以对特定的HandlerMethod进行适配做处理。也可以自己实现一个handler和对应的
HandlerAdapter。
每个HandlerMethod都对应了一个controller的一个方法。
比如通过@RequestMapping标注的方法都是由RequestMappingHandlerAdapter进行匹配处理。

装饰器模式
BeanWrapper bean的包装类,spring用这个类的实现类来对bean进行获取和设值。反射调用bean的set get方法设置和获取属性值。
包装类提供了增强功能,提供了统一的操作bean的方法。因为spring支持用xml设置值,spring只知道属性名就可以包装这个bean,对这个bean进行设置。

代理模式
代理类。比如bean添加事务注解时,之所以操作方法会加上事务,就是因为代理对象对bean进行了增强。加上了事务提交等操作。
spring支持cglib和jdk的动态代理。

观察者
ApplicationListener 启动停止进行事件通知

ApplicationListener 监听器
监听spring一些事件,ApplicationContext是事件源,ApplicationEventMulticaster事件广播类
原理是spring在启动过程中,每经历一些阶段就会通过广播类通知所有的监听器,监听器可以在事件进行到某个阶段来做不同的处理。

策略模式
Resource接口是访问资源的接口,定义了访问资源的通用方法。比如UrlResource、InputStreamResource、ByteArrayResource等。
策略模式有利于bean的扩展

模版方法
可以抽出公用代码,子类分别实现。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值