SpringBoot的自动装配源码分析

news2024/11/25 4:32:05

文章目录

  • 一:什么是自动装配
  • 二、springboot的启动流程
    • 1.调用SpringApplication()的构造方法
    • 2.执行核心run方法()
    • 3.执行核心prepareContext()
    • 4.执行核心refreshContext()
    • 5.ConfigurationClassPostProcess
  • 三:流程概述
  • 四:总结


Spring Boot的核心理念是简化Spring应用的搭建和开发过程,提出了约定大于配置和自动装配的思想。开发Spring项目通常要配置xml文件,当项目变得复杂的时候,xml的配置文件也将变得极其复杂。为了解决这个问题,我们将一些常用的通用的配置先配置好,要用的时候直接装上去,不用的时候卸下来,这些就是Spring Boot框架在Spring框架的基础上要解决的问题。

一:什么是自动装配

在传统的Spring框架中,我们需要手动配置和管理Bean的依赖关系,但在Spring Boot中,大量的配置可以自动完成。这是因为Spring Boot中引入了自动装配的概念。自动装配指的是根据应用程序的依赖关系自动配置Spring Bean,而无需手动配置。

手动装配

比如在下面代码中,我创建了一个cat和一个dog。然后通过手动方式把他注入到people类的属性cat和dog中。

<?xml version="1.0" encoding="UTF-8"?>    <beans xmlns="http://www.springframework.org/schema/beans"           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"           xsi:schemaLocation="http://www.springframework.org/schema/beans        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="cat" class="com.kuang.pojo.Cat"/>
    <bean id="dog" class="com.kuang.pojo.Dog"/>
    <bean id="people" class="com.kuang.pojo.Peopel">
        <property name="name" value="张三"/>
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    </bean></beans>

自动装配

spring中实现自动装配的方式有两种,一种是通过xml文件、一种是通过注解的方式。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean id="cat" class="com.kuang.pojo.Cat"/>
    <bean id="dog" class="com.kuang.pojo.Dog"/>
    <bean id="people" class="com.kuang.pojo.Peopel">
        <property name="name" value="张三"/>
    </bean>
</beans>

通过@Autowired注解就可以直接完成我们cat和dog的属性注入

public class Peopel {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
    private String name;
     
    setter/getter... 
}

提示:以下是本篇文章正文内容,下面案例可供参考

二、springboot的启动流程

在上面回忆了下什么是自动装配后,我们先来探索下springboot 的启动流程,看看springboot是在哪一步完成自动装配的。

这是一个启动springboot程序的启动类

@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
@EnableTransactionManagement
public class RuoYiApplication
{
    public static void main(String[] args)
    {
        // System.setProperty("spring.devtools.restart.enabled", "false");
        SpringApplication.run(RuoYiApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  若依启动成功   ლ(´ڡ`ლ)゙  \n" +
                " .-------.       ____     __        \n" +
                " |  _ _   \\      \\   \\   /  /    \n" +
                " | ( ' )  |       \\  _. /  '       \n" +
                " |(_ o _) /        _( )_ .'         \n" +
                " | (_,_).' __  ___(_ o _)'          \n" +
                " |  |\\ \\  |  ||   |(_,_)'         \n" +
                " |  | \\ `'   /|   `-'  /           \n" +
                " |  |  \\    /  \\      /           \n" +
                " ''-'   `'-'    `-..-'              ");
    }
}

1.调用SpringApplication()的构造方法

在执行run方法之前,会调用SpringApplication()的构造方法

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
	
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		return new SpringApplication(primarySources).run(args);
	}

SpringApplication的构造方法,这里主要做几件事
第一件事:确定应用程序类型,看看当前程序是servlet,还是NONE 和 REACTIVE (响应式编程);
第二件事:将启动类设置为Sources(也就是RuoYiApplication)
第三件事:设置初始化器
第四件事:设置监听器
第五件事:程序运行的主类

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		// 将启动类设置为Sources(也就是RuoYiApplication)
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 确定应用程序的类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// 设置初始化器
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 设置监听器
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 程序运行的主类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

