springboot启动main方法中都会加@SpringBootApplication这个注解,就把这个注解作为入口,研究下springboot自动装配的原理。
springboot使用的版本号是2.2.1.RELEASE


文章目录

  • @SpringBootApplication
  • @EnableAutoConfiguration
  • @AutoConfigurationPackage
  • @Import
  • @Import的使用
  • @Import(AutoConfigurationPackages.Registrar.class)
  • 新建demo类
  • 原理分析
  • @Import(AutoConfigurationImportSelector.class)
  • 新建demo类
  • 原理分析
  • 深入研究源码原理
  • 扩展资料


@SpringBootApplication

@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 {
	//...
}

元注解的用法自行百度,网上教程很多。我们重点关注@EnableAutoConfiguration这个注解。首先看下@SpringBootConfiguration:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
	//...
}

@SpringBootConfiguration其实就是一个@Configuration配置注解。

@EnableAutoConfiguration

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

这里有两个注解@AutoConfigurationPackage@Import

@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

}

这里也用到了@Import注解,下面统一讲解下@Import注解。

@Import

@Import的使用

先理解@Import的使用,之后的内容方便理解。


@Import(AutoConfigurationPackages.Registrar.class)

public abstract class AutoConfigurationPackages {
		//...
		static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
				@Override
				public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
						register(registry, new PackageImport(metadata).getPackageName());
				}
				@Override
				public Set<Object> determineImports(AnnotationMetadata metadata) {
						return Collections.singleton(new PackageImport(metadata));
				}
		}
}

下面举个demo来讲解下这个Registrar的源码到底干了什么。

新建demo类

先模仿springboot新建几个demo类,同时附上对应于springboot中的类:

  • LoggerService -> 一个普通的bean
public class LoggerService {
	//...
}
  • LoggerMyRegistrar -> AutoConfigurationPackages.Registrar
public class LoggerMyRegistrar implements ImportBeanDefinitionRegistrar {
	    @Override
	    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		        Class beanClass = LoggerService.class;
		        RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(beanClass);
		        String beanName = StringUtils.uncapitalize(beanClass.getSimpleName());
		        registry.registerBeanDefinition(beanName, rootBeanDefinition);
		    }
}
  • EnableMyConfig -> AutoConfigurationPackage
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Import({LoggerMyRegistrar.class})
public @interface EnableMyConfig {
    	Class<?>[] exclude() default {};
}
  • EnableDemoMain -> 启动类
@SpringBootApplication
@EnableMyConfig
public class EnableDemoMain {
	    public static void main(String[] args) {
		        ConfigurableApplicationContext context = SpringApplication.run(EnableDemoMain.class,args);
		        System.out.println(context.getBean(LoggerService.class));
	    }
}
原理分析

LoggerService类上没有加任何的注解,但在main方法输出中,却能得到LoggerService这个Bean,原因就是LoggerMyRegistrar在起作用,这个类相对于springboot中的AutoConfigurationPackages.Registrar.class。具体流程如下:

  1. 启动类上加了@EnableMyConfig注解,EnableMyConfig@Import(LoggerMyRegistrar.class)引入LoggerMyResitrar这个类。
  2. LoggerMyResitrar实现ImportBeanDefinitionRegistrar接口中的registerBeanDefinitions方法。
    该方法的第一个参数是元数据,可参考这篇博客,详细了解下spring元数据,嫌麻烦的可以打个断点调试下看看该参数的具体值是多少。第二个参数是bean注册中心,详细可参考这篇博客,bean注册中心。
  3. registry.registerBeanDefinition(beanName, beanDefinition)最终通过这个方法注入需要的bean。
  4. 通过调试可知,源码中register(registry, new PackageImport(metadata).getPackageName())是把启动类所在的包下面的类都给注入。

@Import(AutoConfigurationImportSelector.class)

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
		//...
		@Override
		public String[] selectImports(AnnotationMetadata annotationMetadata) {
				if (!isEnabled(annotationMetadata)) {
						return NO_IMPORTS;
				}
				AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
				AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata);
				return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
		}
}

下面举个demo来讲解下这个ImportSelector的源码到底干了什么。

新建demo类

先模仿springboot新建几个demo类,同时附上对应于springboot中的类:

  • CacheService -> 一个普通的bean
public class CacheService {
	//...
}
  • CacheImportSelector -> AutoConfigurationImportSelector
