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的自动配置类。 |
|