Spring Boot自动装配原理

本文的代码分析基于Spring Boot 2.1.5的版本。

Spring Boot相对于Spring的一大改变或者优势来说就是“约定大于配置”的思想,不像Spring一样所有的配置都需要我们自己去实现,Spring Boot集成了许多默认的配置。拿Spring MVC来举例,原来Spring时代是通过写两个XML配置文件来实现的,一个web.xml,另一个applicationContext.xml。这些文件内容复杂,且大部分情况下不需要改变,在各个项目中的迁移也只是复制粘贴里面的代码而已,这无疑增加了使用成本。而在Spring Boot中,只需要引入相关spring-boot-starter-web依赖即可,其他的配置都不需要。即使有需要其他配置的地方,统一在application.properties配置文件中进行配置即可,该文件写法是类似于json的键值对的格式,不像XML格式那样的重量级。

那么既然不需要相关配置,Spring Boot是如何实现自动装配类的呢?如何在项目启动的时候将需要加载的类都注入到Spring的IoC容器中?本文将探究这个问题。

通常在Spring Boot的启动类上会加上@SpringBootApplication的注解,如下所示:

@SpringBootApplication
public class Application {

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

其注解主要是由三个子注解构成的,分别是@SpringBootConfiguration、@EnableAutoConfiguration和@ComponentScan:

@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注解而不再需要使用以上三个注解来标识启动类了。其中@EnableAutoConfiguration是开启自动装配的功能,该注解的代码如下所示:

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

由上所示,@EnableAutoConfiguration注解需要引入AutoConfigurationImportSelector这个类或者其子类,如果没有找到,则自动装配失败。

而在上述启动时会调用SpringApplication的run方法。该方法会最终调用如下所示的重载run方法:

