springboot实战-自动配置

目录

前言

自定义配置实现

自定义组件测试

源码分析

一、启动类

二、SpringApplication的prepareContext

三、SpringApplication的refreshContext


前言

我们在springboot项目开发过程中,可能存在多个项目需要用到同一个功能,这时候不同的项目组实现这个功能都采用了不同的方式,同时后期需要各自项目组进行代码维护升级,这样会导致代码质量不一致、重复造轮子、浪费人力等问题。

遇到上面的问题,需要思考下此功能是否具备通用性,如第三方的接入、中间件的接入、工具类等,如果具备,那么我们可以把这一块功能抽离出来,做成公共组件,在springboot的项目中,我们就可以采用自定义starter组件的方式实现公共组件。

自定义配置实现

1、创建自定义配置starter项目,目录如下:

2、pom.xml引入maven包spring-boot-autoconfigure

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sam-spring-boot</artifactId>
        <groupId>com.sam.project</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>my-spring-boot-starter</artifactId>

    <name>my-spring-boot-starter</name>
    <version>1.0.0</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.3.4.RELEASE</version>
        </dependency>
    </dependencies>

</project>

这里要注意,如果要做成组件可以maven打包出来供其他应用引入使用,packaging要设置成jar

3、创建一个User类,因为是例子,这里就只设置一个属性name

package com.sam.project.my.domain;

public class User {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

4、创建属性类:MyProperty,负责从application.properties文件中获取配置的属性值

package com.sam.project.my.config;

import org.springframework.boot.context.properties.ConfigurationProperties;


@ConfigurationProperties(prefix = "my.user")
public class MyProperty {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

@ConfigurationProperties(prefix = "my.user")注解就是标识要匹配前缀是my.user的值,name对应的是my.user.name=xxxx的值

5、编写配置类:MyAutoConfiguration,这是关键类

package com.sam.project.my;

import com.sam.project.my.config.MyProperty;
import com.sam.project.my.domain.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableConfigurationProperties(MyProperty.class)
public class MyAutoConfiguration {

    @Autowired
    private MyProperty myProperty;

    @Bean
    @ConditionalOnMissingBean(User.class)
    public User user(){
        User user = new User();
        user.setName(myProperty.getName());
        return user;
    }

}
  • @Configuration 标识这个类是配置类,类似于spring的xml配置文件的作用;
  • @EnableConfigurationProperties(MyProperty.class) 看到Enable的前缀就猜出,只有加上这个配置,才能让MyProperty的类初始化自动填充属性,下面就可以用@Autowired获取这个bean来使用
  • @Bean 就是表示这个方法创建的User作为bean注入到IOC容器;
  • @ConditionalOnMissingBean 表示当容器中没有User这个Bean时才执行这个方法新建一个User的Bean;

6、在resources资源文件夹下面,创建文件夹META-INF,然后创建文件spring.factories(路径是/resources/META-INF/spring.factories)

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sam.project.my.MyAutoConfiguration

到此,我们就已经创建好组件了,然后使用maven执行install命令打包到本地仓库中就可以;

自定义组件测试

1、创建一个springboot的web项目,然后pom中引入上面的my-spring-boot-starter组件maven包

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sam-spring-boot</artifactId>
        <groupId>com.sam.project</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>web-test-spring-boot-start</artifactId>
    <packaging>war</packaging>

    <name>web-test-spring-boot-start</name>

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

        <dependency>
            <groupId>com.sam.project</groupId>
            <artifactId>my-spring-boot-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <finalName>sam-project-web</finalName>
    </build>

</project>

2、编写application.properties文件

server.port=8080
server.servlet.context-path=/sam/web

my.user.name=sam

3、编写测试controller类

package com.sam.project.web.controller;

import com.sam.project.my.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@RequestMapping(value = "/test", produces = "application/json; charset=utf-8")
public class TestController {


    @Resource
    private User user;

    @GetMapping(value = "/name/get")
    public String getUserName() {
        return "hello " + user.getName();
    }

}

这里就可以获取User里面的值为sam

4、编写启动类SamWebApplication

package com.sam.project.web;

import org.springframework.boot.autoconfigure.SpringBootApplication;

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

5、测试

启动运行后,在浏览器输入http://localhost:8080/sam/web/test/name/get

 

源码分析

一、启动类

先从启动类来分析如何加载组件

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

通过jar命令启动的时候,执行run方法

    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);
	}