在设置初始化器和监听器过程中,会调用getSpringFactoriesInstances()这个方法。该方法主要做的事情就是读取我们META-INF/spring.factories下的文件,并存入缓存cache中。到这里大家就会好奇spring.factories是什么样的文件,有什么作用。

  private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            Map<String, List<String>> result = new HashMap();
            try {
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");

                while(urls.hasMoreElements()) {
                 .......
                }
				 .......
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

spring.factories 文件用于在 Spring Boot 项目中配置自动配置项。它包含了一系列 key-value 对,key 是自动配置类的全限定名,value 是这些配置类对应的条件类。
在这里插入图片描述
在这里插入图片描述

其实也可以理解为,比如我们自己写的类都加了@Componet注解,或者扫描我们自己需要的路径加上@ComponetScan注解。但是有一些我们引入的jar包,它里面的类我们要怎么注入到我们的spring容器中呢。这时候就需要spring.factories,我们只需在这个文件配置全限定名,然后spring去扫描spring.factories文件最后加入到我们容器即可。

2.执行核心run方法()

run()方法中最主要的是prepareContext() 和refreshContext()这两个方法。其他细节我就不在概述了,主要还是以这两个方法为主。

		// 核心方法准备上下文
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			// 刷新上下文
refreshContext(context);
	public ConfigurableApplicationContext run(String... args) {
		// 开启计时器
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		DefaultBootstrapContext bootstrapContext = createBootstrapContext();
		ConfigurableApplicationContext context = null;
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		// 启用监听器
		listeners.starting(bootstrapContext, this.mainApplicationClass);
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
			// 准备环境变量
			ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
			configureIgnoreBeanInfo(environment);
			// 打印beanner信息
			Banner printedBanner = printBanner(environment);
			// 创建上下文
			context = createApplicationContext();
			context.setApplicationStartup(this.applicationStartup);
			// 核心方法准备上下文
			prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
			// 核心刷新上下文
			refreshContext(context);
			afterRefresh(context, applicationArguments);
			stopWatch.stop();
			if (this.logStartupInfo) {
				new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
			}
			listeners.started(context);
			callRunners(context, applicationArguments);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, listeners);
			throw new IllegalStateException(ex);
		}

		try {
			listeners.running(context);
		}
		catch (Throwable ex) {
			handleRunFailure(context, ex, null);
			throw new IllegalStateException(ex);
		}
		return context;
	}

3.执行核心prepareContext()

刷新应用上下文前的准备阶段。也就是prepareContext()方法。

Set sources = getAllSources();还记得前面我们在实例化SpringApplication时,将启动类设置为了sources,在这里我们就获取到了sources,进行load加载。

private void prepareContext(ConfigurableApplicationContext context,
                            ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
                            ApplicationArguments applicationArguments, Banner printedBanner) {
    //设置容器环境
    context.setEnvironment(environment);
    //执行容器后置处理
    postProcessApplicationContext(context);
    //执行容器中的 ApplicationContextInitializer 包括spring.factories和通过三种方式自定义的
    applyInitializers(context);
    //向各个监听器发送容器已经准备好的事件
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
 
    // Add boot specific singleton beans
    //将main函数中的args参数封装成单例Bean,注册进容器
    context.getBeanFactory().registerSingleton("springApplicationArguments",
            applicationArguments);
    //将 printedBanner 也封装成单例,注册进容器
    if (printedBanner != null) {
        context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
    }
 
    // Load the sources
    Set<Object> sources = getAllSources();
    Assert.notEmpty(sources, "Sources must not be empty");
    //加载我们的启动类,将启动类注入容器
    load(context, sources.toArray(new Object[0]));
    //发布容器已加载事件
    listeners.contextLoaded(context);
}

在load方法中,通过createBeanDefinitionLoader()方法为我们创建了一个启动类的BeanDefinition。

protected void load(ApplicationContext context, Object[] sources) {
    if (logger.isDebugEnabled()) {
        logger.debug(
                "Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
    }
    //创建 BeanDefinitionLoader
    BeanDefinitionLoader loader = createBeanDefinitionLoader(
            getBeanDefinitionRegistry(context), sources);
    if (this.beanNameGenerator != null) {
        loader.setBeanNameGenerator(this.beanNameGenerator);
    }
    if (this.resourceLoader != null) {
        loader.setResourceLoader(this.resourceLoader);
    }
    if (this.environment != null) {
        loader.setEnvironment(this.environment);
    }
    loader.load();
}

在 this.annotatedReader.register(source)方法中,会将我们的启动类注入到我们的BeanDefinition容器中。

	private void load(Class<?> source) {
		if (isGroovyPresent() && GroovyBeanDefinitionSource.class.isAssignableFrom(source)) {
			// Any GroovyLoaders added in beans{} DSL can contribute beans here
			GroovyBeanDefinitionSource loader = BeanUtils.instantiateClass(source, GroovyBeanDefinitionSource.class);
			((GroovyBeanDefinitionReader) this.groovyReader).beans(loader.getBeans());
		}
		if (isEligible(source)) {
			this.annotatedReader.register(source);
		}
	}

4.执行核心refreshContext()

	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			// 初始化前的准备工作,主要是一些系统属性、环境变量的校验,比如Spring启动需要某些环境变量,可以在这个地方进行设置和校验
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			// 获取bean工厂,ConfigurableListableBeanFactory是默认的容器,在这一步会完成工厂的创建以及beanDefinition的读取
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			// 进入prepareBeanFactory前spring以及完成了对配置的解析,Spring的拓展从这里开始
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				// 留给子类覆盖做拓展,这里一般不做任何处理
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				// 调用所有的BeanFactoryPostProcessors,将结果存入参数beanFactory中
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				// 注册BeanPostProcessors,这里只是注册,真正的调用是在doGetBean中
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				// 初始化消息原,比如国际化
				initMessageSource();

				// Initialize event multicaster for this context.
				// 初始化消息广播器
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				// 留给子类类初始化其他的bean
				onRefresh();

				// Check for listener beans and register them.
				// 注册监听器
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				// 初始化剩下的单例bean,在这里才开始真正的对bean进行实例化和初始化
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				// 完成刷新,通知生命周期处理器刷新过程。
				finishRefresh();
			}
		}
	}

