SpringBoot自动装配原理及SPI拓展

2 篇文章 0 订阅

前言:SpringBoot作为新一代框架,极大简化了Spring应用的开发过程,成为市面上主流的开发框架。整个SpringBoot的核心其实就是自动装配,而自动装配所做的,就是自动将Bean装配到IOC容器中这么一个操作。

阅读本文章前,请务必了解 Maven Spring等相关技术,按需求自行创建SpringBoot应用


目录:

  1. @SpringBootApplication
  2. @EnableAutoConfiguration
  3. 总结
  4. spring.factories拓展

1.@SpringBootApplication

@SpringBootApplication
public class XimuApplication {

    public static void main(String[] args) {
        SpringApplication.run(XimuApplication.class, args);
    }

}

我们先来分析一下主启动类,发现有个叫@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 {

    @AliasFor(annotation = EnableAutoConfiguration.class)
    Class<?>[] exclude() default {};
    @AliasFor(annotation = EnableAutoConfiguration.class)
    String[] excludeName() default {};
//根据包路径扫描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};
//直接根据class类扫描
    @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};

}

@Target(ElementType.TYPE),@Retention(RetentionPolicy.RUNTIME),@Documented,@Inherited这四个基础注解本文不做赘述,请自行百度。

着重来看下面三个注解

@SpringBootConfiguration:顾名思义,这个实质就是Configuration配置类,相当于一个beans.xml文件,用到后面我们会见到各种各样的Configuration。

@ComponentScan:它的功能其实就是自动扫描并加载符合条件的组件或Bean定义,最终将这些bean定义加载到容器中。

@EnableAutoConfiguration:代表开启SpringBoot的自动装配,也是最重要的一个注解,接下来就会围绕它展开分析。


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

点开EnableAutoConfiguration又后发现了五个注解,上面四个与上一个注解依旧相同,来看看下面两个都是写啥东西。

@AutoConfigurationPackage:它负责保存标注相关注解的类的所在包路径。使用一个BasePackage类,保存这个路径。然后使用@Import注解将其注入到ioc容器中。这样,可以在容器中拿到该路径。

@Import(AutoConfigurationImportSelector.class):加载自动装配类,通过它@AutoConfiguration就可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前springboot创建并使用的IOC容器。

我们点进AutoConfigurationImportSelector.class继续往下看:

@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
//判断自动装配开关是否打开
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
//获取所有需要装配的bean
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}

这个类的内容比较多,我会挑些主要流程来写。

这里看到有一个方法叫做selectImports,能看出它调用了一个名为autoConfigurationEntry的方法,这个方法主要负责加载自动配置类的。跳转到autoConfigurationEntry分析。

protected AutoConfigurationEntry getAutoConfigurationEntry(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 = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

在这个方法中有一个叫做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;
	}

发现getCandidateConfigurations做了两件事,一个是非空断言,还有一个就是调用了loadFactoryNames方法,SpringFactoriesLoader属于Spring框架私有的一种扩展方案,其主要功能就是从指定的配置文件META-INF/spring.factories加载配置,即根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为查找的Key,获取对应的一组@Configuration类。说道这里可能还是不了解,来看看它做了什么。

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) {
		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 factoryTypeName = ((String) entry.getKey()).trim();
					for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
						result.add(factoryTypeName, factoryImplementationName.trim());
					}
				}
			}
			cache.put(classLoader, result);
			return result;
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
	}

loadFactoryNames返回了下面叫做的方法loadSpringFactories,其中的getResources方法指向获取的一个资源文件,进入这个常量:

public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";

于是到这里一切都拨云见雾了,关于spring.factories,我会在拓展简略说明


3.总结

@EnableAutoConfiguration作用就是从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。这些功能配置类要生效的话,会去classpath中找是否有该类的依赖类(也就是pom.xml必须有对应功能的jar包才行)并且配置类里面注入了默认属性值类,功能类可以引用并赋默认值。生成功能类的原则是自定义优先,没有自定义时才会使用自动装配类。

所以功能类能生效需要的条件:

1.spring.factories里面有这个类的配置类(一个配置类可以创建多个围绕该功能的依赖类

2.并且pom.xml里面需要有对应的jar包


4.spring.factories拓展

位于spring-boot-autoconfigure.jar下的/META-INF/spring.factories文件,其内容就是预制了非常多常用的Configuration,正是它们让我们免于大量的手写配置。他们通常长这样:

org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\

那我们看看这里面都写了些什么,以jdbc数据源的自动配置为例:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
    //这里是简略介绍,就不详细写了,了解用法即可
}

第四条注解,@EnableConfigurationProperties(DataSourceProperties.class),加载了一个为DataSourceProperties的类,这个应该就是我们需要的类,继续:

@ConfigurationProperties(prefix = "spring.datasource")
public class DataSourceProperties implements BeanClassLoaderAware, InitializingBean {

	private ClassLoader classLoader;

	private String name;

	private boolean generateUniqueName = true;

	private Class<? extends DataSource> type;

	private String driverClassName;

	private String url;

	private String username;

	private String password;

@ConfigurationProperties:直译过来就是配置属性的意思。

里面的参数prefix = "spring.datasource",这个用SpringBoot链接过数据库的可能就眼熟了。prefix前缀不就是写在yml或者factories里的参数前缀嘛,那下面的属性也显而易见了,在factories中设置它的用户名就是spring.datasource.username=*****;

其他配置同理。


end~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值