new SpringApplication(primarySources)执行初始化,这里进去,这里的primarySource是class com.sam.project.web.SamWebApplication,后面就会根据这个primarySource进行解析。

    public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}


	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		......
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		......
	}

我们关注setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class))里面的方法getSpringFactoriesInstances(ApplicationContextInitializer.class)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

进入方法SpringFactoriesLoader.loadFactoryNames(type, classLoader)

    public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		String factoryTypeName = factoryType.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
		MultiValueMap<String, String> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}

		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			result = new LinkedMultiValueMap<>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

我们可以看到调用的第二个loadFactoryNames方法里面的代码classLoader.getResources("META-INF/spring.factories")会加载项目里面所有resources资源文件夹下的META-INF/spring.factories文件,然后解析spring.factories,获取里面的keyValue格式的Properties值,最后保存到Map<ClassLoader, MultiValueMap<String, String>> cache中,其中ClassLoader是用来区分加载器,MultiValueMap里面的内容我们可以看一下:

其中EnableAutoConfiguration的map里面value就有我们后续自己实现在spring.factories设置的自定义组件的初始化配置类的全路径类名称。

(注意,这里只是全量预缓存,还没有真正用到EnableAutoConfiguration里面的value列表进行实例化,目前还是执行者其中的获取ApplicationContextInitializer的value里面的类的实例化)

二、SpringApplication的prepareContext

接着就是执行new SpringApplication(primarySources).run(args)的方法中执行的prepareContext进行容器准备,一直跟到最后的load方法:

SpringApplication {
	run(...) {
		// 准备context
		prepareContext(context, environment, listeners, applicationArguments, printedBanner);
	}

	private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
			SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
		......
		// 这里获取到了之前primarySource的值class com.sam.project.web.SamWebApplication,然后执行load加载
		Set<Object> sources = getAllSources();
		Assert.notEmpty(sources, "Sources must not be empty");
		load(context, sources.toArray(new Object[0]));
		......
	}
	
	protected void load(ApplicationContext context, Object[] sources) {
		......
		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();
	}
}

进去到BeanDefinitionLoader这个加载BeanDefinition的类方法中,这里主要就是对SamWebApplication类型的Source进行加载


BeanDefinitionLoader {
	int load() {
		int count = 0;
		for (Object source : this.sources) {
		//class com.sam.project.web.SamWebApplication,执行load加载
			count += load(source);
		}
		return count;
	}
	
	private int load(Object source) {
		Assert.notNull(source, "Source must not be null");
		if (source instanceof Class<?>) {
			return load((Class<?>) source);
		}
		......
	}
	private int load(Class<?> source) {
		......
		if (isEligible(source)) {
		// 调用AnnotatedBeanDefinitionReader的注册
			this.annotatedReader.register(source);
			return 1;
		}
		return 0;
	}
}

this.annotatedReader.register(source)注册调用的就是AnnotatedBeanDefinitionReader类的注册方法,将SamWebApplication和BeanDefinition的映射注册进AnnotationConfigServletWebServerApplicationContext容器里面

AnnotatedBeanDefinitionReader {
	public void register(Class<?>... componentClasses) {
		for (Class<?> componentClass : componentClasses) {
			registerBean(componentClass);
		}
	}
	public void registerBean(Class<?> beanClass) {
		doRegisterBean(beanClass, null, null, null, null);
	}
	private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
			@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
			@Nullable BeanDefinitionCustomizer[] customizers) {
		// 这里解析com.sam.project.web.SamWebApplication的class获取metaData基础数据,得到BeanDefinition
		AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
		......
		BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
		definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
		// 将SamWebApplication和BeanDefinition的映射注册进AnnotationConfigServletWebServerApplicationContext里面
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
	}
}

三、SpringApplication的refreshContext

执行完prepareContext准备工作后,接着会执行

1、refreshContext(context)刷新容器方法