public class CacheImportSelector implements ImportSelector {
	    @Override
	    public String[] selectImports(AnnotationMetadata annotationMetadata) {
	    		//可以加自定义判断逻辑
		        Map<String, Object> attributes = annotationMetadata.getAnnotationAttributes(EnableMyConfig.class.getName());
		        return new String[]{CacheService.class.getName()};
	    }
}
  • EnableMyConfig -> EnableAutoConfiguration
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
@Import({CacheImportSelector.class})
public @interface EnableMyConfig {
    	Class<?>[] exclude() default {};
}
  • EnableDemoMain -> 启动类
@SpringBootApplication
@EnableMyConfig
public class EnableDemoMain {
	    public static void main(String[] args) {
		        ConfigurableApplicationContext context = SpringApplication.run(EnableDemoMain.class,args);
		        System.out.println(context.getBean(CacheService.class));
	    }
}
原理分析

CacheService类上没有加任何的注解,但在main方法输出中,却能得到CacheService这个Bean,原因就是CacheImportSelector在起作用,这个类相对于springboot中的AutoConfigurationImportSelector。具体流程如下:

  1. 启动类上加了@EnableMyConfig注解,EnableMyConfig@Import(CacheImportSelector.class)引入CacheImportSelector这个类。
  2. CacheImportSelector实现ImportSelector接口中的selectImports方法。
    该方法的第一个参数是元数据,可参考这篇博客,详细了解下spring元数据,嫌麻烦的可以打个断点调试下看看该参数的具体值是多少。
  3. 方法返回需要注入的bean类全路径字符串数组。
深入研究源码原理
  • AutoConfigurationImportSelector实现的接口DeferredImportSelector
public interface DeferredImportSelector extends ImportSelector {
		//...
}
  • 最终实现的还是ImportSelector接口。
  • AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader)
final class AutoConfigurationMetadataLoader {
		protected static final String PATH = "META-INF/spring-autoconfigure-metadata.properties";
		
		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 = 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);
				}
		}
		//...
  • 可以查看下META-INF/spring-autoconfigure-metadata.properties配置文件:
//...
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration.ConditionalOnClass=
com.couchbase.client.java.Cluster,com.couchbase.client.java.CouchbaseBucket
//...
  • 文件内容很长,我随便选了一行,key的构成:自动配置的类全名.条件=值,其中条件ConditionalOnClass这个类应该很熟悉,不懂的可以百度springboot条件注解。可以查看下org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration源码,对照下配置文件中的内容方便理解:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ CouchbaseBucket.class, Cluster.class })
@Conditional(CouchbaseAutoConfiguration.CouchbaseCondition.class)
@EnableConfigurationProperties(CouchbaseProperties.class)
public class CouchbaseAutoConfiguration {
		//...
}
  • CouchbaseAutoConfiguration源码中用了@ConditionalOnClass({ CouchbaseBucket.class, Cluster.class })和配置文件中的内容一对比,刚好匹配。
    这段代码的意思是:注入配置文件META-INF/spring-autoconfigure-metadata.properties中满足条件的Bean。当然自己可以对该文件进行扩展,在项目resources文件夹下新建META-INF/spring-autoconfigure-metadata.properties配置文件,添加自己的内容,就可以扩展。
  • getAutoConfigurationEntry(autoConfigurationMetadata,annotationMetadata)
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);
}
  • 以上代码可以打断点调试,看看那些参数值分别是多少。重点看getCandidateConfigurations(annotationMetadata, attributes)
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.loadFactoryNames(EnableAutoConfiguration.class)
public final class SpringFactoriesLoader {
		public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
		//...
		public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
				String factoryTypeName = factoryType.getName();
				return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
		}

		private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
				//...
				try {
						Enumeration<URL> urls = (classLoader != null ?
								classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
								ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
						result = new LinkedMultiValueMap<>();
						//...
						return result;
				}
				catch (IOException ex) {
						//...
				}
		}
}
  • 里面用到了 META-INF/spring.factories这个配置文件,这个配置文件在spring-boot-autoconfigure-2.2.1.RELEASE.jar/META-INF/spring.factories,文件中找到org.springframework.boot.autoconfigure.EnableAutoConfiguration:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
...
  • 看到这,可能依然不是很明白。
    总结:通过META-INF/spring.factories文件里的配置,key为org.springframework.boot.autoconfigure.EnableAutoConfiguration,找到对应的值,把这些类注入。 当然META-INF/spring.factories该文件也是可以扩展的。SPI也是使用了类似的原理,数据库链接串就是用到了SPI,不懂的可自行百度。

扩展资料

  • spring元数据


  • spring的bean注册中心


  • @Import注解的使用