一步一步解读 refresh源码

Posted 流楚丶格念

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一步一步解读 refresh源码相关的知识,希望对你有一定的参考价值。

解读refresh

refresh()

refresh()方法代码如下:

@Override
public void refresh() throws BeansException, IllegalStateException 
    synchronized (this.startupShutdownMonitor) 
        // 准备此上下文以进行刷新
        prepareRefresh();
        // 告诉子类刷新内部 bean 工厂
        ConfigurableListableBeanFactory 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);
            
            // 销毁已经创建的单例以避免悬空资源
            destroyBeans();
            // 重置“活动”标志。
            cancelRefresh(ex);
            // 将异常传播给调用者
            throw ex;
        
        finally 
            // 重置 Spring 核心中的公共自省缓存,因为我们可能不再需要单例 bean 的元数据...re...
            resetCommonCaches();
        
    

首先是对上下文进行刷新:

 // 准备此上下文以进行刷新
prepareRefresh();

1. prepareRefresh

创建和准备了 Environment 对象:

  • Environment 的作用之一是为后续 @Value,值注入时提供键值

流程如下图所示:

prepareRefresh重点是准备了初始环境,我们跟进prepareRefresh源码实现:

可以看到获取环境配置的代码,继续跟进

里面创建了一个标准的环境:

我们在prepareRefresh();这打个断点也能看到里面的environment:

我们将第一步走完,可以看到environment被初始化成了StandardEnvironment

我们执行一个表达式测试一下:

比如说我们测试一下有没有JAVA_HOME的键值信息,我们发现是可以执行查询到的,所以说此时,一些系统的环境都已经加载到Spring中了:

我们将 展开可以看到JAVA环境变量和系统环境变量:

两个环境变量的内容如下:

我们也可以通过下面代码获取这两个环境变量

// 获取系统环境变量
System.getenv().keySet().stream().forEach(s -> System.out.println(s));
// java环境变量
System.getProperties().keySet().stream().forEach(s -> System.out.println(s));

同样,我们也可以使用StandardEnvironment获取这两个环境变量

StandardEnvironment env = new StandardEnvironment();
env.getSystemEnvironment().keySet().stream().forEach(s -> System.out.println(s));
env.getSystemProperties().keySet().stream().forEach(s -> System.out.println(s));

当然也可以加一个环境变量:

// 加一个环境变量
env.getPropertySources().addLast(new ResourcePropertySource("my",new ClassPathResource("1.properties")));

2. obtainFreshBeanFactory

获取(或创建)BeanFactory 来刷新 内部 bean 工厂:

  • BeanFactory 的作用是负责 bean 的创建、依赖注入和初始化
  • BeanDefinition 作为 bean 的设计蓝图,规定了 bean 的特征,如单例多例、依赖关系、初始销毁方法等
  • BeanDefinition 的来源有多种多样,可以是通过 xml 获得、通过配置类获得、通过组件扫描获得,也可以是编程添加

ApplicationContext没有Bean初始化,依赖注入等功能他都没有,他得简介的调用BeanFactory 来对Bean进行管理,ApplicationContext委托给BeanFactory 来干这些活,ApplicationContext做的是扩展的活,Bean的核心功能是在BeanFactory 中。

执行流程如下:

我们继续跟进源码:

/** 
* 告诉子类刷新内部 bean 工厂。
* @return 新的 BeanFactory 实例
* @see #refreshBeanFactory()
* @see #getBeanFactory()
*/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() 
	refreshBeanFactory();
	return getBeanFactory();

里面调用了接口中的两个方法:

/**
 * 子类必须实现此方法才能执行实际的配置加载。
 * 该方法在任何其他初始化工作之前由 @link #refresh() 调用。
 * <p>子类要么创建一个新的bean工厂并持有对它的引用,
 * 或返回它拥有的单个 BeanFactory 实例。在后一种情况下,它将
 * 如果多次刷新上下文,通常会抛出 IllegalStateException。
 * @throws BeansException 如果 bean 工厂的初始化失败
 * @throws IllegalStateException 如果已经初始化并且多次刷新
 * 不支持尝试
 */