SpringApplication {
	run(...) {
		// 1、刷新context
		refreshContext(context);
	}
	private void refreshContext(ConfigurableApplicationContext context) {
		refresh((ApplicationContext) context);
		......
	}
	protected void refresh(ApplicationContext applicationContext) {
		Assert.isInstanceOf(ConfigurableApplicationContext.class, applicationContext);
		refresh((ConfigurableApplicationContext) applicationContext);
	}
	protected void refresh(ConfigurableApplicationContext applicationContext) {
		applicationContext.refresh();
	}
}

2、applicationContext的refresh方法使用的是抽象类AbstractApplicationContext类中的refresh方法;

3、实例化和调用所有 BeanFactoryPostProcessor, BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它

4、getBeanFactoryPostProcessors()获取当前已加载的三个Processors:

  • org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
  • org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
  • org.springframework.boot.context.config.ConfigFileApplicationListener$PropertySourceOrderingPostProcessor

AbstractApplicationContext {
	// 2、调用到抽象类的刷新方法
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
				......
				// 3、实例化和调用所有 BeanFactoryPostProcessor, BeanFactoryPostProcessor 在容器实例化任何 bean 之前读取 bean 的定义,并可以修改它
				invokeBeanFactoryPostProcessors(beanFactory);
				......
	}
	
	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		// 4、获取当前已加载的三个Processors:
		//[org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor, //org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor, //org.springframework.boot.context.config.ConfigFileApplicationListener$PropertySourceOrderingPostProcessor]
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
		......
	}

}

5、PostProcessorRegistrationDelegate的invokeBeanFactoryPostProcessors负责按照优先级处理BeanDefinitionRegistryPostProcessors,先处理org.springframework.context.annotation.internalConfigurationAnnotationProcessor。这里第一个优先处理是currentRegistryProcessors=ConfigurationClassPostProcessor这个用于处理配置类的Processor。

6、调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法注册;

PostProcessorRegistrationDelegate {
	public static void invokeBeanFactoryPostProcessors(
			ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
		// 5、按照优先级处理BeanDefinitionRegistryPostProcessors,先处理org.springframework.context.annotation.internalConfigurationAnnotationProcessor
		// 这里第一次处理是currentRegistryProcessors=ConfigurationClassPostProcessor
		invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
		......
	}
	private static void invokeBeanDefinitionRegistryPostProcessors(
			Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
		for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
		// 6、调用ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法注册;
			postProcessor.postProcessBeanDefinitionRegistry(registry);
		}
	}
}

7、遍历candidateNames,如果是属于自定义的@Configuration注解修饰的配置类的configCandidates(这里只有我们的启动类samWebApplication符合,其他都是系统的),就加到configCandidates中;接下来执行的就是解析每一个@Configuration注解的类,这里只有一个samWebApplication需要解析,执行方法parser.parse(candidates);

ConfigurationClassPostProcessor {
	public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
		......
		processConfigBeanDefinitions(registry);
	}
	
	public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
		List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
		String[] candidateNames = registry.getBeanDefinitionNames();
		// 7、下面的省略代码是遍历candidateNames,如果是属于自定义的@Configuration注解修饰的配置类的configCandidates(这里只有我们的启动类samWebApplication符合,其他都是系统的),就加到configCandidates中
		......
		// 解析每一个@Configuration注解的类,这里只有一个samWebApplication需要解析
		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();

			Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
			configClasses.removeAll(alreadyParsed);

			// Read the model and create bean definitions based on its content
			if (this.reader == null) {
				this.reader = new ConfigurationClassBeanDefinitionReader(
						registry, this.sourceExtractor, this.resourceLoader, this.environment,
						this.importBeanNameGenerator, parser.getImportRegistry());
			}
			// 18、这个方法主要是把前面解析出来的配置类中的@Bean注解的方法解析成beanDefinition都注册到容器中
			this.reader.loadBeanDefinitions(configClasses);
			alreadyParsed.addAll(configClasses);

			candidates.clear();
			if (registry.getBeanDefinitionCount() > candidateNames.length) {
				String[] newCandidateNames = registry.getBeanDefinitionNames();
				Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
				Set<String> alreadyParsedClasses = new HashSet<>();
				for (ConfigurationClass configurationClass : alreadyParsed) {
					alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
				}
				for (String candidateName : newCandidateNames) {
					if (!oldCandidateNames.contains(candidateName)) {
						BeanDefinition bd = registry.getBeanDefinition(candidateName);
						if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
								!alreadyParsedClasses.contains(bd.getBeanClassName())) {
							candidates.add(new BeanDefinitionHolder(bd, candidateName));
						}
					}
				}
				candidateNames = newCandidateNames;
			}
		}
		......
	}
}

