Spring容器启动的过程(超详细,建议收藏)

大体流程如下
在这里插入图片描述

1、初始化

首先,Spring会通过用户提供的配置信息(例如XML文件或者注解)来初始化一个BeanFactory,这个BeanFactory是Spring容器的核心,它负责创建和管理所有的Bean。

ApplicationContextInitializer

ApplicationContextInitializer 接口允许开发人员在 ApplicationContext 刷新之前,修改或配置 ApplicationContext 的属性。常用于解析配置文件,这样在容器刷新的时候可以读取到配置文件的内容

2、读取配置生成并注册BeanDefinition(非配置类方式)

Spring读取散落于程序代码各处的注解、及保存在磁盘上的xml或者其他文件的配置元信息,在内存中总要以一种对象的形式表示,而Spring选择在内存中表示这些配置元信息的方式就是BeanDefination,也就是配置元信息被加载到内存之后是以BeanDefination的形存在的。然后BeanDefination被注册到BeanDefinitionRegistry中,这个过程通常是通过BeanDefinitionReader实现的。BeanDefinition描述了一个bean实例,该实例它具有属性值, 构造函数参数值,以及提供的进一步信息的具体的实现,Spring是根据beanDefinition来创建Spring bean的,结构如下所示。
在这里插入图片描述
上图就是beanDefinition所包含的主要内容

  • lazy:是否是懒加载
  • scope注解来标识这个bean其作用域,常用的是singleton和prototype
  • beanClass,类的class对象
  • isPrimary,是否被优先加载的
  • factoryBeanName:工厂名
  • factoryMethodName:工厂方法
  • initMethodName,初始化方法
  • destoryMethodName,销毁化方法
  • isInstanceFactory:是否工厂方法,是的话标记为1

BeanDefinationReader

Spring是如何看懂这些配置元信息的呢?这个就要靠我们的BeanDefinationReader了。不同的BeanDefinationReader拥有不同的功能

  • 读取xml配置元信息,那么可以使用XmlBeanDefinationReader。
  • 要读取properties配置文件,那么可以使用PropertiesBeanDefinitionReader。
  • 读取注解配置元信息,那么可以使用 AnnotatedBeanDefinitionReader加载。

我们也可以很方便的自定义BeanDefinationReader来自己控制配置元信息的加载。总的来说,BeanDefinationReader的作用就是加载配置元信息,并将其转化为内存形式的BeanDefination,存在某一个地方,这个地方就是BeanDefinationRegistry

BeanDefinationRegistry

执行到这里,Spring已经将存在于各处的配置元信息加载到内存,并转化为BeanDefination的形式,这样我们需要创建某一个对象实例的时候,找到相应的BeanDefination然后创建对象即可。那么我们需要某一个对象的时候,去哪里找到对应的BeanDefination呢?这种通过Bean定义的id找到对象的BeanDefination的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinationRegistry了。

BeanDefinationRegistry是一个接口规范,本质就是提供注册及保存BeanDefination的规范,以他的子类实现DefaultListableBeanFactory为例

Spring通过BeanDefinationReader将配置元信息(xml类型为例)加载到内存生成相应的BeanDefination之后,本质是将其注册到DefaultListableBeanFactory中的一个ConcurrentHashMap类型的属性beanDefinitionMap中,如下

private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);

配置元信息:

Spring IOC容器将对象实例的创建与对象实例的使用分离,当业务中需要依赖某个对象,不再依靠我们自己手动创建,只需向Spring要,Spring就会以注入的方式交给我们需要的依赖对象。既然将对象创建的任务交给了Spring,那么Spring就需要知道创建一个对象所需要的一些必要的信息。而这些必要的信息的主要形式有

  • xml文件配置
 <!-- 
    property元素是定义类的属性,name属性定义的是属性名称 value是值,相当于:
    Hello hello=new Role();
    role.setName("Spring");
    role.setA(a);
 -->
  <bean id="hello" class="com.exemple.demo.Hello" scope="prototype">
       <property name="name" value="Spring"/>
       <property name="a" ref="a"/>
  </bean>
  
   <bean name="a" class="com.exemple.demo.test.A">
<!--        <property name="b" ref="b"></property>-->
    </bean>
  • @bean注解配置

ClassPathBeanDefinitionScanner

扫描类路径下的候选组件,并注册到容器中,注册的过程本质就是调用BeanDefinitionParser,分为类路径下解析器类和配置类解析器类,分别注册类路径和配置文件下的的组件注册

3、实例化 BeanFactory