protected abstract void refreshBeanFactory() throw BeansExceptionIllegalStateException
/**
 * 子类必须在此处返回其内部 bean 工厂。他们应该实施
 * 高效查找,因此可以重复调用而不会降低性能。
 * <p>注意:子类之前应该检查上下文是否仍然处于活动状态
 * 返回内部 bean 工厂。内部工厂一般应该是
 * 一旦上下文关闭,则认为不可用。
 * @return 此应用程序上下文的内部 bean 工厂(从不 @code null)
 * @throws IllegalStateException 如果上下文还没有包含内部 bean 工厂
 * (通常如果 @link #refresh() 从未被调用过)或者如果上下文已经
 * 已经关闭
 * @see #refreshBeanFactory()
 * @see #closeBeanFactory()
 */
@Override
公共抽象 ConfigurableListableBeanFactory getBeanFactory() throw IllegalStateException

我们在这加个断点看看具体干了什么:

断点出我们执行下一步,看一下refreshBeanFactory的实现:

首先是一个Boolean,这个refreshed变量是用来判断有没有调用过refresh

给BeanFactory实现一个序列化id,这个就对于解读没啥大用了:

下面就是getBeanFactory方法:

这里直接返回了beanFactory

这是个啥?他之前创建过吗?怎么直接返回了?

这个其实在我们创建上下文时候就已经创建了,我们可以在GenericApplicationContext.java中找到答案:

在调用构造的时候就已经初始化beanFactory了

注意这里的ClassPathXmlApplicationContext上下文是调用的另一个refreshBeanFactory的实现

@Override
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);
	

会用新的beanFactory覆盖旧的beanFactory

这里面实现获取BeanFactory 的方法是loadBeanDefinitions(beanFactory) ,下面来测试一下:

我们在xml中配置一个bean:

执行后可以看到加载了我们xml文件中的Bean,bean的所有信息都在value里

BeanDefinition测试(读取Bean方式)

1.xml方式读取

public class BeanDefinitionTest 
    public static void main(String[] args) 
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 1.xml方式读取
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions("classpath:app.xml");
        System.out.println(reader.getBeanFactory());
    


得到从app.xml获取的bean

把这个常用的加到监控变量中:

2.组件扫描

加一个Bean3 添加 @Component

编写下面代码:

// 2.组件扫描
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan("com.yyl.refresh");

执行可以看到Bean3加到了BeanFactory中:

(其他的是一些bean处理器的)

4.编程方式(Bean构建器)

// 4.编程方式添加
beanFactory.registerBeanDefinition("Bean4", BeanDefinitionBuilder.genericBeanDefinition(Bean4.class).getBeanDefinition());

编程方式比较可控,比如我们设置为多例:

// 4.编程方式添加
beanFactory.registerBeanDefinition("Bean4", BeanDefinitionBuilder
        .genericBeanDefinition(Bean4.class)
        .setScope("prototype")
        .getBeanDefinition());
// 再打印一下
System.out.println(beanFactory.getBean("Bean4"));
System.out.println(beanFactory.getBean("Bean4"));
System.out.println(beanFactory.getBean("Bean4"));

结果如下,生成的是三个不同的对象:

再比如我们加一个初始化函数:

public class Bean4 
    public void initMethod()
        System.out.println("Bean init!!!");
    


// 4.编程方式添加
beanFactory.registerBeanDefinition("Bean4", BeanDefinitionBuilder
        .genericBeanDefinition(Bean4.class)
        .setScope("prototype")
        .setInitMethodName("initMethod")
        .getBeanDefinition());
System.out.println(beanFactory.getBean("Bean4"));
System.out.println(beanFactory.getBean("Bean4"));
System.out.println(beanFactory.getBean("Bean4"));

结果如下:

3. prepareBeanFactory