核心方法 invokeBeanFactoryPostProcessors

public static void invokeBeanFactoryPostProcessors(
        ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
  ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
 
    // Invoke BeanDefinitionRegistryPostProcessors first, if any.
    Set<String> processedBeans = new HashSet<String>();
 
    // 1.判断beanFactory是否为BeanDefinitionRegistry,beanFactory为DefaultListableBeanFactory,
    // 而DefaultListableBeanFactory实现了BeanDefinitionRegistry接口,因此这边为true
    if (beanFactory instanceof BeanDefinitionRegistry) {
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        // 用于存放普通的BeanFactoryPostProcessor
        List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<BeanFactoryPostProcessor>();
        // 用于存放BeanDefinitionRegistryPostProcessor
        List<BeanDefinitionRegistryPostProcessor> registryProcessors = new LinkedList<BeanDefinitionRegistryPostProcessor>();
 
        // 2.首先处理入参中的beanFactoryPostProcessors
        // 遍历所有的beanFactoryPostProcessors, 将BeanDefinitionRegistryPostProcessor和普通BeanFactoryPostProcessor区分开
        for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
            if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
                // 2.1 如果是BeanDefinitionRegistryPostProcessor
                BeanDefinitionRegistryPostProcessor registryProcessor =
                        (BeanDefinitionRegistryPostProcessor) postProcessor;
                // 2.1.1 直接执行BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法
                registryProcessor.postProcessBeanDefinitionRegistry(registry);
                // 2.1.2 添加到registryProcessors(用于最后执行postProcessBeanFactory方法)
                registryProcessors.add(registryProcessor);
            } else {
                // 2.2 否则,只是普通的BeanFactoryPostProcessor
                // 2.2.1 添加到regularPostProcessors(用于最后执行postProcessBeanFactory方法)
                regularPostProcessors.add(postProcessor);
            }
        }
 
        // 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.
        // 用于保存本次要执行的BeanDefinitionRegistryPostProcessor
        List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<BeanDefinitionRegistryPostProcessor>();
 
        // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
        // 3.调用所有实现PriorityOrdered接口的BeanDefinitionRegistryPostProcessor实现类
        // 3.1 找出所有实现BeanDefinitionRegistryPostProcessor接口的Bean的beanName
        String[] postProcessorNames =
                beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        // 3.2 遍历postProcessorNames
        for (String ppName : postProcessorNames) {
            // 3.3 校验是否实现了PriorityOrdered接口
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                // 3.4 获取ppName对应的bean实例, 添加到currentRegistryProcessors中,
                // beanFactory.getBean: 这边getBean方法会触发创建ppName对应的bean对象, 目前暂不深入解析
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                // 3.5 将要被执行的加入processedBeans,避免后续重复执行
                processedBeans.add(ppName);
            }
        }
        // 3.6 进行排序(根据是否实现PriorityOrdered、Ordered接口和order值来排序)
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        // 3.7 添加到registryProcessors(用于最后执行postProcessBeanFactory方法)
        registryProcessors.addAll(currentRegistryProcessors);
        // 3.8 遍历currentRegistryProcessors, 执行postProcessBeanDefinitionRegistry方法
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        // 3.9 执行完毕后, 清空currentRegistryProcessors
        currentRegistryProcessors.clear();

}			

