SpringBoot学习笔记16 - 类的自动装配

本文深入探讨了SpringBoot的自动装配机制,从@SpringBootApplication注解的解析开始,详细阐述了@EnableAutoConfiguration的工作原理,包括@AutoConfigurationPackage和@Import的作用。同时,介绍了以@Enable开头的注解功能,如配置类、选择器和注册器的角色。最后,简要提及了application.yml配置文件的加载流程。
摘要由CSDN通过智能技术生成

1、剖析@SpringBootApplication注解


创建一个SpringBoot工程后,SpringBoot会为用户提供一个Application类,该类负责项目的启动:

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

这是一个被@SpringBootApplication注解的类,该注解完成了SpringBoot中类的自动装配任务:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
	...
}

抛却元注解不谈,@SpringBootApplication继承了三个注解:

  • @SpringBootConfiguration
    /**
     * Indicates that a class provides Spring Boot application
     * {@link Configuration @Configuration}. Can be used as an
     * alternative to the Spring's standard @Configuration 
     * annotation so that configuration can be found
     * automatically (for example in tests).
     *
     * Application should only ever include one 
     * @SpringBootConfiguration and most idiomatic Spring Boot 
     * applications will inherit it from @SpringBootApplication.
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Configuration
    public @interface SpringBootConfiguration {
    	...
    }
    
    在说明中提到,@SpringBootConfiguration注解是用来替代Spring的@Configuration,方便SpringBoot自动找到配置。
  • @ComponentScan
    /**
     * Configures component scanning directives
     * for use with Configuration classes.
     * Provides support parallel with Spring XML's
     * <context:component-scan> element.
     *
     * Either #basePackageClasses or #basePackages
     * (or its alias #value} may be specified to
     * define specific packages to scan. If specific
     * packages are not defined, scanning will occur
     * from the package of the class that declares
     * this annotation.
     *
     * Note that the <context:component-scan> element
     * has an annotation-config attribute; however,
     * this annotation does not. This is because
     * in almost all cases when using @ComponentScan,
     * default annotation config processing
     * (e.g. processing @Autowired and friends)
     * is assumed. Furthermore, when using 
     * AnnotationConfigApplicationContext,
     * annotation config processors are always
     * registered, meaning that any attempt to disable
     * them at the @ComponentScan level would be ignored.
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Documented
    @Repeatable(ComponentScans.class)
    public @interface ComponentScan {
    	...
    }
    
    在说明中我们可以得知:@ComponentScan只负责指定要扫描的包,并没有装配其中的类,这个真正装配这些类是@EnableAutoConfiguration
  • @EnableAutoConfiguration
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)
    public @interface EnableAutoConfiguration {
    	...
    }
    
    该类真正完成了SpringBoot对于类的装配工作,具体内容在后续会作出解释。

2、以@Enable开头的注解


以@Enable开头的注解(@EnableXxx)一般用于开启某一项功能,是为了简化代码的导入。它是一个组合注解,一般情况下@EnableXxx注解中都会组合一个@Import注解,而该@Import注解用于导入指定的类,而被导入的类一般有三种:

2.1、配置类

  • 类的特征:@Import中指定的类一般以Configuration结尾
  • 类的配置:该类上会注解@Configuration
  • 类的案例:定时任务启动注解:SchedulingConfiguration
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Import(SchedulingConfiguration.class)
    @Documented
    public @interface EnableScheduling {
    }
    

2.2、选择器

  • 类的特征:@Import中指定的类一般以 Selector 结尾
  • 类的配置:该类直接或间接实现了ImportSelector接口,表示当前类会根据条件选择导入不同的类。
  • 类的案例:Redis配置类:CachingConfigurationSelector
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(CachingConfigurationSelector.class)
    public @interface EnableCaching {
    	...
    }
    
    public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    	...
    	@Override
    	public String[] selectImports(AdviceMode adviceMode) {
    		switch (adviceMode) {
    			case PROXY:
    				return getProxyImports();
    			case ASPECTJ:
    				return getAspectJImports();
    			default:
    				return null;
    		}
    	}
    	...
    }
    

2.3、注册器

  • 类的特征:@Import 中指定的类一般以 Registrar 结尾。
  • 类的配置:该类直接或间接实现了ImportBeanDefinitionRegistrar接口,用于导入注册器,该类可以在代码运行时动态注册指定类的实例。
  • 类的案例:AspectJ:AspectJAutoProxyRegistrar
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    	...
    }
    
    class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    	@Override
    	public void registerBeanDefinitions(
    			AnnotationMetadata importingClassMetadata,
    			BeanDefinitionRegistry registry) {
    
    		AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
    
    		AnnotationAttributes enableAspectJAutoProxy =
    				AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
    		if (enableAspectJAutoProxy != null) {
    			if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
    				AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
    			}
    			if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
    				AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
    			}
    		}
    	}
    }
    

3、解析@EnableAutoConfiguration


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

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

该注解是一个组合注解,用于完成自动配置,它是Spring Boot的核心注解。所谓自动配置是指,将用户自定义的类框架本身用到的类进行装配。

3.1、@AutoConfigurationPackage

/**
 * Registers packages with AutoConfigurationPackages.
 * When no #basePackages base packages or
 * #basePackageClasses base package classes are
 * specified, the package of the annotated class is 
 * registered.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
	...
}

从类的说明中我的得知,该注解用于导入并装配用户自定义类,即自动扫描包中的类。若该注解未通过basePackagesbasePackageClasses参数指明要扫描的包路径,则默认扫描含该注解的类所在包及其子包。

3.2、@Import

用于导入并装配框架本身的类。其参数AutoConfigurationImportSelector.java类,该类用于导入自动配置的类。其装配跟踪入口:#getCandidateConfigurations

public class AutoConfigurationImportSelector implements 
		DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, 
		EnvironmentAware, Ordered {
	...
	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;
	}
	...
}

#getCandidateConfigurations -> SpringFactoriesLoader.loadFactoryNames

public final class SpringFactoriesLoader {
	...
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	...
	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) {
		...
		try {
			Enumeration<URL> urls = (classLoader != null ?
					classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			...
		} catch (IOException ex) {
			...
		}
	}
}

追踪到这里,我们得知,框架本身定义的类是从META-INF/spring.factories文件中获取的。该文件目录在哪儿呢?
在创建SpringBoot Web项目时,我们在pom.xml文件中会自动导入一个依赖:

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

打开一个starter,如spring-boot-starter-web依赖,我们可以看到其中包含了一个子依赖:

<!-- spring-boot-starter-web-2.3.4.RELEASE.pom -->
<dependencies>
	...
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
		<version>2.3.4.RELEASE</version>
		<scope>compile</scope>
	</dependency>
	...
</dependencies>

打开spring-boot-starter依赖,可以看到这么一个子依赖:

<!-- spring-boot-starter-2.3.4.RELEASE.pom -->
<dependencies>
	...
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-autoconfigure</artifactId>
		<version>2.3.4.RELEASE</version>
		<scope>compile</scope>
	</dependency>
    ...
</dependencies>

查看该依赖的内容,打开spring.factories文件:

在这里插入图片描述
在这里插入图片描述
这些就是框架定义的,需要装配的类。

4、application.yml的加载


application.yml文件对于 Spring Boot 来说是核心配置文件,至关重要!那么,该文件是如何加载到内存的呢?我们需要从启动类的run()方法开始跟踪,该跟踪过程比较深,耐心差的读者慎入。

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

进入run方法:

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

	public ConfigurableApplicationContext run(String... args) {
		...
		// 准备运行环境
		ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
		...
	}

	private ConfigurableEnvironment prepareEnvironment(
			SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		...
		// 让监听器监听环境准备过程
		listeners.environmentPrepared(environment);
		...
	}
	...
}

让监听器监听环境准备过程

class SpringApplicationRunListeners {
	...
	void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);
		}
	}
	...
}

发布环境准备事件

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
	...
	@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster.multicastEvent(
			new ApplicationEnvironmentPreparedEvent(
				this.application,
				this.args,
				environment
			)
		);
	}

	@Override
	public void multicastEvent(ApplicationEvent event) {
		multicastEvent(event, resolveDefaultEventType(event));
	}

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				// 触发监听器
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}
	...
}

触发监听器

public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
	...
	protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
		ErrorHandler errorHandler = getErrorHandler();
		if (errorHandler != null) {
			try {
				doInvokeListener(listener, event);
			}
			catch (Throwable err) {
				errorHandler.handleError(err);
			}
		}
		else {
			doInvokeListener(listener, event);
		}
	}

	private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
		...
		listener.onApplicationEvent(event);
		...
	}
	...
}

ApplicationListener#onApplicationEvent是一个接口方法,我们主要看它的ConfigFileApplicationListener实现类的实现

public class ConfigFileApplicationListener implements ... {
	...
	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
		}
		...
	}

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}
	...
}

EnvironmentPostProcessor#postProcessEnvironment是一个接口方法,我们主要看它的ConfigFileApplicationListener实现类的实现

public class ConfigFileApplicationListener implements ... {
	...
	@Override
	public void postProcessEnvironment(
			ConfigurableEnvironment environment,
			SpringApplication application) {
		// 加载配置文件
		addPropertySources(environment, application.getResourceLoader());
	}

	protected void addPropertySources(
			ConfigurableEnvironment environment,
			ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();
	}

	private class Loader {
		void load() {
			FilteredPropertySource.apply(
				this.environment, 
				DEFAULT_PROPERTIES, 
				LOAD_FILTERED_PROPERTY,
				(defaultProperties) -> {
					...
					while (!this.profiles.isEmpty()) {
						...
						load(profile, this::getPositiveProfileFilter,
								addToLoaded(MutablePropertySources::addLast, false));
						...
					}
					...
				});
		}

		private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			getSearchLocations().forEach((location) -> {
				boolean isDirectory = location.endsWith("/");
				Set<String> names = isDirectory ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
			});
		}

		private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
			...
			for (PropertySourceLoader loader : this.propertySourceLoaders) {
				for (String fileExtension : loader.getFileExtensions()) {
					if (processed.add(fileExtension)) {
						loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer);
					}
				}
			}
		}

		private void loadForFileExtension(
				PropertySourceLoader loader,
				String prefix,
				String fileExtension,
				Profile profile,
				DocumentFilterFactory filterFactory,
				DocumentConsumer consumer) {
			...
			load(loader, prefix + fileExtension, profile, profileFilter, consumer);
		}

		private void load(
				PropertySourceLoader loader,
				String location,
				Profile profile,
				DocumentFilter filter,
				DocumentConsumer consumer) {
			...
			List<Document> documents = loadDocuments(loader, name, resource);
			...
		}

		private List<Document> loadDocuments(
				PropertySourceLoader loader,
				String name,
				Resource resource) throws IOException {
			DocumentsCacheKey cacheKey = new DocumentsCacheKey(loader, resource);
			List<Document> documents = this.loadDocumentsCache.get(cacheKey);
			if (documents == null) {
				List<PropertySource<?>> loaded = loader.load(name, resource);
				documents = asDocuments(loaded);
				this.loadDocumentsCache.put(cacheKey, documents);
			}
			return documents;
		}
	}
	...
}

PropertySourceLoader#getFileExtensionsPropertySourceLoader#load都是接口方法,我们主要看它的YamlPropertySourceLoader实现类的实现

public class YamlPropertySourceLoader implements PropertySourceLoader {
	@Override
	public String[] getFileExtensions() {
		return new String[] { "yml", "yaml" };
	}

	@Override
	public List<PropertySource<?>> load(
			String name,
			Resource resource) throws IOException {
		...
		return propertySources;
	}
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值