根据 BeanDefinitionRegistry 中的 BeanDefinition 信息,Spring 实例化一个 BeanFactory,通常是 DefaultListableBeanFactory。BeanFactory 负责管理 Bean 的创建和依赖注入。

4、容器初始化后处理BeanFactoryPostProcessor 处理

BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,在Spring已经把相关的bean信息注册到注册到beanDefinationRegistry后,再次对beanDefination进行一定程度上的修改与替换,或者自定义bean

BeanDefinitionRegistryPostProcessor

是作为BeanFactoryPostProcessor的子节口,所有的BeanDefinition加载完成之后,Bean真正被实例化之前,可以通过实现BeanDefinitionRegistryPostProcessor接口,对BeanDefinition再做一些定制化的操作,比如修改某个bean的BeanDefinition的属性、手动注册一些复杂的Bean。有如下几种用法

配置类解析ConfigurationClassPostProcessor

用用解析注解@Configuration,这个注解的作用是标识当前类作为全配置类,如果是@Componet的话,叫做半配置类,配置类下面会有很多加了@Bean注解的工厂方法,多次调用全配置类下的@Bean标注的工厂方法,不会重复产生新的对象,始终是同一个

ConfigurationClassPostProcessor的工作流程

  • 解析是否加了@ComponentScan, 并进行扫描,如果扫描除了全配置类,那么需要继续递归解析全配置类,每个被扫描的类会生成该类beanDefinition,并最终会存储到beanDefinitionMap中
  • 创建全配置类的增强处理器,生成全配置类的代理类,根据方法信息,选择拦截器,拦截器BeanMethodInterceptor对应@Bean注解的处理
  • 解析带有@Bean的工厂方法,生成beanDeifition注册到beanDeifitionRegister,这些beanDeifition中会被指定facetoryBean为配置类的代理类,也就是意味着在后续实例化阶段,会调用会facetoryBean的工厂方法,也就是这个@Bean所对应的方法,而后被拦截器BeanMethodInterceptor拦截,代为执行,这里会用到缓存机制,也就是首次实例化的存入一个缓存,后续直接从缓存中拿,保证幂等性
    在这里插入图片描述
    @Configuration 与FactoryBean搭配
@Configuration
public class AppConfig {

    @Bean
    public MyServiceFactoryBean myServiceFactoryBean() {
        return new MyServiceFactoryBean();
    }

    public static class MyServiceFactoryBean implements FactoryBean<MyService> {
        
        @Override
        public MyService getObject() throws Exception {
            return new MyService(); // 创建和返回 MyService 实例
        }

        @Override
        public Class<?> getObjectType() {
            return MyService.class; // 返回 MyService 类型
        }

        @Override
        public boolean isSingleton() {
            return true; // 如果是单例模式,返回 true
        }
    }
}

在上面的例子中,我们定义了一个 MyServiceFactoryBean 类,它实现了 FactoryBean。在 getObject() 方法中创建 MyService 的实例,并通过 getObjectType() 声明其类型。最后,通过 isSingleton() 方法指明该 bean 的作用域。

当你从 Spring 容器中获取 MyService 的实例时,你实际上会获得 MyServiceFactoryBean 中所创建的 MyService 实例。这样,MyService 的创建逻辑就被封装在了 FactoryBean 中,你可以在创建过程中添加任何额外的逻辑。

解析@Import

通过@Import来引入一些配置类、ImportSelector实现类组件、ImportBeanDefinitionRegistrar实现类组件

框架中常见的开启某某功能的注解,比如

  • @EnableFeignClients:引入一个类FeignClientsRegistrar,通过它的registerFeignClients()扫描被 @FeignClient标识的接口生成代理类,并把接口和代理类交给Spring的容器管理
  • @EnableSwagger

本质就是通过@Import引入一系列配置类,这些配置类用于支持它们所提供的某某功能

mapper扫描注册beanDefition MapperScannerConfigurer

核心就是给@MapperScan扫描路径下的mapper接口的beanDefition的beanClass属性设置为MapperFactoryBean.class,并给FactoryBean设置

常用bean配置

HandlerMapping
Spring MVC 的一个核心接口,它的主要责任是根据 HTTP 请求来查找相应的处理器(Handler),一个处理器通常对应于一个 Controller 中的一个方法。当一个 HTTP 请求到达时,HandlerMapping 需要确定这个请求应该由哪个处理器来处理。基于注解的 RequestMappingHandlerMapping,基于 XML 配置的 BeanNameUrlHandlerMapping 等。这些实现类在应用启动时会扫描并收集所有的处理器,并建立起 URL 到处理器的映射关系(后面到了初始化流程中也会涉及到)

