springboot源码解析(二)自动配置
前言
本文主要从源码角度分析springboot如何进行自动配置的。
springboot中的自动装配
springboot最核心的一点
特性:零xml、自动装配、内嵌tomcat。spring可以利用servlet3.0实现零xml和内嵌tomcat,但是自动装配实现不了
首先我们需要弄清楚自动装配做了什么,我先举个例子:
比如:
在单纯的spring项目中,我们开启AOP的方式是这样:
先在pom.xml中导入AOP的坐标(引入相关依赖)
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> </dependency>
然后,在配置来中开启AOP,然后编写相关类来实现逻辑
@Configuration @ComponentScan("com.example") @EnableAspectJAutoProxy // 这里开启AOP public class AppConfig { }
如果在springboot中,启用AOP
我们只需要在pom.xml加入相关依赖就行,然后直接使用就可以
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
1. 自定义自动装配
在下面目录创建 resources/META-INF/spring.factories文件
# 里面添加如下内容,key固定,value自己写类 # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.demo.config.MyAutoConfiguration
编写
MyAutoConfiguration
配置类@Configuration // 可以自定义加载的条件,可以说必须吧,因为一般也是满足什么条件才加载。 // 比如:@ConditionalOnClass(DispatcherServlet.class),要存在这个类才加载 @Conditional(MyCondition.class) // 会先加载这个,配置类里面加载相关设置。当然不是必须 @EnableConfigurationProperties(MyProperties.class) public class MyAutoConfiguration { // 赋值后,后面可以来使用 private final MyProperties myProperties; public MyAutoConfiguration(MyProperties properties) { this.myProperties = properties; } // ...todo }
编写配置类和条件类
MyProperties类
@ConfigurationProperties(prefix = "my", ignoreUnknownFields = true) public class MyProperties { private String name; private String password; }
MyCondition类
public class MyCondition implements Condition { // 返回true才会加载。 // 这个方法里面写相关的逻辑,来返回true 或 false public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { // ...todo return false; } }
自定义一个自动装配已经完成,下面进入源码分析了。
2. 源码解析
从springboot项目的启动类开始,
App类
@SpringBootApplication public class App { public static void main(String[] args) { // SpringApplication.run(App.class); SpringApplication application = new SpringApplication(App.class); application.run(args); } }
点击
@SpringBootApplication注解
点击@EnableAutoConfiguration注解
这里面用了 @Import注解,这里是利用spring中的扩展方式之一,后面可能会将spring中主要的扩展方式
@AutoConfigurationPackage // 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值 @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
进入
AutoConfigurationImportSelector类
这里利用了@Import扩展方式的特点,通过全限定类名,加载出类,然后放入到spring容器中
里面重要的方法:
selectImports()方法
@Override public String[] selectImports(AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return NO_IMPORTS; } AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader .loadMetadata(this.beanClassLoader); // 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值 AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); // 最后在这个方法的返回值使用,然后spring内部,会通过全限定类名的字符串加载出类,然后放入到spring容器中 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); }
getAutoConfigurationEntry()方法
只要用的是 AutoConfigurationEntry类中的configurations属性,里面保存这需要被加载的类的全限定类名的集合
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
// 剔除一些配置,比如:因为项目中并没有加入相关的依赖(rabbitMQ),所以也不能创建对象
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
getCandidateConfigurations()方法
返回所有的key为 EnableAutoConfiguration 的所有类的全限定类名
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 这里面又是从 spring.factories配置文件中拿 key为 EnableAutoConfiguration.class 的所有value值
// getSpringFactoriesLoaderFactoryClass()方法返回的是 EnableAutoConfiguration.class
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;
}
SpringFactoriesLoader类
里面的 loadSpringFactories()方法返回,key为EnableAutoConfiguration的所有在spring.factories文件中的value值
public final class SpringFactoriesLoader { /** * The location to look for factories. * <p>Can be present in multiple JAR files. */ // 这里就是要写在相关目录下的原因,这里会加载类路径下的META-INF目录下的 spring.factories文件 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) { // 这里首先尝试从cache中拿取,如果不为空,直接就返回 MultiValueMap<String, String> result = cache.get(classLoader); if (result != null) { return result; } try { // FACTORIES_RESOURCE_LOCATION 就是 "META-INF/spring.factories" 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 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 map中 result.add(factoryClassName, factoryName.trim()); } } } // 放入到cache中 cache.put(classLoader, result); return result; } // ...... } // ...... }
到这里,本篇博客也写的基本完成了,后面如果有需要可能会进行补充,如有错误,欢迎指出。又水了一篇,快乐。