8、第7步得到的com.sam.project.web.SamWebApplication的BeanDefinitionHolder属于AnnotatedBeanDefinition,调用这里解析;

9、处理校验SamWebApplication的注解,并组装这个的SourceClass资源类

10、循环递归处理

11、doProcessConfigurationClass方法执行:如果是有注解@Compnent内部类,并且是配置类,就执行processConfigurationClass方法递归;

12、处理有注解@PropertySource的属性

13、处理有注解@ComponentScan

14、处理有@Import的注解

  • getImports(sourceClass)遍历SamWebApplication启动资源类的所有注解,获取Import注解的资源类AutoConfigurationPackages和AutoConfigurationImportSelector
  • AutoConfigurationPackages添加到SamWebApplication的ConfigurationClass类的importBeanDefinitionRegistrars中
  • AutoConfigurationImportSelector实例化成ImportSelector后添加到当前的List<DeferredImportSelectorHolder>中,等待后续的处理解析;

ConfigurationClassParser {
	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try {
				if (bd instanceof AnnotatedBeanDefinition) {
				// 8、属于AnnotatedBeanDefinition,调用这里解析,com.sam.project.web.SamWebApplication的BeanDefinitionHolder
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				......
			}
		}
		// 17、这里是处理下面AutoConfigurationImportSelector实例化的ImportSelector,得到的就是EnableAutoConfiguration映射的配置列表
		this.deferredImportSelectorHandler.process();
	}
	
	protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
		processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
	}
	
	protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
		......
		// 9、处理校验SamWebApplication的注解,并组装sourceClass
		SourceClass sourceClass = asSourceClass(configClass, filter);
		// 10、循环递归处理
		do {
			sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
		}
		while (sourceClass != null);

		this.configurationClasses.put(configClass, configClass);
	}
	
	protected final SourceClass doProcessConfigurationClass(
			ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
			throws IOException {
		//11、如果是有注解@Compnent内部类,判断如果是配置类,就执行processConfigurationClass方法递归
		if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
			// Recursively process any member (nested) classes first
			processMemberClasses(configClass, sourceClass, filter);
		}

		//12、处理有注解@PropertySource的属性
		for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
				sourceClass.getMetadata(), PropertySources.class,
				org.springframework.context.annotation.PropertySource.class)) {
			if (this.environment instanceof ConfigurableEnvironment) {
				processPropertySource(propertySource);
			}
			else {
				logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
						"]. Reason: Environment must implement ConfigurableEnvironment");
			}
		}
		//13、处理有注解@ComponentScan
		// Process any @ComponentScan annotations
		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(bdCand.getBeanClassName(), holder.getBeanName());
					}
				}
			}
		}
		//14、处理有@Import的注解
		// getImports(sourceClass)遍历SamWebApplication启动资源类的所有注解,获取Import注解的资源类AutoConfigurationPackages和AutoConfigurationImportSelector
		// AutoConfigurationPackages添加到SamWebApplication的ConfigurationClass类的importBeanDefinitionRegistrars中
		// AutoConfigurationImportSelector实例化成ImportSelector后添加到当前的List<DeferredImportSelectorHolder>中
		processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

		......
		return null;
	}
	
	
}

15、ConfigurationClassParser的processImports()方法会调用getImports执行process方法,这里的group.process就是调用AutoConfigurationImportSelector的process方法

		public Iterable<Group.Entry> getImports() {
			for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
				this.group.process(deferredImport.getConfigurationClass().getMetadata(),
						deferredImport.getImportSelector());
			}
			return this.group.selectImports();
		}