和自动装配相关的还是这一段代码

     // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
        // 3.调用所有实现PriorityOrdered接口的BeanDefinitionRegistryPostProcessor实现类
        // 3.1 找出所有实现BeanDefinitionRegistryPostProcessor接口的Bean的beanName
        String[] postProcessorNames =
                beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        // 3.2 遍历postProcessorNames
        for (String ppName : postProcessorNames) {
            // 3.3 校验是否实现了PriorityOrdered接口
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                // 3.4 获取ppName对应的bean实例, 添加到currentRegistryProcessors中,
                // beanFactory.getBean: 这边getBean方法会触发创建ppName对应的bean对象, 目前暂不深入解析
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                // 3.5 将要被执行的加入processedBeans,避免后续重复执行
                processedBeans.add(ppName);
            }
        }
          // 3.6 进行排序(根据是否实现PriorityOrdered、Ordered接口和order值来排序)
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        // 3.7 添加到registryProcessors(用于最后执行postProcessBeanFactory方法)
        registryProcessors.addAll(currentRegistryProcessors);
        // 3.8 遍历currentRegistryProcessors, 执行postProcessBeanDefinitionRegistry方法
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);

首先从我们的BeanDefintionMap中获取是BeanDefinitionRegistryPostProcessor.class的实现类,而ConfigurationClassPostProcess是一个后置处理器的类,主要功能是参与BeanFactory的建造,主要功能如下:

1.解析加了@Configuration的配置类
2.解析@ComponentScan扫描的包
3.解析@ComponentScans扫描的包
4.解析@Import注解

在这里插入图片描述

5.ConfigurationClassPostProcess

在这里插入图片描述
ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,而 BeanDefinitionRegistryPostProcessor 接口继承了 BeanFactoryPostProcessor 接口,所以 ConfigurationClassPostProcessor 中需要重写 postProcessBeanDefinitionRegistry() 方法和 postProcessBeanFactory() 方法。

postProcessBeanDefinitionRegistry()方法:定位、加载、解析、注册相关注解。
postProcessBeanFactory()方法:添加CGLIB增强处理及ImportAwareBeanPostProcessor后置处理类。

我们先来看看ConfigurationClassPostProcessor 的postProcessBeanDefinitionRegistry()方法是怎么做的

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
		PriorityOrdered, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {
    /**
	 * 构建和验证一个类是否被@Configuration修饰,并做相关的解析工作
	 * 如果你对此方法了解清楚了,那么springboot的自动装配原理就清楚了
	 */
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        
        // 创建存放BeanDefinitionHolder的对象集合
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		// 当前registry就是DefaultListableBeanFactory,获取所有已经注册的BeanDefinition的beanName
		String[] candidateNames = registry.getBeanDefinitionNames();
        
        //----------------第一步-----------------
        // 遍历所有要处理的beanDefinition的名称,筛选对应的被注解修饰的beanDefinition
		for (String beanName : candidateNames) {
			// 获取指定名称的BeanDefinition对象
			BeanDefinition beanDef = registry.getBeanDefinition(beanName);
			
			// 判断当前BeanDefinition是否是一个配置类,并为BeanDefinition设置属性为lite或者full,此处设置属性值是为了后续进行调用
			// 如果Configuration配置proxyBeanMethods代理为true则为full
			// 如果加了@Bean、@Component、@ComponentScan、@Import、@ImportResource注解,则设置为lite
			// 如果配置类上被@Order注解标注,则设置BeanDefinition的order属性值
			else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
				// 添加到对应的集合对象中
				configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
			}
		}
}

第一步是遍历所有要处理的beanDefinition的名称,筛选对应的被注解修饰的beanDefinition,做这一步的目的是什么,从开始到现在我们也没有去执行解析启动类上的注解,也没有进行bean的扫描和定义。在前面过程中,我们只是把我们的启动类加入到了beanDefinition的Map中,现在我们是不是该把启动类给取出来,进行注解的解析。
在这里插入图片描述

第二步那肯定是要去解析扫描,启动类上的注解了

