Spring核心-Bean周期
Spring核心-Bean周期
- Spring
- IOC
- 注入方式
- AOP
- Bean的生命周期
- ⏱️ 流程图解 复制实例化 → 属性注入 → Aware回调 → [前置处理] → 初始化 → [后置处理] → (使用中) → 销毁
SSM框架 – >> SSM(Spring+SpringMVC+MyBatis)框架集由Spring、MyBatis两个开源框架整合而成(SpringMVC是Spring中的部分内容)。常作为数据源较简单的web项目的框架。
Spring
Spring是一个粘合剂,可以整合其他框架进行工作
Spring的主要技术是IOC和AOP
IOC
:
依赖注入,控制反转,简单来说就是将对象的创建权力和生命周期管理过程交由Spring来处理,在开发过程中不在需要关注对象的创建和生命周期管理,
而是在需要时从Spring容器中获取,这个由Spring来创建对象和管理对象生命周期的机制称为控制反转
IOC不是技术,而是一种设计思想,IOC意味着将你设计好的对象给容器控制,而不是传统的在你的对象内部直接控制,
IOC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么对象都是主动创建,
但是在IOC/DI思想中,应用程序就变成被动的了,被动的等待IOC容器来创建并注入它需要的对象
IOC很好的体现了面向对象的设计法则:由IOC容器帮对象找相应的依赖对象并注入,而不是由对象主动去找
注入方式
:接口注入,set方法注入,构造器注入,注解注入。
接口注入: 接口注入模式因为具备侵入性,它要求组件必须与特定的接口相关联,因此并不被看好,实际使用有限。
set方式注入:对于习惯了传统 javabean 开发的程序员,通过 setter 方法设定依赖关系更加直观。如果依赖关系较为复杂,那么构造子注入模式的构造函数也会相当庞大,而此时设值注入模式则更为简洁。如果用到了第三方类库,可能要求我们的组件提供一个默认的构造函数,此时构造子注入模式也不适用。
构造器注入:在构造期间完成一个完整的、合法的对象。所有依赖关系在构造函数中集中呈现。依赖关系在构造时由容器一次性设定,组件被创建之后一直处于相对“不变”的稳定状态。只有组件的创建者关心其内部依赖关系,对调用者而言,该依赖关系处于“黑盒”之中。
注解注入:@Resource先会按照名称到spring容器中查找,如果查找不到,就回退按照类型匹配,如果再没有匹配到,就会抛出异常。如果在开发的时候,建议大家都是用@Resource(name=”userDao”),此时只能够按照名称匹配。
@Autowired注解(bytype)和@Resource(byname)存在小差别。
IOC总结:要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC/DI容器来创建并注入她所需要的资源了。
这一举动,有效的分离了对象和她所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
AOP
:
AOP面向切面编程,OOP面向对象编程,AOP是OOP的延伸
OOP引入封装、继承和多态性(重载/重写)等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。
当我们需要为分散的对象引入公共行为的时候,OOP显得无能为力,oop允许你定义从上到下的关系,但并不适合定义从左到右的关系。
例如日志、异常、安全性、性能统计、事务处理等功能。日志代码往往水平地散布在所有对象层次中,而与对象的核心功能毫无关系。
这种散布在各处的无关的代码被称为横切代码,在oop设计中,它导致了大量代码的重复,模块间的耦合度高,而不利于各个模块的复用
简单地说,就是将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少重复代码,降低模块间的耦合度,有利于未来的扩展性和可维护性。
切面:一个关注点的模块化,这个关注点可能会横切多个对象。基于@Aspect注解的方式来实现。
@Aspect:把当前类标记为一个切面类
@Pointcut:切入点,例如定义切入点表达式 execution (* com.yck.service.impl…*. (…))
execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个号:表示返回类型, 号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
4、第二个号:表示类名,号表示所有的类。
5、(…):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
@Before:标识一个前置增强方法,相当于BeforeAdvice的功能,相似功能的还有
@After: final增强,不管是抛出异常或者正常退出都会执行
@AfterThrowing:异常抛出增强,相当于ThrowsAdvice
@AfterReturning:后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@Around:环绕增强,相当于MethodInterceptor
Bean的生命周期
:
在Spring框架中,Bean的生命周期可简洁概括为以下8个核心步骤:
实例化(Instantiation)→ 通过构造方法或工厂方法创建Bean实例。
属性注入(Populate Properties)→ 通过Setter、字段注入等方式填充依赖(如@Autowired)。
Aware接口回调(可选)→ 若实现BeanNameAware,注入Bean的ID。→ 若实现BeanFactoryAware,注入BeanFactory实例。→ 若实现ApplicationContextAware,注入ApplicationContext。
初始化前处理(BeanPostProcessor前置处理)→ 调用所有BeanPostProcessor的postProcessBeforeInitialization()方法(如@PostConstruct逻辑)。
初始化(Initialization)→ 调用InitializingBean.afterPropertiesSet()(若实现)。→ 调用自定义init-method(或@Bean(initMethod))。
初始化后处理(BeanPostProcessor后置处理)→ 调用所有BeanPostProcessor的postProcessAfterInitialization()方法(如AOP代理生成)。
就绪可用(Ready)→ Bean实例完全初始化,可被应用程序使用。
销毁(Destruction)→ 容器关闭时调用DisposableBean.destroy()(若实现)。→ 调用自定义destroy-method(或@PreDestroy注解)。
⏱️ 流程图解
复制实例化 → 属性注入 → Aware回调 → [前置处理] → 初始化 → [后置处理] → (使用中) → 销毁
Bean的创建过程源码疑点?
Bean的创建过程会有11次后置处理器?第7 8 9次后置处理器会决定循环依赖问题(创建过程中有个方法,方法里面有个判断,判断中有三个条件,其中有两个条件一定是成立的,也就是true,最后的条件默认设置为true,但是这个条件是初始值赋值的,也就是说我们可以通过修改源码,或者在bean完成创建完成之前进行修改值,让其不成立),创建bean使用工厂模式,三次判断中去决定bean的创建,使用工厂模式,为什么?(使用工厂模式为了,性能的开销,减少不必要的创建),为什么需要三次判断?(三次判断的作用,在于创建完bean之后会放入相应的容器中,内部最后一次判断如果为空,就会进行创建bean的操作,如果不放入容器中就会多次创建,放入容器中,只需要判断容器是否存在,若存在,取出即可,不存在进行创建(创建对象需要进行开销),为减少这种开销,所以采用工厂的形式进行创建–spring亮点设计模式之一);
所有Bean在创建完成之后,会被放进Spring缓存池容器中管理,Spring中里面有个List 也有个Map,在获取bean之前,会先去从Map中get值,以bean的name作为key,如果存在,则从List中进行获取,不存在,则走Bean的创建过程,为什么不直接从List去获取,而要根据Map来?获取bean的时候,会去List进行循环遍历,加入Map为了更好的性能,先去判断是否存在,再去获取bean,不需要浪费无用的循环来增加程序的时间度。
Spring框架通过三级缓存机制解决Bean的循环依赖问题,具体实现方式如下:
一级缓存(singletonObjects):存放完全初始化好的Bean
二级缓存(earlySingletonObjects):存放提前暴露的原始Bean对象(尚未填充属性)
三级缓存(singletonFactories):存放Bean工厂对象
解决流程:
当创建Bean A时,首先会通过构造器实例化A(此时A还未初始化),并将A的工厂对象放入三级缓存
当A需要注入依赖B时,开始创建B
创建B的过程中发现需要注入A,此时会从三级缓存中获取A的工厂对象
通过工厂对象获取A的早期引用(此时A还未完全初始化)
将获取到的A早期引用注入B,完成B的创建
最后完成A的初始化,并将A从二级/三级缓存升级到一级缓存
注意:
这种机制只能解决通过setter/field注入的单例Bean的循环依赖
构造器注入的循环依赖无法解决,会直接抛出BeanCurrentlyInCreationException
原型(prototype)作用域的Bean也不支持循环依赖解决方案
大致步骤解析
–>ClassPathXmlApplicationContext
读取spring配置使用ApplicationContext来进行读取创建
类关系图:
在这里插入图片描述
这里我们从resource下读取xml配置,因此使用ClassPathXmlApplicationContext来读取配置文件spring.xml
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(
String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
throws BeansException {
super(parent);
setConfigLocations(configLocations);
if (refresh) {
refresh();
}
}
调用构造函数ClassPathXmlApplicationContext(String configLocation) 时,调用重载的构造方法ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) ,并且设置参数:配置文件地址用字符串数组封装;设置refresh为true;parent为null。
于是在真正调用ClassPathXmlApplicationContext()构造方法时会执行setConfigLocations()和refresh()两个方法。
setConfigLocations():
该方法调用其父类AbstractRefreshableConfigApplicationContext的setConfigLocations( String… locations),用于设置参数configLocations[]数组
refresh():
该方法是读取配置并从配置装载bean的主要方法,主要用于解析配置和装载实例
–> refresh()
在ClassPathXmlApplicationContext 中调用的refresh(),实际是调用其父类AbstractApplicationContext.refresh(),其代码如下
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 准备此上下文刷新,主要用于数据初始化
prepareRefresh();
// 告诉子类刷新内部bean工厂,该方法包括创建beanFactory以及解析配置文件装载beanConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();// 准备bean工厂以供在此上下文中使用prepareBeanFactory(beanFactory);try {// 允许子类对该上下文中的bean工厂进行后处理,方法为空postProcessBeanFactory(beanFactory);// 执行在上下文中注册的bean处理器invokeBeanFactoryPostProcessors(beanFactory);// 注册由拦截bean创建的bean处理器registerBeanPostProcessors(beanFactory);// 初始化此上下文的消息源initMessageSource();// 为此上下文初始化事件多主机initApplicationEventMulticaster();// 初始化子类中生命的其他特殊bean,该方法为空onRefresh();// 检查监听器bean并注册它们registerListeners();// 实例化所有剩余的(非延迟初始化)单例finishBeanFactoryInitialization(beanFactory);// 发布对应的事件finishRefresh();}catch (BeansException ex) {if (logger.isWarnEnabled()) {logger.warn("Exception encountered during context initialization - " +"cancelling refresh attempt: " + ex);}// Destroy already created singletons to avoid dangling resources.destroyBeans();// Reset 'active' flag.cancelRefresh(ex);// Propagate exception to caller.throw ex;}finally {// Reset common introspection caches in Spring's core, since we// might not ever need metadata for singleton beans anymore...resetCommonCaches();}
}
}
–> Bean加载
obtainFreshBeanFactory()
在refresh()中首先通过obtainFreshBeanFactory 创建Bean工厂
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
return getBeanFactory();
}
在obtainFreshBeanFactory 中会先通过refreshBeanFactory 创建bean工厂,再通过getBeanFactory 返回创建的Bean工厂,所以重点关注refreshBeanFactory方法。debug代码最终调用其子类AbstractRefreshableApplicationContext的refreshBeanFactory方法中
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
refreshBeanFactory() 做了一下几件事:
1.判断是否已经创建了Bean工厂,如果已经创建了,删除并销毁该Bean工厂,重新创建
2.创建Bean工厂,实际创建类型为DefaultListableBeanFactory
3.序列化新的Bean工厂,并设置自定义属性(是否允许Bean覆盖和是否允许循环依赖)
4.加载Bean到该Bean工厂,在xml文件中定义的Bean加载都是在该方法中进行的
–> loadBeanDefinitions()
loadBeanDefinitions由AbstractRefreshableApplicationContext子类进行具体实现,
实现该方法的子类有:
AbstractXmlApplicationContext、
AnnotationConfigReactiveWebApplicationContext、
AnnotationConfigWebApplicationContext、
GroovyWebApplicationContext、XmlWebApplicationContext
类关系图:
在这里插入图片描述
从ApplicationContext继承关系图可以看到,由于使用ClassPathXmlApplicationContext创建,并且ClassPathXmlApplicationContext类中并没有重写loadBeanDefinitions方法,因此调用其父类AbstractXmlApplicationContext.loadBeanDefinitions()。
在AbstractXmlApplicationContext.loadBeanDefinitions()中,会创建一个xml bean读取器XmlBeanDefinitionReader然后设置初始化后,调用类中loadBeanDefinitions(beanDefinitionReader)进行读取。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
String[] configLocations = getConfigLocations();
if (configLocations != null) {
reader.loadBeanDefinitions(configLocations);
}
}
从reader.loadBeanDefinitions(configLocations);这句可以看出,真正的解析bean是通过Bean读取器从对应的配置文件地址中进行读取的。
在这里插入图片描述
进入loadBeanDefinitions后可以发现,将设置的字符串类型的配置文件地址location封装成Resource对象再通过XmlBeanDefinitionReader.loadBeanDefinitions(Resource resource)调用,最后调用XmlBeanDefinitionReader.loadBeanDefinitions(EncodedResource encodedResource)开始真正的加载。
未完…