开发者功能扩招

自定义并注册bean

##  AspectJAutoProxyBeanDefinitionParser
注册AnnotationAwareAspectJAutoProxyCreator,这是一个beanPostProcessor

修改beanDefition的beanClass属性为单例模式



   
## 解析配置文件生成benaDefition
 解析配置文件,讲替换$占位符为配置文件中的真实的数据,我们的配置元信息中有些可能会修改的配置信息散落到各处,不够灵活,修改相应配置的时候比较麻烦,这时我们可以使用占位符的方式来配置。例如配置Jdbc的DataSource连接的时候可以这样配置
```java
<context:property-placeholder location="classpath:druid.properties" />

<!-- 配置druid数据源 -->
<bean name="pooledDataSource" class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init" destroy-method="close" >
    <!-- 数据库连接基础信息 -->
    <property name="url" value="${druid.url}" />
    <property name="driverClassName" value="${druid.url}"/>
    <property name="username" value="${druid.username}" />
    <property name="password" value="${druid.password}" />
</bean>
```
BeanFactoryPostProcessor就会对注册到BeanDefinationRegistry中的BeanDefination做最后的修改,替换$占位符为配置文件中的真实的数据。

这里实际工作的是PropertySourcesPlaceholderConfigurer,我们配置的property-placeholder也会以BeanDefination的形式被注册BeanDefinationRegistry中,对应组件PropertySourcesPlaceholderConfigurer,然后再执行容器初试化后置处理的过程中,首先遍历BeanDefinationRegistry,从中取出BeanDefinitionRegistryPostProcessor类型的BeanDefination,即PropertySourcesPlaceholderConfigurer,然后通过这个组件来实际完成对druidDataSource组件对应的BeanDefination进线修改,通过执行PropertySourcesPlaceholderConfigurer的processProperties方法,这个核心方法的作用是访问每个BeanDefinationRegistry中的BeanDefination,然后用给的的属性值替换属性值占位符${...}

至此,整个容器启动阶段就算完成了,容器的启动阶段的最终产物就是注册到BeanDefinationRegistry中的一个个BeanDefination了,这就是Spring为Bean实例化所做的预热的工作

# 5.1Register bean processors

5.1 注册所有的BeanPostProcessors

在实例化bean之前,Spring会先注册所有的BeanPostProcessors,注册方法

  • addBeanPostProcessor,单个注册,最早在容器预加载时就会有注册,容器启动后初始化流程中也会有陆续注册
  • addBeanPostProcessors 容器启动后初始化流程后,通过beanFactory.getBeanNamesForType查找容器中所有的beanPostProcessor,注册

相关问题

解决:is not eligible for getting processed by all BeanPostProcessors

个人情况案例

@Service
public class WorkbenchRmiService implements BeanPostProcessor {
}

容器启动后初始化流程后,通过beanFactory.getBeanNamesForType查找容器中所有的beanPostProcessor时会查找到这个WorkbenchRmiService组件,此时会对WorkbenchRmiService做实例化,过程中会实例advisor,此时判断实例化中可用的beanPostProcessor小于当前Spring中所有的beanPostProcessor

5.2、实例化单例Bean

Spring会实例化所有的单例Bean(如果配置为懒加载,则在第一次获取Bean时才会实例化)。这个过程包括实例化Bean、设置Bean的属性、调用Bean的初始化方法等步骤

创建阶段

对象的创建权交由Spring管理了,不再是我们手动new了,这也是IOC的概念,Spring绝大多数情况会通过推断出构造器,进而做反射来创建对象的

实例化bean

主要方式是通过工厂方法,Supplier、反射,FactoryBean,Spring创建bean的方式大概有12种,但本质都是通过这些方式,主要就是用反射

推断构造器

使用反射创建bean,需要首先推断构造器,策略如下

  • 没有重写过构造器,默认使用无参构造器
  • 只重写了一个构造器,那么默认使用这个构造器
  • 重写了多个构造器,优先看是否有被@AutoWired注解标识的构造器,有的话优先使用这个,其次看是否存在重写后的无参构造器,有的话使用这个,否则会报错NoSuchMethodException

FactoryBean的实例化