准备在此上下文中使用的 bean 工厂,完善BeanFactory,把一些重要的成员变量初始化:

  • StandardBeanExpressionResolver 来解析 SpEL
  • ResourceEditorRegistrar 会注册类型转换器,并应用 ApplicationContext 提供的 Environment 完成 $ 解析
  • 特殊 bean 指 beanFactory 以及 ApplicationContext,通过 registerResolvableDependency 来注册它们
  • ApplicationContextAwareProcessor 用来解析 Aware 接口
  • ApplicationListenerDetector 用来识别容器中 ApplicationListener 类型的 bean

执行流程如下:

(右侧绿色表示初始化的)

下面我们追踪一下源码:

都是完善BeanFactory参数:

首先是要找到El解析器里的addPropertyEditorRegistrar:注册一组类型转化器

右键我们执行个语句测试一下这个注册器是干嘛滴:

执行找到我们Bean4的语句

beanFactory.getBeanExpressionResolver().evaluate("#@Bean4",new BeanExpressionContext(beanFactory,null))

发现是能查到我们刚注册的结果的:

可以,能解析EL表达式了

下面我们深入探究下里面的ResourceEditorRegistrar资源类型转化器,我们可以看到不同的资源类型用不同转化器转化

添加Bean后处理器:扩展功能

// Configure the bean factory with context callbacks.
beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

注册特殊类型对象:

// BeanFactory interface not registered as resolvable type in a plain factory.
// MessageSource registered (and found for autowiring) as a bean.
beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
beanFactory.registerResolvableDependency(ResourceLoader.class, this);
beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
beanFactory.registerResolvableDependency(ApplicationContext.class, this);

4. postProcessBeanFactory

这一步是空实现,留给子类扩展,做补充

  • 一般 Web 环境的 ApplicationContext 都要利用它注册新的 Scope,完善 Web 下的 BeanFactory
  • 体现的是模板方法设计模式

直接看源码:

首先我们可以看到确实是空实现:

/**
 * 按照标准修改应用上下文的内部bean工厂
 * 初始化。所有 bean 定义都将被加载,但没有 bean
 * 将被实例化。这允许注册特殊的
 * 某些 ApplicationContext 实现中的 BeanPostProcessors 等。
 * @param beanFactory 应用上下文使用的bean工厂
 */
protected postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) 

来吧看看子类对他补充了啥吧,这里我们主要看一个web里对这个方法的实现:

没有的先要加一个依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

5. invokeBeanFactoryPostProcessors(beanFactory);

调用在上下文中注册的 BeanFactory 的后处理器:进一步完善BeanFactory

  • beanFactory 后处理器,充当 beanFactory 的扩展点,可以用来补充或修改 BeanDefinition
  • ConfigurationClassPostProcessor – 解析 @Configuration、@Bean、@Import、@PropertySource 等
  • PropertySourcesPlaceHolderConfigurer – 替换 BeanDefinition 中的 $
  • MapperScannerConfigurer – 补充 Mapper 接口对应的 BeanDefinition

执行流程如下:

我们看一下源码:

首先他调用了配置后处理器的方法,具体实现太多,里面主要配置两个后处理器BeanDefinitionRegistryPostProcessor与BeanFactoryPostProcessor

// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
		beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);	
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let the bean factory post-processors apply to them!
String[] postProcessorNames =
      beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);

下面我们来进行一个后处理器小测试

ConfigurationClassPostProcessor后处理器

新建一个测试类编写如下方法:

package com.yyl.refresh;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ConfigurationClassPostProcessor;
import org.springframework.context.support.GenericApplicationContext;

public class AppPostProcess 
    public static void main(String[] args) 

        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("muConfig",MyConfig.class);
        // context.registerBean("configurationClassPostProcessor", ConfigurationClassPostProcessor.class);
        context.refresh();
        System.out.println();
    

    @Configuration
    static class MyConfig
        public MyConfig()

        @Bean
        public Bean1 bean1()
            return new Bean1();
        
        @Bean
        public Bean2 bean2()
            return new Bean2();
        
    

    static class Bean1

    
    static class Bean2

    