16、AutoConfigurationImportSelector.java类的getAutoConfigurationEntry方法里面的方法getCandidateConfigurations获取候选配置类名称;这里loadFactoryNames方法才是从前面说的Map<ClassLoader, MultiValueMap<String, String>> cache里面获取EnableAutoConfiguration.class对应的配置列表;

	public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
		Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
				() -> String.format("Only %s implementations are supported, got %s",
						AutoConfigurationImportSelector.class.getSimpleName(),
						deferredImportSelector.getClass().getName()));
        // 16、调用getCandidateConfigurations方法获取配置类名称列表
		AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
				.getAutoConfigurationEntry(annotationMetadata);
		this.autoConfigurationEntries.add(autoConfigurationEntry);
		for (String importClassName : autoConfigurationEntry.getConfigurations()) {
			this.entries.putIfAbsent(importClassName, annotationMetadata);
		}
	}

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
	}

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

17、执行完上面后,继续执行ConfigurationClassParser类parse()方法的this.deferredImportSelectorHandler.process(),这里是处理上面AutoConfigurationImportSelector实例化的ImportSelector,得到的就是EnableAutoConfiguration映射的配置列表,包含了自定义的MyAutoConfiguration;

18、执行完ConfigurationClassParser.parse(...)方法后,ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法继续执行到this.reader.loadBeanDefinitions(configClasses),这个方法的作用就是把前面解析出来的配置类中的@Bean注解的方法解析成beanDefinition都注册到容器中。

结尾:到这里,已经把自定义Configuration的类进行解析,对@Bean等配置类里面的资源都解析成BeanDefinition注册到容器中;然后等到执行finishBeanFactoryInitialization(beanFactory)方法的时候就进行bean的初始化。

 

 

热门文章

暂无图片
编程学习 ·

Java输出数组的内容

Java输出数组的内容_一万个小时-CSDN博客_java打印数组内容1. 输出内容最常见的方式// List<String>类型的列表List<String> list new ArrayList<String>();list.add("First");list.add("Second");list.add("Third");list.ad…
暂无图片
编程学习 ·

母螳螂的“魅惑之术”

在它们对大蝗虫发起进攻的时候&#xff0c;我认认真真地观察了一次&#xff0c;因为它们突然像触电一样浑身痉挛起来&#xff0c;警觉地面对限前这个大家伙&#xff0c;然后放下自己优雅的身段和祈祷的双手&#xff0c;摆出了一个可怕的姿势。我被眼前的一幕吓到了&#xff0c;…
暂无图片
编程学习 ·

疯狂填词 mad_libs 第9章9.9.2

#win7 python3.7.0 import os,reos.chdir(d:\documents\program_language) file1open(.\疯狂填词_d9z9d2_r.txt) file2open(.\疯狂填词_d9z9d2_w.txt,w) words[ADJECTIVE,NOUN,VERB,NOUN] str1file1.read()#方法1 for word in words :word_replaceinput(fEnter a {word} :)str1…
暂无图片
编程学习 ·

HBASE 高可用

为了保证HBASE是高可用的,所依赖的HDFS和zookeeper也要是高可用的. 通过参数hbase.rootdir指定了连接到Hadoop的地址,mycluster表示为Hadoop的集群. HBASE本身的高可用很简单,只要在一个健康的集群其他节点通过命令 hbase-daemon.sh start master启动一个Hmaster进程,这个Hmast…
暂无图片
编程学习 ·

js事件操作语法

一、事件的绑定语法 语法形式1 事件监听 标签对象.addEventListener(click,function(){}); 语法形式2 on语法绑定 标签对象.onclick function(){} on语法是通过 等于赋值绑定的事件处理函数 , 等于赋值本质上执行的是覆盖赋值,后赋值的数据会覆盖之前存储的数据,也就是on…
暂无图片
编程学习 ·

Photoshop插件--晕影动态--选区--脚本开发--PS插件

文章目录1.插件界面2.关键代码2.1 选区2.2 动态晕影3.作者寄语PS是一款栅格图像编辑软件&#xff0c;具有许多强大的功能&#xff0c;本文演示如何通过脚本实现晕影动态和选区相关功能&#xff0c;展示从互联网收集而来的一个小插件&#xff0c;供大家学习交流&#xff0c;请勿…
暂无图片
编程学习 ·

vs LNK1104 无法打开文件“xxx.obj”