        /**
	 * Run the Spring application, creating and refreshing a new
	 * {@link ApplicationContext}.
	 * @param args the application arguments (usually passed from a Java main method)
	 * @return a running {@link ApplicationContext}
	 */
	public ConfigurableApplicationContext run(String... args) {
		StopWatch stopWatch = new StopWatch();
		stopWatch.start();
		ConfigurableApplicationContext context = null;
		Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
		configureHeadlessProperty();
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
		try {
			ApplicationArguments applicationArguments = new DefaultApplicationArguments(
					args);
			ConfigurableEnvironment environment = prepareEnvironment(listeners,
					applicationArguments);
			configureIgnoreBeanInfo(environment);
			Banner printedBanner = printBanner(environment);
			context = createApplicationContext();
			exceptionReporters = getSpringFactoriesInstances(
					SpringBootExceptionReporter.class,
					new Class[] { ConfigurableApplicationContext.class }, context);
			prepareContext(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, exceptionReporters, listeners);
			throw new IllegalStateException(ex);
		}

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

以上方法会创建/刷新ApplicationContext、初始化Environment、listeners等一系列操作。在第23行代码中,其会调用getSpringFactoriesInstances方法,同时会将SpringBootExceptionReporter.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方法。SpringFactoriesLoader类的作用是利用工厂的加载机制来读取装配资源的类,其部分源码如下所示:

public final class SpringFactoriesLoader {

	/**
	 * The location to look for factories.
	 * <p>Can be present in multiple JAR files.
	 */
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

        //...

        private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();

        //...

        /**
	 * Load the fully qualified class names of factory implementations of the
	 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
	 * class loader.
	 * @param factoryClass the interface or abstract class representing the factory
	 * @param classLoader the ClassLoader to use for loading resources; can be
	 * {@code null} to use the default
	 * @throws IllegalArgumentException if an error occurs while loading factory names
	 * @see #loadFactories
	 */
	public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		return loadSpringFactories(classLoader).getOrDefault(factoryClassName, 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 factoryClassName = ((String) entry.getKey()).trim();
					for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryClassName, factoryName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

        //...

}

其中可以看到FACTORIES_RESOURCE_LOCATION这个常量,它就是读取配置资源的文件地址,也就是说会从spring.factories文件中读取所有需要注入的类出来,每一个jar包下基本上都会有一个spring.factories配置文件。而上面源码中第25行的loadFactoryNames方法会调用第30行的loadSpringFactories方法,该方法的作用是读取加载进来的所有jar包下的spring.factories配置文件中的内容,将其放到一个本地缓存cache中。缓存的意义在于第一次调用该方法时会读取spring.factories文件并将读取中的结果放到缓存中,之后再调用该方法时就不再读取文件,而直接返回缓存中的内容就行了。

在spring-boot-autoconfigure的jar包下META-INF目录中的spring.factories文件的部分内容如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\

//...

由上可以看到,其文件格式由键值对组成,每一行末尾的反斜杠表示换行,其他jar包下spring.factories文件的内容也都会有所不同。

而在上面说过的SpringFactoriesLoader类的loadFactoryNames方法中会通过ClassLoader来读取spring.factories文件中的内容。通过run方法中传入的SpringBootExceptionReporter.class这个参数,找到文件中键是SpringBootExceptionReporter所对应的值的集合,通过反射机制来实例化相关的类再返回即可。如前面所说,这里是第一次调用SpringFactoriesLoader类的工厂加载机制的地方,后续再调用的话会直接走缓存中的内容。

再回到最开始SpringApplication类中的run方法中的代码中,之前说的都是基于其中的getSpringFactoriesInstances方法的延展,在该方法执行完毕后,在第28行会调用refreshContext方法,在其中会调用AutoConfigurationImportSelector类的getAutoConfigurationEntry方法,该方法就是自动装配类的方法入口,而AutoConfigurationImportSelector类前面也说过,是@EnableAutoConfiguration这个注解必须要引入的类。在getAutoConfigurationEntry方法中会调用getCandidateConfigurations方法,这两个方法的源码如下:

        /**
	 * Return the {@link AutoConfigurationEntry} based on the {@link AnnotationMetadata}
	 * of the importing {@link Configuration @Configuration} class.
	 * @param autoConfigurationMetadata the auto-configuration metadata
	 * @param annotationMetadata the annotation metadata of the configuration class
	 * @return the auto-configurations that should be imported
	 */
	protected AutoConfigurationEntry getAutoConfigurationEntry(
			AutoConfigurationMetadata autoConfigurationMetadata,
			AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		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);
		configurations = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

        /**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
	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;
	}

        /**
	 * Return the class used by {@link SpringFactoriesLoader} to load configuration
	 * candidates.
	 * @return the factory class
	 */
	protected Class<?> getSpringFactoriesLoaderFactoryClass() {
		return EnableAutoConfiguration.class;
	}

getCandidateConfigurations方法中也会像上面说过的getSpringFactoriesInstances方法一样,调用SpringFactoriesLoader的loadFactoryNames方法来获取配置文件中的内容,只不过这次传入的参数是getSpringFactoriesLoaderFactoryClass(),即EnableAutoConfiguration.class。也就是说会将需要自动装配的类都拿到,通过EnableAutoConfiguration所对应的值的类名的集合。但这次并不会读取spring.factories配置文件,而是会从缓存中读取,因为之前已经调用过loadSpringFactories方法了。读取出的内容依然会使用反射来实例化,将实例化的结果返回回去并最终完成自动装配的全过程。这些自动装配的类会通过一些@ConditionalOnClass、@ConditionalOnMissingClass、@Import之类的注解来加载它们需要的资源。

以上就是Spring Boot完成自动装配的大致核心流程,总结起来就是利用了SpringFactoriesLoader这个类来实现的工厂加载机制,读取jar包下的META-INF目录下的spring.factories配置文件中的内容,然后将需要的类名反射实例化即可。

需要自动装配的类在以前的Spring时代都是需要我们自己写的,但现在Spring Boot的自动装配机制帮我们实现了,只需要引入相关的依赖即可。例如Redis的依赖就是spring-boot-starter-data-redis,引用它就可以了,同时在application.properties配置文件中做些简单的配置即可,就可以直接用起来了,不再需要自己写连接客户端、连接池之类的代码。在上面的spring.factories配置文件的示例中也出现了像RedisAutoConfiguration这样的Redis的自动配置类。

发布了62 篇原创文章 · 获赞 80 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/weixin_30342639/article/details/95549657