zoukankan      html  css  js  c++  java
  • Spring Boot源码探索——自动配置的内部实现

    前面写了两篇文章 《Spring Boot自动配置的魔法是怎么实现的》《Spring Boot起步依赖:定制starter》,分别分析了Spring Boot的自动配置和起步依赖。在感慨Spring Boot的方便之余,也不禁产生了一点疑惑,Spring Boot 内部究竟是怎么触发自动配置和起步依赖的?

    先看下面这段代码,我们只需要调用SpringApplication.run()方法就能启动整个Spring应用。

    @SpringBootApplication
    public class DemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(DemoApplication.class, args);
        }
    }
    

    非常简单的代码,要了解Spring Boot运行原理,我们先从这个方法开始:

    	public ConfigurableApplicationContext run(String... args) {
    		...
    		...
    		try {
    			...
    			...
    			
    			// 看到refresh很容易想到AbstractApplicationContext的refresh()方法
    			refreshContext(context);
    			
    			...
    		}
    		catch (Throwable ex) {
    			handleRunFailure(context, ex, exceptionReporters, listeners);
    			throw new IllegalStateException(ex);
    		}
            
            ...
    	}
    

    run()方法里面有很多方法逻辑,我们不必每个都去深究,对于了解Spring的同学来说,看到refreshContext(context);很容易想到AbstractApplicationContext的refresh()方法。AbstractApplicationContext的refresh()方法正是Spring 核心功能的实现逻辑。我们来看看refreshContext()方法的代码,会发现里面有一个refresh()方法:

    	private void refreshContext(ConfigurableApplicationContext context) {
    	    // 这个方法更类似了
    		refresh(context);
    		if (this.registerShutdownHook) {
    			try {
    				context.registerShutdownHook();
    			}
    			catch (AccessControlException ex) {
    				// Not allowed in some environments.
    			}
    		}
    	}
    

    继续到refresh()方法中去:

    	protected void refresh(ApplicationContext applicationContext) {
    		Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
    		((AbstractApplicationContext) applicationContext).refresh();
    	}
    

    在这里我们看到应用上下文applicationContext被强转为AbstractApplicationContext,从而去调用AbstractApplicationContext的refresh()方法。AbstractApplicationContext的refresh()方法会做很多事情:

    @Override
    public void refresh() throws BeansException, IllegalStateException {
    	synchronized (this.startupShutdownMonitor) {
    		// 准备刷新的上下文环境
    		prepareRefresh();
    		
    		// 初始化BeanFactory
    		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
    		
    		// 对BeanFactory进行各种功能填充
    		prepareBeanFactory(beanFactory);
    
    		try {
    			// 子类覆盖方法做额外的处理
    			postProcessBeanFactory(beanFactory);
    
    			// 激活各种BeanFactory处理器
    			invokeBeanFactoryPostProcessors(beanFactory);
    
    			// 注册拦截Bean创建的Bean处理器,这里只是注册,真正的调用是在getBean时
    			registerBeanPostProcessors(beanFactory);
    
    			// 国际化处理
    			initMessageSource();
    
    			// 初始化应用消息广播器,并放入“applicationEventMulticaster”Bean中
    			initApplicationEventMulticaster();
    
    			// 留给子类来初始化其它的Bean
    			onRefresh();
    
    			// 在所有注册的Bean中查找Listener Bean,并注册消息广播器中
    			registerListeners();
    
    			// 初始化单例Bean(非延迟初始化的)
    			finishBeanFactoryInitialization(beanFactory);
    
    			// 完成刷新过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent通知其它人
    			finishRefresh();
    		}
            ...
    	}
    }
    

    我们主要关注invokeBeanFactoryPostProcessors(beanFactory)方法,因为这个方法激活各种BeanFactory处理器,包括BeanDefinitionRegistryPostProcessor。BeanDefinitionRegistryPostProcessor可以在Spring 处理Bean的定义之前,注册Bean的定义。现在的项目基本都用Java Config的方式来配置Bean,这些Bean就是通过BeanDefinitionRegistryPostProcessor注册到Spring中的。

    invokeBeanFactoryPostProcessors(beanFactory)方法中的代码挺多的,一开始可能根本找不到我们想要的代码。我们想知道Spring Boot是如何实现自动配置的,自动配置根本上来说,是根据判断条件来加载Bean。现在找到了Spring加载Bean的代码,但是目前我们在这个方法里根本看不到任何与Spring Boot有关的逻辑,只知道Spring Boot确实调用了这个方法。既然Spring Boot调用了这个方法,尽管情况不明,我们也可以试着debug一下。我直接把断点打在了invokeBeanFactoryPostProcessors()方法中:

    public static void invokeBeanFactoryPostProcessors(
    			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
        // Invoke BeanDefinitionRegistryPostProcessors first, if any.
    	Set<String> processedBeans = new HashSet<>();
    
    	if (beanFactory instanceof BeanDefinitionRegistry) {
    	
    	    ... 
    	    ...
    	    
    	    // Do not initialize FactoryBeans here: We need to leave all regular beans
    		// uninitialized to let the bean factory post-processors apply to them!
    		// Separate between BeanDefinitionRegistryPostProcessors that implement
    		// PriorityOrdered, Ordered, and the rest.
    		List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
    
    		// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
    		String[] postProcessorNames =
    				beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
    		for (String ppName : postProcessorNames) {
    			if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
    				currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
    				processedBeans.add(ppName);
    			}
    		}
    	    
    	    ...
    	    
    	}
        
        ...
    }
    

    上面这段代码会从Spring的BeanFactory中寻找BeanDefinitionRegistryPostProcessor类型的Bean,如果找到了就加载它。我们为什么要注意这段代码呢?前面说过,BeanDefinitionRegistryPostProcessor可以向Spring注册Bean定义,那么Spring Boot自动配置的那些Bean也应该是由一个我们现在不清楚的BeanDefinitionRegistryPostProcessor来注册的。如果你正在调试的SpringBoot应用没有配置任何额外东西的话,那么这里的调试信息,应该和下面的图相似:

    我们看到一个 ConfigurationClassPostProcessor 的类,阅读这个类的文档可以知道,正是这个类处理 @Confiugration 注解的类。正好,@SpringBootApplication 间接继承 @Confiugration。显然,ConfigurationClassPostProcessor 将会对Spring Boot的配置进行处理。我们进一步去 ConfigurationClassPostProcessor 中查看:

    /**
     * Derive further bean definitions from the configuration classes in the registry.
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    	int registryId = System.identityHashCode(registry);
    	if (this.registriesPostProcessed.contains(registryId)) {
    		throw new IllegalStateException(
    				"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
    	}
    	if (this.factoriesPostProcessed.contains(registryId)) {
    		throw new IllegalStateException(
    				"postProcessBeanFactory already called on this post-processor against " + registry);
    	}
    	this.registriesPostProcessed.add(registryId);
    
    	processConfigBeanDefinitions()方法(registry);
    }
    

    ConfigurationClassPostProcessor 中的 postProcessBeanDefinitionRegistry() 这个方法,是在上面的 AbstractApplicationContext 类中被调用。再继续看 processConfigBeanDefinitions() 方法:

    /**
     * Build and validate a configuration model based on the registry of
     * {@link Configuration} classes.
     */
    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        ...
        ...
        // Parse each @Configuration class
    	ConfigurationClassParser parser = new ConfigurationClassParser(
    			this.metadataReaderFactory, this.problemReporter, this.environment,
    			this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    
    	Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    	Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    	do {
    		parser.parse(candidates);
    		parser.validate();
            
            ...
            
        }while (!candidates.isEmpty());
        
        ...
    }
    

    又是一个比较复杂的方法,不过好在源码中的注释表明 ConfigurationClassParser 类负责进行 @Configuration 类的解析。我们直接看 ConfigurationClassParser 类的 parse() 方法:

    public void parse(Set<BeanDefinitionHolder> configCandidates) {
    	this.deferredImportSelectors = new LinkedList<>();
    
    	for (BeanDefinitionHolder holder : configCandidates) {
    		BeanDefinition bd = holder.getBeanDefinition();
    		try {
    			if (bd instanceof AnnotatedBeanDefinition) {
    				parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
    			}
    			else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
    				parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
    			}
    			else {
    				parse(bd.getBeanClassName(), holder.getBeanName());
    			}
    		}
    		catch (BeanDefinitionStoreException ex) {
    			throw ex;
    		}
    		catch (Throwable ex) {
    			throw new BeanDefinitionStoreException(
    					"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
    		}
    	}
    
    	processDeferredImportSelectors();
    }
    

    这个方法主要有两个逻辑,parse()方法对BeanDefinition进行解析,递归找出所有的 @Configuration 注解的类。接着processDeferredImportSelectors()处理 DeferredImportSelector 类。这个 DeferredImportSelector 类看源码文档注释:

    A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans have been processed. This type of selector can be particularly useful when the selected imports are {@code @Conditional}.

    意思就是说,这个类能够处理 @Conditional 类型的配置。恰好,我们知道,Spring Boot就是依赖 @Conditional 实现的自动配置。我们应该是找对了地方。

    回过头来看 Spring Boot 的 @EnableAutoConfiguration 注解:

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    	...
    	...
    }
    

    我们看到了 @Import(AutoConfigurationImportSelector.class) 注解,其中它导入的 AutoConfigurationImportSelector 就是 DeferredImportSelector 接口的实现类。这也就意味着,AutoConfigurationImportSelector 是Spring Boot 自动配置的一个关键类。我们继续看看 AutoConfigurationImportSelector 类的实现:

    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
    	if (!isEnabled(annotationMetadata)) {
    		return NO_IMPORTS;
    	}
    	AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
    			.loadMetadata(this.beanClassLoader);
    	AnnotationAttributes attributes = getAttributes(annotationMetadata);
    	List<String> configurations = getCandidateConfigurations(annotationMetadata,
    			attributes);
    	configurations = removeDuplicates(configurations);
    	Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    	checkExcludedClasses(configurations, exclusions);
    	configurations.removeAll(exclusions);
    	// filter()方法是关键
    	configurations = filter(configurations, autoConfigurationMetadata);
    	fireAutoConfigurationImportEvents(configurations, exclusions);
    	return StringUtils.toStringArray(configurations);
    }
    

    很自然地,我们肯定先看从 ImportSelector 继承的方法 selectImports(),在这个方法里面,从字面意思的角度,我们肯定也会先看filter()方法:

    private List<String> filter(List<String> configurations,
    		AutoConfigurationMetadata autoConfigurationMetadata) {
    	long startTime = System.nanoTime();
    	String[] candidates = StringUtils.toStringArray(configurations);
    	boolean[] skip = new boolean[candidates.length];
    	boolean skipped = false;
    	for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) {
    		invokeAwareMethods(filter);
    		boolean[] match = filter.match(candidates, autoConfigurationMetadata);
    		for (int i = 0; i < match.length; i++) {
    			if (!match[i]) {
    				skip[i] = true;
    				skipped = true;
    			}
    		}
    	}
        ...
        ...
    }
    

    这里我们看到一个 AutoConfigurationImportFilter 类,程序获取这个类,并调用它的 match()方法,这让我们联想到 Condition 类的 macthes() 方法。我们查看这个类的继承体系,会发现它是 OnClassCondition 类的父类。而 OnClassCondition 负责处理 @ConditionalOnClass@ConditionalOnMissingClass 两个注解。

    到这里,Spring Boot 自动配置的流程大致是清楚了。用一张程序调用栈的图片或许能够表达清楚:

    这里我们只发现了 OnClassCondition ,实际上Spring Boot中还有很多其他的 Conditon,比如:OnBeanConditionOnPropertyConditionOnResourceCondition等等。他们基本上也是类似的调用流程。

  • 相关阅读:
    学习php 韩顺平 数据类型 三元运算,字符串运算类型运算
    学习php 韩顺平
    贪小便宜吃大亏关于汇泽平板和智能手表
    学习spring的第三天
    学习spring的第二天
    学习spring的第一天
    mybatis批量添加和删除
    关于mybatis的<selectKey>中的keyColumn
    mybatis+maven+父子多模块进行crud以及动态条件查询
    mybatis的插入数据后的主键获取
  • 原文地址:https://www.cnblogs.com/bluemilk/p/10612879.html
Copyright © 2011-2022 走看看