Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@EnableAutoConfiguration 的作用 #306

Open
Bpazy opened this issue Oct 9, 2023 · 4 comments
Open

@EnableAutoConfiguration 的作用 #306

Bpazy opened this issue Oct 9, 2023 · 4 comments

Comments

@Bpazy
Copy link
Owner

Bpazy commented Oct 9, 2023

@EnableAutoConfiguration 是一个加载 Starter 目录包之外的需要 Spring 自动生成 bean 对象(是否需要的依据是 META-INF/spring.factoriesorg.springframework.boot.autoconfigure.EnableAutoConfiguration 后面是有能找到那个 bean)的带有@Configuration 注解的类。一般就是对各种引入的 spring-boot-starter 依赖包指定的(spring.factories)类进行实例化。

整体流程:

  1. 我们启动类中会加上 @SpringBootApplication
  2. @SpringBootApplication 包含了 @EnableAutoConfiguration,告知应用启动的时候需要扫描依赖包中需要实例化的类
  3. Springboot 启动的时候会去扫描 META-INF/spring.factories,查看具体是哪些类需要实例化
  4. 扫描哪些需要实例化的类,看下是否有 @Configuration 注解,如果有,则实例化
  5. 实例化的时候可能需要一些配置属性,一般这些类都会加上 @EnableConfigurationProperties(RocketMQProperties.class)
  6. RocketMQProperties 会将属性映射为 bean 类
@Bpazy
Copy link
Owner Author

Bpazy commented Oct 9, 2023

在Spring boot中,我们可以通过 @Configuration, @Service, @Component 等注解生成对象到 IOC 容器中。

未必所有实例化都是通过 @EnableAutoConfiguration, @Configuration, @Service, @Component 等注解,也可以通过其他方式(说白了怎么加载类的问题,因为实例化最终就是找到类,然后通过反射生成对象而已)。

其他实例化方式其实可以从 META-INF/spring.factories 中看出来,里面不仅有org.springframework.boot.autoconfigure.EnableAutoConfiguration,还有org.springframework.context.ApplicationContextInitializer(初始化的时候实例化),org.springframework.context.ApplicationListener(初始化监听的时候实例化),org.springframework.boot.diagnostics.FailureAnalyzer(应用启动失败分析类)等等。

但是对于这些实例化方式有个共同点,都需要在 META-INF/spring.factories 中配置。

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 9, 2023

默认自动装配的类

@EnableAutoConfiguration 所属的 jar 包 spring-boot-autoconfigure 下的 spring.factories 里有所有:

# 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.CloudAutoConfiguration,\
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.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
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.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
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.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.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.DeviceDelegatingViewResolverAutoConfiguration,\
org.springframework.boot.autoconfigure.mobile.SitePreferenceAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.ReactorAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.social.SocialWebAutoConfiguration,\
org.springframework.boot.autoconfigure.social.FacebookAutoConfiguration,\
org.springframework.boot.autoconfigure.social.LinkedInAutoConfiguration,\
org.springframework.boot.autoconfigure.social.TwitterAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 9, 2023

加载流程解析

@EnableAutoConfiguration 有注解 @Import(EnableAutoConfigurationImportSelector.class),其中的 EnableAutoConfigurationImportSelector 是关键实现,关键方法:

# org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		try {
			AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
					.loadMetadata(this.beanClassLoader);
			AnnotationAttributes attributes = getAttributes(annotationMetadata);
			List<String> configurations = getCandidateConfigurations(annotationMetadata,
					attributes);
			configurations = removeDuplicates(configurations);
			configurations = sort(configurations, autoConfigurationMetadata);
			Set<String> exclusions = getExclusions(annotationMetadata, attributes);
			checkExcludedClasses(configurations, exclusions);
			configurations.removeAll(exclusions);
			configurations = filter(configurations, autoConfigurationMetadata);
			fireAutoConfigurationImportEvents(configurations, exclusions);
			return configurations.toArray(new String[configurations.size()]);
		}
		catch (IOException ex) {
			throw new IllegalStateException(ex);
		}
	}

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

核心就在于 org.springframework.core.io.support.SpringFactoriesLoader#loadFactoryNames:

	public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
		String factoryClassName = factoryClass.getName();
		try {
			Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
					ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
			List<String> result = new ArrayList<String>();
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
				String factoryClassNames = properties.getProperty(factoryClassName);
				result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
			}
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
					"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

看代码可得出流程:

  1. 扫描 classpath 下的所有 META-INF/spring.factories 文件
  2. 获取 META-INF/spring.factories 文件中 key 为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有 value,就是这些: @EnableAutoConfiguration 的作用 #306 (comment)
  3. 因为 spring.factories 可以有很多,所以要去重
  4. 排序:先按 class name 字母正序排序、再按 @order 排序、最后处理 @AutoConfigureBefore @AutoConfigureAfter 注解排序
  5. 排除被 @EnableAutoConfiguration 排除掉的类(exclude, excludeName)
  6. 过滤,获取所有 AutoConfigurationImportFilter 实例指定的需要被过滤的过滤 class,具体逻辑点此:@EnableAutoConfiguration 的作用 #306 (comment)

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 9, 2023

AutoConfigurationImportFilter

提供给开发者的扩展接口,用于过滤 spring.factories 中自动配置类。

核心逻辑在 org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#filter 中:

	private List<String> filter(List<String> configurations,
			AutoConfigurationMetadata autoConfigurationMetadata) {
		long startTime = System.nanoTime();
		String[] candidates = configurations.toArray(new String[configurations.size()]);
		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<String>(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<String>(result);
	}

	protected List<AutoConfigurationImportFilter> getAutoConfigurationImportFilters() {
		return SpringFactoriesLoader.loadFactories(AutoConfigurationImportFilter.class,
				this.beanClassLoader);
	}

翻译一下,就是从 spring.factories 中获取 AutoConfigurationImportFilter 实例列表,然后依据具体实现类决定某个 EnableAutoConfiguration 对应的类是否应该被过滤掉。

应用场景

该能力一般用于用配置文件控制某些类是否会被自动初始化,比如:

public class MyConfigurationImportFilter implements AutoConfigurationImportFilter, EnvironmentAware {
 
    private Environment environment;
 
    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        boolean[] matches = new boolean[autoConfigurationClasses.length];
        for (int i = 0; i < matches.length; i++) {
            // 当读取到 MyAutoConfiguration 自动配置类时, 如果配置文件中 my-auto-configuration.enabled 为 true, 则加载该自动配置类
            matches[i] = MyAutoConfiguration.class.getName().equals(autoConfigurationClasses[i]) ? environment.getProperty("my-auto-configuration.enabled", Boolean.class, true) : true;
        }
        return matches;
    }
 
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

@Configuration
public class MyAutoConfiguration {
    @Bean
    public User user() {
        return new User();
    }
}

public class User {
    public String getName() {
        return "hello world";
    }
}


@EnableAutoConfiguration
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(Test.class, args);
        System.out.println(applicationContext.getBean(User.class).getName());
    }
}

如果配置文件中 my-auto-configuration.enabled 值为 true 时,则装载 MyAutoConfiguration 自动配置类。

然后在 resources/META-INF 下建立 spring.factories 文件:

# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
com.github.bpazy.example.autoconfigure.MyConfigurationImportFilter
 
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.bpazy.example.autoconfigure.MyAutoConfiguration

然后在 application.properties 中指定配置:

my-auto-configuration.enabled=true

运行 Application.main() 输出:

hello world

Refer: https://blog.csdn.net/qq_38644901/article/details/123555895

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant