SpringBoot应用--04-自动化配置机制
1. 了解SPI机制
详见 https://www.cnblogs.com/dduo/p/14984693.html
2. 使用SpringBoot的Enable模块
方式一: 使用注解+@Import(ImportSelector实现类)
step1: 自定义Server接口以及实现类型
/** * @description: 定义服务器接口---Server以及服务类型Server.Type */ public interface Server { /*启动服务器*/ void start(); /*关闭服务器 */ void stop(); enum Type{ HTTP, //HTTP服务器 FTP //FTP服务器 } } public class HttpServer implements Server { @Override public void start() { System.out.println("HTTP Server is starting"); } @Override public void stop() { System.out.println("HTTP Server is stopping"); } } public class FtpServer implements Server { @Override public void start() { System.out.println("FTP Server is starting"); } @Override public void stop() { System.out.println("FTP Server is stopping"); } }
step2: 自定义ImportSelector接口实现类
public class ServerImportSelector implements ImportSelector{ @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { //1. 读取@EnableServer中所有属性方法,当前仅有type() ,其中key为属性名称,value为属性方法的返回对象 Map<String,Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(EnableServer.class.getName()); //2. 获取名为type的属性方法,并且强制转化成Server.Type类型 Server.Type type = (Server.Type) annotationAttributes.get("type"); //3. 导入的类名称数组 String[] importClassNames = new String[0]; switch (type){ case HTTP: importClassNames = new String[]{HttpServer.class.getName()}; break; case FTP: importClassNames = new String[]{FtpServer.class.getName()}; break; } return importClassNames; } }
step3: 自定义注解使用@Import引入ImportSelector接口实现类
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ServerImportSelector.class) public @interface EnableServer { /*设置服务器类型 */ Server.Type type(); }
step4: Spring容器主启动类
/* @description: ImportSelector接口编程实现引导类 */ @EnableServer(type = Server.Type.FTP) public class EnableServerBootstrap { public static void main(String[] args) throws Exception{ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(EnableServerBootstrap.class); ctx.refresh(); Server server = ctx.getBean(Server.class); server.start(); Thread.sleep(5000); server.stop(); ctx.close(); } }
step5: Spring容器主启动类测试,发现自动加载了FTPServer组件
方式二: 使用注解+@Import(ImportBeanDefinitionRegistrar实现类)
step1: 自定义Server接口以及实现类型
step2: 自定义ImportSelector接口实现类 ---与方法一中一样
step3: 自定义ImportBeanDefinitionRegistrar接口实现类
/**@description: 自定义ImportBeanDefinitionRegistrar接口实现 */ public class ServerImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { // 复用 ServerImportSelector实现 ImportSelector importSelector = new ServerImportSelector(); String[] selectedClassNames = importSelector.selectImports(annotationMetadata); // 创建Bean定义 Stream.of(selectedClassNames) // 转化为BeanDefinitionBuilder对象 .map(BeanDefinitionBuilder::genericBeanDefinition) // 转化为BeanDefinition对象 .map(BeanDefinitionBuilder::getBeanDefinition) .forEach(beanDefition -> { // 注册BeanDefinition到BeanDefinitionRegistry(Spring IOC 容器中) BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefition,beanDefinitionRegistry); }); } }
step4: 自定义注解使用@Import引入ImportBeanDefinitionRegistrar接口实现类
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(ServerImportBeanDefinitionRegistrar.class) public @interface EnableServer2 { /* 设置服务器类型 */ Server.Type type(); }
step5: Spring容器主启动类
/* @description: ImportBeanDefinitionRegistrar接口编程实现引导类 */ @EnableServer2(type = Server.Type.HTTP) public class EnableServerBootstrap { public static void main(String[] args) throws Exception{ AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(EnableServerBootstrap.class); ctx.refresh(); Server server = ctx.getBean(Server.class); server.start(); Thread.sleep(1000); server.stop(); ctx.close(); } }
step6: Spring容器主启动类启动测试--结果与方法一一致
3. @SpringBootApplication注解
SpringBootApplication注解是一个组合注解,包含了@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 { @AliasFor(annotation = EnableAutoConfiguration.class) Class<?>[] exclude() default {}; @AliasFor(annotation = EnableAutoConfiguration.class) String[] excludeName() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackages") String[] scanBasePackages() default {}; @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses") Class<?>[] scanBasePackageClasses() default {}; }
4. @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 {}; }
4.1 @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector类是ImportSelector接口的实现类,关键方法是selectImports方法
4.2 selectImports方法解析
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 从注解EnableAutoConfiguration的属性中获取是否支持自动化装配 if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; }
// 加载 META-INF/spring-autoconfigure-metadata.properties文件 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
// 获取EnableAutoConfiguration注解上属性信息 exclude excludeName AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 获取所有自动配置类 List<String> configurations = getCandidateConfigurations(annotationMetadata,attributes);
// 剔除重复的自动配置类 configurations = removeDuplicates(configurations);
// 根据EnableAutoConfiguration注解上 exclude excludeName属性 剔除需要排除的自动装配类 + 校验排除的自动装配类是否是SpringBoot中spring.factories文件中定义的 Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions);
// 根据spring.factories文件中的AutoConfigurationImportFilter 过滤类处理自动装配类(根据spring-autoconfigure-metadata.properties中的各个自动装配类的元数据) configurations = filter(configurations, autoConfigurationMetadata);
// 根据spring.factories文件中的AutoConfigurationImportListener事件监听器发布并处理监听事件 fireAutoConfigurationImportEvents(configurations, exclusions); return StringUtils.toStringArray(configurations); }
4.2.1 isEnabled方法详情--查看当前Spring应用是否支持自动装配
protected boolean isEnabled(AnnotationMetadata metadata) { if (getClass() == AutoConfigurationImportSelector.class) { return getEnvironment().getProperty( EnableAutoConfiguration.ENABLED_OVERRIDE_PROPERTY, Boolean.class,true); } return true; } String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
4.2.2 AutoConfigurationMetadataLoader.loadMetadata方法详情
final class AutoConfigurationMetadataLoader { protected static final String PATH = "META-INF/" + "spring-autoconfigure-metadata.properties"; private AutoConfigurationMetadataLoader() {} public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) { return loadMetadata(classLoader, PATH); } static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader, String path) { try { Enumeration<URL> urls = (classLoader != null) ? classLoader.getResources(path): ClassLoader.getSystemResources(path);
// 使用一个Properties对象将文件中的配置信息读取进去 Properties properties = new Properties(); while (urls.hasMoreElements()) { properties.putAll(PropertiesLoaderUtils.loadProperties(new UrlResource(urls.nextElement()))); } return loadMetadata(properties); }catch (IOException ex) { throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", ex); } } // 该方法的作用是将Properties对象数据包进PropertiesAutoConfigurationMetadata类中,该类中提供了更加方便获取数据的方法 static AutoConfigurationMetadata loadMetadata(Properties properties) { return new PropertiesAutoConfigurationMetadata(properties); }
AutoConfigurationMetadata 接口定义
public interface AutoConfigurationMetadata { /*Return {@code true} if the specified class name was processed by the annotation */ boolean wasProcessed(String className); /* Get an {@link Integer} value from the meta-data. */ Integer getInteger(String className, String key); /*Get an {@link Integer} value from the meta-data. */ Integer getInteger(String className, String key, Integer defaultValue); /*Get a {@link Set} value from the meta-data.*/ Set<String> getSet(String className, String key); /*Get a {@link Set} value from the meta-data.*/ Set<String> getSet(String className, String key, Set<String> defaultValue); /* Get an {@link String} value from the meta-data.*/ String get(String className, String key); /*Get an {@link String} value from the meta-data.*/ String get(String className, String key, String defaultValue); }
spring-autoconfigure-metadata.properties文件数据 定义了要自动加载的类的一些元数据信息(例如加载条件Conditional,绝对加载顺序Order,相对加载顺序Before|After等)
#Thu Apr 05 11:37:28 UTC 2018 org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration= org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration.Configuration= org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration.ConditionalOnClass=com.datastax.driver.core.Cluster,org.springframework.data.cassandra.core.ReactiveCassandraTemplate,reactor.core.publisher.Flux org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration.ConditionalOnClass=org.apache.solr.client.solrj.SolrClient,org.springframework.data.solr.repository.SolrRepository org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration= org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration= org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.Configuration= org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureBefore=org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration org.springframework.boot.autoconfigure.jms.artemis.ArtemisXAConnectionFactoryConfiguration= org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration.Configuration= org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration= org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration= org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass=com.couchbase.client.java.CouchbaseBucket,com.couchbase.client.java.Cluster org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration.ConditionalOnClass=org.springframework.amqp.rabbit.core.RabbitTemplate,com.rabbitmq.client.Channel org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration.AutoConfigureOrder=-2147483648 org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration= org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigureOrder=-2147483638 org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.Configuration= org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration.ConditionalOnClass=com.couchbase.client.java.Bucket,org.springframework.data.couchbase.repository.ReactiveCouchbaseRepository,reactor.core.publisher.Flux org.springframework.boot.autoconfigure.batch.BatchConfigurerConfiguration.Configuration= org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration.Configuration= org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration.Configuration= org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration.ConditionalOnClass=javax.sql.DataSource,org.springframework.jdbc.core.JdbcTemplate org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration=
4.2.3 getAttributes方法追踪 --获取@EnableAutoConfiguration注解上exclude excludeName 需要排除的配置类
protected AnnotationAttributes getAttributes(AnnotationMetadata metadata) { String name = getAnnotationClass().getName(); AnnotationAttributes attributes = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(name, true)); Assert.notNull(attributes, () -> "No auto-configuration attributes found. Is " + metadata.getClassName() + " annotated with " + ClassUtils.getShortName(name) + "?"); return attributes; }
4.2.4 getCandidateConfigurations方法追踪
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; }
SpringFactoriesLoader类中方法: SpringBoot SPI机制读取数据的加载类,加载META-INF/spring.factories文件中key为EnableAutoConfiguration的所有自动装配类
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; 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); } }
4.2.5 removeDuplicates 方法追踪--将读取的自动配置类进行去重操作-使用LinkedHashSet
protected final <T> List<T> removeDuplicates(List<T> list) { return new ArrayList<>(new LinkedHashSet<>(list)); }
4.2.6 getExclusions方法追踪-从EnableAutoConfiguration注解上属性信息 exclude excludeName 需要排除的自动装配类
protected Set<String> getExclusions(AnnotationMetadata metadata, AnnotationAttributes attributes) { Set<String> excluded = new LinkedHashSet<>(); excluded.addAll(asList(attributes, "exclude")); excluded.addAll(Arrays.asList(attributes.getStringArray("excludeName"))); excluded.addAll(getExcludeAutoConfigurationsProperty()); return excluded; }
4.2.7 checkExcludedClasses方法追踪-检查是否存在 当前类路径下存在但是自动配置类集合中不存在的排除类,如果有抛出异常,言外之意排除的自动装配类只能是SpringBoot中定义的
private void checkExcludedClasses(List<String> configurations, Set<String> exclusions) { List<String> invalidExcludes = new ArrayList<>(exclusions.size()); for (String exclusion : exclusions) { if (ClassUtils.isPresent(exclusion, getClass().getClassLoader()) && !configurations.contains(exclusion)) { invalidExcludes.add(exclusion); } } if (!invalidExcludes.isEmpty()) { handleInvalidExcludes(invalidExcludes); } } protected void handleInvalidExcludes(List<String> invalidExcludes) { StringBuilder message = new StringBuilder(); for (String exclude : invalidExcludes) { message.append("\t- ").append(exclude).append(String.format("%n")); } throw new IllegalStateException(String.format( "The following classes could not be excluded because they are" + " not auto-configuration classes:%n%s", message)); }
4.2.8 filter方法追踪-根据spring.factories文件中的AutoConfigurationImportFilter 过滤类处理自动装配类(根据spring-autoconfigure-metadata.properties中的各个自动装配类的元数据)
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) { long startTime = System.nanoTime(); String[] candidates = StringUtils.toStringArray(configurations); boolean[] skip = new boolean[candidates.length]; boolean skipped = false; for (AutoConfigurationImportFilter filter : getAutoConfigurationImportFilters()) { invokeAwareMethods(filter); boolean[] match = filter.match(candidates, autoConfigurationMetadata); for (int i = 0; i < match.length; i++) { if (!match[i]) { skip[i] = true; skipped = true; } } } if (!skipped) { return configurations; } List<String> result = new ArrayList<>(candidates.length); for (int i = 0; i < candidates.length; i++) { if (!skip[i]) { result.add(candidates[i]); } } if (logger.isTraceEnabled()) { int numberFiltered = configurations.size() - result.size(); logger.trace("Filtered " + numberFiltered + " auto configuration class in "+ TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms"); } return new ArrayList<>(result); } // 获取spring.factories文件中的AutoConfigurationImportFilter 过滤类 protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class, this.beanClassLoader); } // private void invokeAwareMethods(Object instance) { if (instance instanceof Aware) { if (instance instanceof BeanClassLoaderAware) { ((BeanClassLoaderAware) instance).setBeanClassLoader(this.beanClassLoader); } if (instance instanceof BeanFactoryAware) { ((BeanFactoryAware) instance).setBeanFactory(this.beanFactory); } if (instance instanceof EnvironmentAware) { ((EnvironmentAware) instance).setEnvironment(this.environment); } if (instance instanceof ResourceLoaderAware) { ((ResourceLoaderAware) instance).setResourceLoader(this.resourceLoader); } } }
4.2.9 fireAutoConfigurationImportEvents方法追踪-根据spring.factories文件中的AutoConfigurationImportListener事件监听器发布并处理监听事件
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) { List<AutoConfigurationImportListener> listeners = getAutoConfigurationImportListeners(); if (!listeners.isEmpty()) { AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions); for (AutoConfigurationImportListener listener : listeners) { invokeAwareMethods(listener); listener.onAutoConfigurationImportEvent(event); } } } protected List<AutoConfigurationImportListener> getAutoConfigurationImportListeners() { return SpringFactoriesLoader.loadFactories(AutoConfigurationImportListener.class, this.beanClassLoader); } # Auto Configuration Import Listeners org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\ org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener class ConditionEvaluationReportAutoConfigurationImportListener implements AutoConfigurationImportListener, BeanFactoryAware { private ConfigurableListableBeanFactory beanFactory; @Override public void onAutoConfigurationImportEvent(AutoConfigurationImportEvent event) { if (this.beanFactory != null) { ConditionEvaluationReport report = ConditionEvaluationReport.get(this.beanFactory); report.recordEvaluationCandidates(event.getCandidateConfigurations()); report.recordExclusions(event.getExclusions()); } } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = (beanFactory instanceof ConfigurableListableBeanFactory)? (ConfigurableListableBeanFactory) beanFactory : null; }
4.2.10 StringUtils.toStringArray(configurations)
返回经过过滤之后的自动装配类的全类名信息列表
4.2.11 AutoConfigurationImportSelector初始化与Spring交互
参照此位仁兄博文 : https://blog.csdn.net/shiyan719902675/article/details/106378107