默认没有用到Bean1和Bean2,是不会主动加到BeanFactory中的

我们在第4步与第5步加一个断点,debug下:

我们看到只有一个我们注册的Bean:

当我们加上一个后处理器:ConfigurationClassPostProcessor

context.registerBean("configurationClassPostProcessor", ConfigurationClassPostProcessor.class);

再运行,当执行到第四步时,只有两个我们注册的Bean:

当执行完第五步,我们发现执行了后处理器的方法,把配置中的Bean也加到Bean工厂里来了

下面我们在使用@import测试下

@Configuration
@Import(Bean3.class)
static class MyConfig
    public MyConfig()
    @Bean
    public Bean1 bean1()
        return new Bean1();
    
    @Bean
    public Bean2 bean2()
        return new Bean2();
    


static class Bean3


重新Debug,可以看到Bean3也能通过后处理器加载进来

再加一个资源文件的注解

@Configuration
@Import(Bean3.class)
@PropertySource("classpath:1.properties")
static class MyConfig
    public MyConfig()
    @Bean
    public Bean1 bean1()
        return new Bean1();
    
    @Bean
    public Bean2 bean2()
        return new Bean2();
    

运行,可以看到资源文件也能加载到环境当中

MapperScannerConfigurer后处理器

// mybatis后处理器
context.registerBean("mapperScannerConfigurer", MapperScannerConfigurer.class,bd -> 
    // 找包名
    bd.getPropertyValues().add("basePackage","com.yyl.mapper");
);

设置查找包下接口注入BeanFactory

// mybatis后处理器
context.registerBean("mapperScannerConfigurer", MapperScannerConfigurer.class,bd -> 
    // 找包名
    bd.getPropertyValues().add("basePackage","com.yyl.mapper");
    // 找配置了@mapper注解的
    bd.getPropertyValues().add("annotationClass","org.apache.ibatis.annotations.Mapper");
);

这回就只有roleMapper了

PropertySourcesPlaceholderConfigurer后处理器

解析$的值

context.registerBean("propertySourcesPlaceholderConfigurer", PropertySourcesPlaceholderConfigurer.class);

测试一下,新增一个Bean4:

static class Bean4 
    private String value;
    public String getValue() 
        return value;
    
    public void setValue(String value) 
        this.value = value;
    

将$JAVA_HOME设置到value上

context.registerBean("bean4",Bean4.class,bd -> 
    bd.getPropertyValues().add("value","$JAVA_HOME");
);

如果没加这个处理器是不能显示的

加上PropertySourcesPlaceholderConfigurer后处理器

context.registerBean("propertySourcesPlaceholderConfigurer", PropertySourcesPlaceholderConfigurer.class);

再运行没有问题:

6. registerBeanPostProcessors(beanFactory);

注册 bean创建的 bean 后处理器(在Bean的声明周期对Bean进行扩展)

  • bean 后处理器,充当 bean 的扩展点,可以工作在 bean 的实例化、依赖注入、初始化阶段
  • AutowiredAnnotationBeanPostProcessor 功能有:解析 @Autowired,@Value 注解
  • CommonAnnotationBeanPostProcessor 功能有:解析 @Resource,@PostConstruct,@PreDestroy
  • AnnotationAwareAspectJAutoProxyCreator 功能有:为符合切点的目标 bean 自动创建代理

三个处理器执行时机如下图所示:

执行流程如下:

beanDefinition中有没有Bean实现了PostProcess接口,有就说明不是一般的Bean是一个Bean

以上是关于一步一步解读 refresh源码的主要内容,如果未能解决你的问题,请参考以下文章

一步一步构建Spring5源码

一步一步Spring 源码环境搭建

Mybatis源码解析,一步一步从浅入深:映射代理类的获取

RecyclerView 源码学习:一步一步自定义LayoutManager

RecyclerView 源码学习:一步一步自定义LayoutManager

带你一步一步的解析ARouter 源码