在Spring中,当容器扫描到一个Bean定义并发现它实现了FactoryBean接口时,Spring会在内部进行一些特殊的处理。这通常是通过Bean的定义元数据来标记的。Spring容器在处理FactoryBean时,会使用以下方式进行标记和管理:在Bean定义中,Spring会将工厂Bean的isInstanceFactory属性标记为true,这表示这是一个工厂Bean,到了Spring容器启动实例化的阶段,对于这种FactoryBean类型的Bean不需要进行构造器推断 + 反射,应该通过其getObject()方法来获取实际的对象。

依赖注入

解析依赖注入相关的方法与属性

AutowiredAnnotationBeanPostProcessor,首先查找所有被@AutoWired注解标识的方法和属性(包括对象属性和集合属性),封装为InjectionMetadata集合,如果是集合属性,会根据PriorityOrdered,Ordered,@Order确定注入集合属性的优先级,CommonAnnotationBeanPostProcessor用于处理@Resource

查找依赖注入的bean

首先看是否支持集合注入

  • 集合属性,优先按PrioriyOrder排序,其次Ordered,最后@Order
  • 非集合属性,首先看是否有@Qualifier的话,会根据@Qualifier的value值作为beanName去查找,差找不到会报错,然后该注解会通过beanType做查找,看找到bean的个数
    • 如果找到一个,返回这个bean
    • 没有找到,且Autowired注解的required为false,返回null,required为true,报错
    • 找到多个,优先选择被@Primary注解标识的,没有@Primary的话看注解的@Priority,选择优先级高的,@Priority也没有的话,将当前的属性的名作为beanName去容器中查找,依然没有找到的话就报错
      在这里插入图片描述

填充属性

populateBean方法,通过InstantiationAwareBeanPostProcessor做属性填充

初始化流程

回调aware接口

注意:

回调InitializingBean初始化方法

BeanPostProcessor前置处理 - postProcessBeforeInitialization

对 Bean 的属性进行自定义注入,AOP 预处理等操作

AOP 预处理
AnnotationAwareAspectJAutoProxyCreator,首先根据@Aspect获取切面类、切面类中的所有通知方法,存储在Advisors列表中,获取切点,判断当前bean是否满足切点,满足切点的bean会被生成代理类对象,并设置切入点方法对象,创建代理对象主要是是jdk动态代理和cglib动态代理,Spring中默认使用cglib生成动态代理类对象

事务
InfrastructureAdvisorAutoProxyCreator
匹配逻辑为判断该Bean的类上是否存在@Transactional注解,或者类中的某个方法上是否存在@Transactional注解,如果存在则表示该Bean需要进行动态代理产生一个代理对象作为Bean对象

BeanPostProcessor后置处理 - postProcessAfterInitialization

AnnotationAwareAspectJAutoProxyCreator验证bean属性的完整性,定制初始化功能,资源回收,生成AOP代理对象

生成AOP代理对象

7、调用bean阶段

aop、事务,获取到的是代理类的bean对象
普通单实例对象是从一级缓存中获取到对象,如果获取不到,那就开始实例化、依赖注入,初始化、存入缓存、被调用…
原型模式,每次被调用都创建一个新的bean对象

销毁

Spring启动过程可以分为以下几个步骤[^1]: 1. 创建Spring容器:通过创建一个ApplicationContext对象来启动Spring容器。可以使用不同的ApplicationContext实现类,如AnnotationConfigApplicationContext、ClassPathXmlApplicationContext等。 2. 解析配置类:如果使用AnnotationConfigApplicationContext启动容器,会解析配置类,将配置类中的Bean定义注册到容器中。 3. 准备启动:在容器准备启动之前,会执行一些准备工作,如初始化消息源、初始化事件广播器等。 4. 注册Bean后置处理器:在容器启动之前,会注册一些Bean后置处理器,用于在Bean实例化和初始化过程中进行一些额外的处理。 5. 初始化BeanFactory:在容器启动之前,会对BeanFactory进行一些初始化操作,如设置类加载器、设置Bean的后置处理器等。 6. 完成容器初始化:在容器启动之前,会完成BeanFactory的初始化工作,包括实例化和初始化所有的单例Bean。 7. 容器启动完成通知:在容器启动完成后,可以通过创建一个实现SmartLifecycle接口的类来监听容器启动完成的通知。 以下是一个示例代码,演示了Spring启动过程中的一些关键步骤: ```java import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; @Component public class Beanfinish implements SmartLifecycle { @Override public void start() { System.out.println("容器启动完成通知..."); } @Override public void stop() { } @Override public boolean isRunning() { return false; } @Override public boolean isAutoStartup() { return true; } } public class Main { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); context.start(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值