//----------------第二步-----------------
        // 存放相关的BeanDefinitionHolder对象
		Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
		// 存放扫描包下的所有bean
		Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
		do {
			// 解析带有@Controller、@Import、@ImportResource、@ComponentScan、@ComponentScans、@Bean的BeanDefinition
			parser.parse(candidates);
			// 将解析完的Configuration配置类进行校验,1、配置类不能是final,2、@Bean修饰的方法必须可以重写以支持CGLIB
			parser.validate();

			// 获取所有的bean,包括扫描的bean对象,@Import导入的bean对象
			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			// 清除掉已经解析处理过的配置类
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			// 判断读取器是否为空,如果为空的话,就创建完全填充好的ConfigurationClass实例的读取器
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
				while (!candidates.isEmpty());

parse()方法会解析配置类上的注解(ComponentScan扫描出的类,@Import注册的类,以及@Bean方法定义的类),解析完以后(解析成ConfigurationClass类),会将解析出的结果放入到parser的configurationClasses这个属性中(这个属性是个Map)。parse会将@Import注解要注册的类解析为BeanDefinition,但是不会把解析出来的BeanDefinition放入到BeanDefinitionMap中,真正放入到map中是在这一行代码实现的:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
	this.deferredImportSelectors = new LinkedList<>();
    // 根据BeanDefinition类型的不同,调用parse()不同的重载方法
    // 实际上最终都是调用processConfigurationClass()方法
	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());
			}
		}
	}
	// 处理延迟importSelector
	processDeferredImportSelectors();
}

processConfigurationClass()方法中最核心的就是doProcessConfigurationClass方法,在该方法会去处理我们的注解逻辑

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
	// 处理配置类,由于配置类可能存在父类(若父类的全类名是以java开头的,则除外),所有需要将configClass变成sourceClass去解析,然后返回sourceClass的父类。
	// 如果此时父类为空,则不会进行while循环去解析,如果父类不为空,则会循环的去解析父类
	// SourceClass的意义:简单的包装类,目的是为了以统一的方式去处理带有注解的类,不管这些类是如何加载的
	// 如果无法理解,可以把它当做一个黑盒,不会影响看spring源码的主流程
	SourceClass sourceClass = asSourceClass(configClass);
	do {
    // 核心处理逻辑
		sourceClass = doProcessConfigurationClass(configClass, sourceClass);
	}
	while (sourceClass != null);
    // 将解析的配置类存储起来,这样回到parse()方法时,能取到值
	this.configurationClasses.put(configClass, configClass);
}

@Component注解,先判断该配置类是否含有@Component注解(isAnnotated方法会找当前类标识的注解,包括注解内部的元注解),

	if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}

@PropertySource注解 ,接着解析配置类的@PropertySource,解析配置文件并加载到容器环境中

	// Process any @PropertySource annotations
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.propertySourceRegistry != null) {
				this.propertySourceRegistry.processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}

解析@ComponentScan组件,首先解析出类上的@ComponentScan和@ComponentScans注解,然后根据配置的扫描包路径,利用ASM技术(ASM技术是一种操作字节码的技术,有兴趣的朋友可以去网上了解下)扫描出所有需要交给Spring管理的类,由于扫描出的类中可能也被加了@ComponentScan和@ComponentScans注解,因此需要进行递归解析,直到所有加了这两个注解的类被解析完成。

Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
		if (!componentScans.isEmpty() &&
				!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
			for (AnnotationAttributes componentScan : componentScans) {
				// The config class is annotated with @ComponentScan -> perform the scan immediately
				Set<BeanDefinitionHolder> scannedBeanDefinitions =
						this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
				// Check the set of scanned definitions for any further config classes and parse recursively if needed
				for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
					BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
					if (bdCand == null) {
						bdCand = holder.getBeanDefinition();
					}
					if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
					// 注意这里会继续调用parse方法,加载扫描有compent注解的类,继续走该方法逻辑加入到我们的BeanDefiantio的map中
						parse(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}

解析@Import注解

@Import:用来向ioc容器注册组件。

注意:@Import只是向容器注册添加组件的相关信息,组件还未实例化,后续由容器进行实例化

如果不理解@Import注解可以看看这篇文章@Import的作用

processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

// 在getImports方法中,会调用collectImports方法,该方法会对我们启动类上的注解进行层层解析。把@import注解获取的类最后加入到Set<SourceClass> imports这个set集合中
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
			throws IOException {

		if (visited.add(sourceClass)) {
			for (SourceClass annotation : sourceClass.getAnnotations()) {
				String annName = annotation.getMetadata().getClassName();
				if (!annName.equals(Import.class.getName())) {
					collectImports(annotation, imports, visited);
				}
			}
			imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
		}
	}

解析@ImportResource注解

// Process any @ImportResource annotations
		AnnotationAttributes importResource =
				AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
		if (importResource != null) {
			String[] resources = importResource.getStringArray("locations");
			Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
			for (String resource : resources) {
				String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
				configClass.addImportedResource(resolvedResource, readerClass);
			}
		}

解析@Bean注解

	// Process individual @Bean methods
		Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
		for (MethodMetadata methodMetadata : beanMethods) {
			configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
		}

至此,通过解析将容器中的所有配置类、通过解析注解得到的beanDefinition都被保存到容器的beanDefinitions集合中。
可是现在还有一个问题。就是我们的beanDefinition的Map中目前注入的只有我们自己所加载定义的Bean,而前面spring.factories获取的类,包括我们@import注解获取需要的类或组件什么时候加入到beanDefinition的map中呢?
这里我就长话短说了。

public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition annotatedBeanDef) {
					parse(annotatedBeanDef.getMetadata(), holder.getBeanName());
				}
		}
	 // 处理延迟importSelector
		this.deferredImportSelectorHandler.process();
	}
	
	// 底层会调用这个方法
	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		// 从启动类的@import上获取到了AutoConfigurationImportSelector,
		// 该类是ImportSelect接口的实现类,它重写了接口中的selectImports()方法,
		//得到一个String类型的数组。该数组是通过底层加载配置文件 META-INF/spring.factories得到的,
		// 该配置文件中定义了大量的配置类
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		// 在我调试过程中,configurations 是有146条数据
		configurations.removeAll(exclusions);
		//  在这一步会给你做删除,只保留当前对本项目需要的依赖,去除后只有27条数据
		// 底层会根据条件化注解进行筛选
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}
	
