springboot自动装配@Import原理

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

@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的使用,之后的内容方便理解。

https://www.cnblogs.com/zhoading/p/12194960.html

@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元数据

https://blog.csdn.net/f641385712/article/details/88765470

  • spring的bean注册中心

https://blog.csdn.net/f641385712/article/details/89518940

  • @Import注解的使用

https://www.cnblogs.com/zhoading/p/12194960.html

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值