写在前面&#xff1a; 向大家推荐两本新书&#xff0c;《深度学习计算机视觉实战》和《学习OpenCV4&#xff1a;基于Python的算法实战》。 《深度学习计算机视觉实战》讲了计算机视觉理论基础&#xff0c;讲了案例项目&#xff0c;讲了模型部署&#xff0c;这些项目学会之后可以…
暂无图片
编程学习 ·

工业元宇宙的定义与实施路线图

工业元宇宙的定义与实施路线图 李正海 1 工业元宇宙 给大家做一个关于工业元宇宙的定义。对于工业&#xff0c;从设计的角度来讲&#xff0c;现在的设计人员已经做到了普遍的三维设计&#xff0c;但是进入元宇宙时代&#xff0c;就不仅仅只是三维设计了&#xff0c;我们的目…
暂无图片
编程学习 ·

【leectode 2022.1.15】完成一半题目

有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&#xff1a…
暂无图片
编程学习 ·

js 面试题总结

一、js原型与原型链 1. prototype 每个函数都有一个prototype属性&#xff0c;被称为显示原型 2._ _proto_ _ 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype 3. constructor 每个prot…
暂无图片
编程学习 ·

java练习代码

打印自定义行数的空心菱形练习代码如下 import java.util.Scanner; public class daYinLengXing{public static void main(String[] args) {System.out.println("请输入行数");Scanner myScanner new Scanner(System.in);int g myScanner.nextInt();int num g%2;//…
暂无图片
编程学习 ·

RocketMQ-什么是死信队列?怎么解决

目录 什么是死信队列 死信队列的特征 死信消息的处理 什么是死信队列 当一条消息初次消费失败&#xff0c;消息队列会自动进行消费重试&#xff1b;达到最大重试次数后&#xff0c;若消费依然失败&#xff0c;则表明消费者在正常情况下无法正确地消费该消息&#xff0c;此时…
暂无图片
编程学习 ·

项目 cg day04

第4章 lua、Canal实现广告缓存 学习目标 Lua介绍 Lua语法 输出、变量定义、数据类型、流程控制(if..)、循环操作、函数、表(数组)、模块OpenResty介绍(理解配置) 封装了Nginx&#xff0c;并且提供了Lua扩展&#xff0c;大大提升了Nginx对并发处理的能&#xff0c;10K-1000K Lu…
暂无图片
编程学习 ·

输出三角形

#include <stdio.h> int main() { int i,j; for(i0;i<5;i) { for(j0;j<i;j) { printf("*"); } printf("\n"); } }
暂无图片
编程学习 ·

stm32的BOOTLOADER学习1

序言 最近计划学习stm32的BOOTLOADER学习,把学习过程记录下来 因为现在网上STM32C8T6还是比较贵的,根据我的需求flash空间小一些也可以,所以我决定使用stm32c6t6.这个芯片的空间是32kb的。 #熟悉芯片内部的空间地址 1、flash ROM&#xff1a; 大小32KB&#xff0c;范围&#xf…
暂无图片
编程学习 ·

通过awk和shell来限制IP多次访问之学不会你打死我

学不会你打死我 今天我们用shell脚本&#xff0c;awk工具来分析日志来判断是否存在扫描器来进行破解网站密码——限制访问次数过多的IP地址&#xff0c;通过Iptables来进行限制。代码在末尾 首先我们要先查看日志的格式&#xff0c;分析出我们需要筛选的内容&#xff0c;日志…
暂无图片
编程学习 ·

Python - 如何像程序员一样思考

在为计算机编写程序之前&#xff0c;您必须学会如何像程序员一样思考。学习像程序员一样思考对任何学生都很有价值。以下步骤可帮助任何人学习编码并了解计算机科学的价值——即使他们不打算成为计算机科学家。 顾名思义&#xff0c;Python经常被想要学习编程的人用作第一语言…
暂无图片
编程学习 ·

蓝桥杯python-数字三角形

问题描述 虽然我前后用了三种做法&#xff0c;但是我发现只有“优化思路_1”可以通过蓝桥杯官网中的测评&#xff0c;但是如果用c/c的话&#xff0c;每个都通得过&#xff0c;足以可见python的效率之低&#xff08;但耐不住人家好用啊&#xff08;哭笑&#xff09;&#xff09…