自动配置中使用的条件化注解 
===============================================================================================================
条件化注解 						    配置生效条件 
===============================================================================================================
@ConditionalOnBean 					配置了某个特定Bean 
@ConditionalOnMissingBean 			没有配置特定的Bean 
@ConditionalOnClass 				Classpath里有指定的类 
@ConditionalOnMissingClass 			Classpath里缺少指定的类 
@ConditionalOnExpression 			给定的Spring Expression Language(SpEL)表达式计算结果为true 
@ConditionalOnJava 					Java的版本匹配特定值或者一个范围值 
@ConditionalOnJndi 					参数中给定的JNDI位置必须存在一个,如果没有给参数,则要有JNDI InitialContext 
@ConditionalOnProperty 				指定的配置属性要有一个明确的值 
@ConditionalOnResource 				Classpath里有指定的资源 
@ConditionalOnWebApplication 		这是一个Web应用程序 
@ConditionalOnNotWebApplication 	这不是一个Web应用程序 
===============================================================================================================
	// 最后调用这个方法将所有类包括配置类都注入到beanDefinitionMap中
	public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
		TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
		for (ConfigurationClass configClass : configurationModel) {
			loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
		}
	}

执行之前beanDefinitionMap只有15条数据
在这里插入图片描述
这里是其中的一个Spring所依赖的类,在这个类中也有其他@Bean相关注解的类,所以在这个过程中。spring也会去做扫描,并把它加入到我们的beanDefinitionMap中。(该类有@ConditionalOnClass注解,表示当前类是可以使用的类)
在这里插入图片描述
至此,不管是我们配置的类。还是springboot中@import注解所添加的类,还是我们@ComponetScan注解扫描以外的类,都已经添加到我们的beanDefinitionMap中了,后续我们只需要进行bean的实例化即可。
在这里插入图片描述

三:流程概述

1、在springboot启动的时候会创建一个SpringApplication对象,在对象的构造方法里面会进行一些参数的初始化工作,最主要的是判断当前应用程序的类型以及设置初始化器以及监听器,并在这个过程中会加载整个应用程序的spring.factories文件,将文件中的内容放到缓存当中,方便后续获取;

2、SpringApplication对象创建完成之后会执行run()方法来完成整个应用程序的启动,启动的过程中有两个最主要的方法prepareContext()和refreshContext(),在这两个方法中完成了自动装配的核心功能,在run()方法里还执行了一些包括上下文对象的创建,打印banner图,异常报告期的准备等各个准备工作,方便后续进行调用;

3、在prepareContext()中主要完成的是对上下文对象的初始化操作,包括属性的设置,比如设置环境变量。在整个过程中有一个load()方法,它主要是完成一件事,那就是将启动类作为一个beanDefinition注册到registry,方便后续在进行BeanFactoryPostProcessor调用执行的时候,可以找到对应执行的主类,来完成对@SpringBootApplication、@EnableAutoConfiguration等注解的解析工作;

4、在refreshContext()方法中会进行整个容器的刷新过程,会调用spring中的refresh()方法,refresh()方法中有13个非常关键的方法,来完成整个应用程序的启动。而在自动装配过程中,会调用的关键的一个方法就是invokeBeanFactoryPostProcessors()方法,在这个方法中主要是对ConfigurationClassPostProcessor类的处理,这个类是BFPP(BeanFactoryPostProcessor)的子类,因为实现了BDRPP(BeanDefinitionRegistryPostProcessor)接口,在调用的时候会先调用BDRPP中的postProcessBeanDefinitionRegistry()方法,然后再调用BFPP中的postProcessBeanFactory()方法,在执行postProcessBeanDefinitionRegistry()方法的时候会解析处理各种的注解,包含@PropertySource、@ComponentScan、@Bean、@Import等注解,最主要的是对@Import注解的解析;

5、在解析@Import注解的时候,会有一个getImport()方法,从主类开始递归解析注解,把所有包含@Import的注解都解析到,然后在processImport()方法中对import的类进行分类,例如AutoConfigurationImportSelect归属于ImportSelect的子类,在后续的过程中会调用AutoConfigurationImportSelector类里面的process方法,来完成整个EnableAutoConfiguration的加载。

四:总结

Spring 和 Spring Boot的最大的区别在于Spring Boot的自动装配原理:
比如:
我们使用Spring创建Web程序时需要导入几个Maven依赖,而Spring Boot只需要一个Maven依赖来创建Web程序,并且Spring Boot还把我们最常用的依赖都放到了一起,现在的我们只需要spring-boot-starter-web这一个依赖就可以完成一个简单的Web应用。

以前用Spring的时候需要XML文件配置开启一些功能,现在Spring Boot不用XML配置了,只需要写一个配置类(@Configuration和继承对应的接口)就可以继续配置。

Spring Boot会通过启动器开启自动装配功能以@EnableAutoConfiguration扫描在spring.factories中的配置,然后通过@EnableAutoConfiguration进行扫描和配置所需要的Bean,自动的扫描SpringBoot项目引入的Maven依赖,只有用到的才会被创建成Bean,然后放到IOC容器内。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/949630.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

信创软件测试质量的四个特性是什么?

对于信创软件而言&#xff0c;需结合其自身的特点、用户单位的实际使用需求&#xff0c;选择合适的质量特性范围&#xff0c;制定恰当的测试方案&#xff0c;以最大效率发现适配问题、尽快地完成适配质量的提升。那么&#xff0c;信创软件测试质量的四个特性是什么?下面&#…

【状压+概率DP】CF678 E

Problem - E - Codeforces 题意&#xff1a; 思路&#xff1a; 首先&#xff0c;n < 18&#xff0c;应当想到状压 很明显&#xff0c;这里可以使用状压DP 设 dp[s][i] 表示&#xff0c;现在选的方案为 s &#xff0c;且我是 i 的最终胜利的概率是多少 重要的是转移 这是…

1688API技术解析,实现获得店铺的所有商品

获得店铺的所有商品可以通过1688的开放API实现。以下是对1688API的技术解析&#xff1a; 1. 注册成为开发者&#xff1a;首先&#xff0c;你需要在1688开放平台上注册成为开发者&#xff0c;并创建一个应用来获取API授权。 2. 获取授权访问令牌&#xff1a;通过使用OAuth 2.0…

Centos7 + Apache Ranger 2.4.0 部署

一、Ranger简介 Apache Ranger提供一个集中式安全管理框架, 并解决授权和审计。它可以对Hadoop生态的组件如HDFS、Yarn、Hive、Hbase等进行细粒度的数据访问控制。通过操作Ranger控制台,管理员可以轻松的通过配置策略来控制用户访问权限。 1、组件列表 # Service Name Liste…

nil、空接口和空结构体联系与区别

nil&#xff1a; nil是空&#xff0c;并不一定是空指针&#xff0c;nil是一个变量&#xff0c;类型是Type 可能是一下6中类型&#xff0c;以下6种类型的初始值 空结构体

简易虚拟培训系统-UI控件的应用4

目录 Slider组件的常用参数 示例-使用Slider控制主轴 示例-Slider控制溜板箱的移动 本文以操作面板为例&#xff0c;介绍使用Slider控件控制开关和速度。 Slider组件的常用参数 Slider组件下面包含了3个子节点&#xff0c;都是Image组件&#xff0c;负责Slider的背景、填充区…

linux————ELK(日志收集系统集群)

一、概述 一、为什么要使用ELK 日志对于分析系统、应用的状态十分重要&#xff0c;但一般日志的量会比较大&#xff0c;并且比较分散。 如果管理的服务器或者程序比较少的情况我们还可以逐一登录到各个服务器去查看、分析。但如果服务器或者程序的数量比较多了之后这种方法就显…

C++ ASIO 实现异步套接字管理

Boost ASIO&#xff08;Asynchronous I/O&#xff09;是一个用于异步I/O操作的C库&#xff0c;该框架提供了一种方便的方式来处理网络通信、多线程编程和异步操作。特别适用于网络应用程序的开发&#xff0c;从基本的网络通信到复杂的异步操作&#xff0c;如远程控制程序、高并…

vulnhub靶机Solstice

下载地址&#xff1a;https://download.vulnhub.com/sunset/solstice.ova 主机发现 arp-scan -l 扫描端口 nmap --min-rate 10000 -p- 192.168.21.147 这里端口有太多于是我就整理了一下 nmap --min-rate 10000 -p- 192.168.21.147 -oA ports 数据整理 cat ports.nmap|grep…

【不良人】官方声明:天罡传电影拍摄三部,第七季仅一句话说明

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析画江湖系列&#xff01; 距离画江湖之不良人第六季完结已经有一段时间了&#xff0c;就在小郑都快忘了这部动漫的时候&#xff0c;突然官方发声了。先是透露了关于画江湖之不良人番外电影天罡传的情报&#xff0c;之后又明…

Ansible自动化运维之playbooks剧本

文章目录 一.playbooks介绍1.playbooks简述2.playbooks剧本格式3.playbooks组成部分4.运行playbooks及检测文件配置 二.模块实战实例1.playbooks模块实战实例2.vars模块实战实例3.指定远程主机sudo切换用户4.when模块实战实例5.with_items迭代模块实战实例6.Templates 模块实战…

windows 搭建 swoole开发环境(官网已支持)

第一步下载&#xff1a;swoole官网下载 swoole-cli-v5.0.3-cygwin-x64.zip 只支持 64 位的系统 第二步解压到指定文件夹&#xff1a;E:\phpstudy_pro\WWW\swoole-cli-v5.0.3-cygwin-x64 第三步设置环境变量&#xff1a;把解压后的文件夹下的 bin 目录路径配置到系统的 Path 环境…

茶凳浅谈-使用QCA7006AQ 让电动汽车成为智慧电网的一环

前言: 智慧电网一词相信大家都已经耳熟能详。智能电网是指采用先进的电力技术和设备、信息与通信技术&#xff0c;系统地实现电网的智能型监测、分析和决策控制&#xff0c;支持新型能源发电和灵活优质用电&#xff0c;具有高自动化水平&#xff0c;并有一定自愈、互动功能的安…

百万级单细胞多组学数据集成

写在前面 这是一篇粉丝来稿&#xff0c;文章题目为“Multi-omics integration in the age of million single-cell data”&#xff0c;于2021年发表于《Nature Reviews Nephrology》上&#xff0c;影响因子为42.439。由于单细胞目前快速的买入了百万级、多组学的时代&#xff…

用WebGPU实现基于物理的渲染

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 最近&#xff0c;我花了相当多的时间在 WebGPU 中使用 IBL&#xff08;基于图像的照明&#xff09;编写 PBR&#xff08;基于物理的渲染&#xff09;渲染器。 PBR 本身并没有什么新奇之处。 这是一项自 2014 年以来就存在的…

Python装饰器(decorators)

本文改编自以下文章&#xff1a;Decorators in Python 装饰器是一个很强大的工具&#xff0c;它允许我们很便捷地修改已有函数或者类的功能&#xff0c;我们可以用装饰器把另一个函数包装起来&#xff0c;扩展一些功能而不需要去修改这个函数代码。 预备知识 在Python中&…

度矩阵、邻接矩阵

度矩阵&#xff08;degree matrix&#xff09; 度矩阵是对角阵&#xff0c;对角上的元素为各个顶点的度&#xff0c;顶点vi的度表示和该顶点相关联的变得数量。 在无向图中&#xff0c;顶点vi的度d(vi)N(i)&#xff08;即与顶点相连的边的数目&#xff09;有向图中&#xff0…

六年北漂:一个普通程序员的成长之路

微信推送规则改了&#xff0c;星标一下公众号&#xff0c;否则可能收不到推送 收拾完东西&#xff0c;终于忙完一天了&#xff0c;坐在桌子前&#xff0c;梳理一下我的北漂经历。 中午刚下飞机&#xff0c;到了住的地方&#xff0c;直接开始下雨&#xff0c;瞬间感受到一股闷热…

揭秘:房产小程序如何助力售楼业务提升

随着移动互联网的发展&#xff0c;小程序已经成为各行各业进行营销推广的利器之一。对于房地产行业而言&#xff0c;小程序同样具有巨大的潜力。下面&#xff0c;我们将介绍如何使用乔拓云平台开发一款吸睛的房地产营销小程序。 第一步&#xff1a;注册登录乔拓云平台&#xff…