说起spring boot和spring的区别,大家第一反应就是spring boot 少了很多配置,但不是说少了很多配置很多功能就没有了,或者比spring就少了很多功能,而是spring boot 自己约定了很多默认配置,让大家感觉不到其中的一些信息
问题来了,spring boot的自动配置怎么实现的?今天我将自己这2天学习到的东西分享一下,有不对的地方可以说出来一起讨论
首先我们先看一下下面这个注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
看到@import那个注解了吗?这个注解就是会把第三方jar的类加载到当前spring容器,接下来我们看一下import的这个类,源码如下
@Deprecated
public class EnableAutoConfigurationImportSelector extends AutoConfigurationImportSelector {
public EnableAutoConfigurationImportSelector() {
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return this.getClass().equals(EnableAutoConfigurationImportSelector.class) ? ((Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true)).booleanValue() : true;
}
}
下面是父类AutoConfigurationImportSelector 中关键2个方法的源码
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
//判断 enableautoconfiguration注解有没有开启,默认开启
return NO_IMPORTS;
} else {
try {
//第一部分 :获取 META-INF/spring-autoconfigure-metadata.properties 的配置数据
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//第二部分 :获取 META-INF/spring.factoies 的类相关数据
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
//去重
configurations = this.removeDuplicates(configurations);、
//排序
configurations = this.sort(configurations, autoConfigurationMetadata);
//第三部分:去除一些多余的类,根据EnableAutoConfiguration 注解的一个exclusions属性
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//第四部:根据OnClassCondition注解过滤调一些条件没有满足的
configurations = this.filter(configurations, autoConfigurationMetadata);
//第五部:广播AutoConfigurationImportEvents事件(最下面有详解)
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return (String[])configurations.toArray(new String[configurations.size()]);
} catch (IOException var6) {
throw new IllegalStateException(var6);
}
}
}
protected boolean isEnabled(AnnotationMetadata metadata) {
return true;
}
首先程序是先调用EnableAutoConfigurationImportSelector 的selectImport方法,也就是父类的这个方法,下面我们先研究一下代码
大家看上面源码的第一部分,获取spring-autoconfigure-metadata.properties的代码,深究进去的代码如下
public static AutoConfigurationMetadata loadMetadata(ClassLoader classLoader) {
return loadMetadata(classLoader, "META-INF/spring-autoconfigure-metadata.properties");
}
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((URL)urls.nextElement())));
}
return loadMetadata(properties);
} catch (IOException var4) {
throw new IllegalArgumentException("Unable to load @ConditionalOnClass location [" + path + "]", var4);
}
}
ClassLoader.getResource()方法找到具有给定名称的资源。资源是一些数据(图像,音频,文本等),返回URL对象读取资源。
该方法就是获取该目录下的配置数据
第二部分:道理跟第一部分一样获取相关类的数据
第三步就是去除一些不用的class,这是具体过滤的代码
private List<String> filter(List<String> configurations, AutoConfigurationMetadata autoConfigurationMetadata) {
long startTime = System.nanoTime();
String[] candidates = (String[])configurations.toArray(new String[configurations.size()]);
boolean[] skip = new boolean[candidates.length];
boolean skipped = false;
Iterator var8 = this.getAutoConfigurationImportFilters().iterator();
while(var8.hasNext()) {
AutoConfigurationImportFilter filter = (AutoConfigurationImportFilter)var8.next();
this.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;
} else {
List<String> result = new ArrayList(candidates.length);
int numberFiltered;
for(numberFiltered = 0; numberFiltered < candidates.length; ++numberFiltered) {
if (!skip[numberFiltered]) {
result.add(candidates[numberFiltered]);
}
}
if (logger.isTraceEnabled()) {
numberFiltered = configurations.size() - result.size();
logger.trace("Filtered " + numberFiltered + " auto configuration class in " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
}
return new ArrayList(result);
}
}
AutoConfigurationImportFilter 是一个接口,OnClassCondition才是它的实现类,主要功能就是第二部加载的类中不是所有都是要加载的,spring boot 提供了很多条件注解,具体如下
@ConditionalOnClass : classpath中存在该类时起效
@ConditionalOnMissingClass : classpath中不存在该类时起效
@ConditionalOnBean : DI容器中存在该类型Bean时起效
@ConditionalOnMissingBean : DI容器中不存在该类型Bean时起效
@ConditionalOnSingleCandidate : DI容器中该类型Bean只有一个或@Primary的只有一个时起效
@ConditionalOnExpression : SpEL表达式结果为true时
@ConditionalOnProperty : 参数设置或者值一致时起效
@ConditionalOnResource : 指定的文件存在时起效
@ConditionalOnJndi : 指定的JNDI存在时起效
@ConditionalOnJava : 指定的Java版本存在时起效
@ConditionalOnWebApplication : Web应用环境下起效
@ConditionalOnNotWebApplication : 非Web应用环境下起效
以上注解都是元注解@Conditional
演变而来的,过滤调一些没有满足条件的class
第五步:广播事件
private void fireAutoConfigurationImportEvents(List<String> configurations, Set<String> exclusions) {
// 通过SpringFactoriesLoader.loadFactories()方法获取所有实现
// AutoConfigurationImportListener的实例化对象
List<AutoConfigurationImportListener> listeners = this.getAutoConfigurationImportListeners();
if (!listeners.isEmpty()) {
// 生成一个Even事件,给listener发送
AutoConfigurationImportEvent event = new AutoConfigurationImportEvent(this, configurations, exclusions);
Iterator var5 = listeners.iterator();
while(var5.hasNext()) {
AutoConfigurationImportListener listener = (AutoConfigurationImportListener)var5.next();
// 如果实现了或者继承了一些Aware,则设置相应的值。这个大家可以去百度Aware
this.invokeAwareMethods(listener);
// 给AutoConfigurationImportListener监听器发送事件
listener.onAutoConfigurationImportEvent(event);
}
}
}
关键的一个问题来了,回到源头,项目启动之后什么时候会去执行AutoConfigurationImportSelector的selectImports方法?
查了一下资料,其实spring boot是从我们SpringApplication.run方法开始最终执行最终执行到selectImports方法,然后将selectImports方法得到的数据注入到容器里面
springApplication.run----->refreshContext()----->AbstractApplicationContext.refresh---->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors----->ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry----------->ConfigurationClassPostProcessor.processConfigBeanDefinitions------>ConfigurationClassParser.parse
最终到如下代码
private void processDeferredImportSelectors() {
List<ConfigurationClassParser.DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
Iterator var2 = deferredImports.iterator();
while(var2.hasNext()) {
ConfigurationClassParser.DeferredImportSelectorHolder deferredImport = (ConfigurationClassParser.DeferredImportSelectorHolder)var2.next();
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
//执行导入自动化数据配置
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
//处理这些数据,注入容器
this.processImports(configClass, this.asSourceClass(configClass), this.asSourceClasses(imports), false);
} catch (BeanDefinitionStoreException var6) {
throw var6;
} catch (Throwable var7) {
throw new BeanDefinitionStoreException("Failed to process import candidates for configuration class [" + configClass.getMetadata().getClassName() + "]", var7);
}
}
}
好了,这个如何注入的问题是解决了,spring boot的自动化注入大致讲解完了,
现在又有一个问题了,如果自己要做一个类似于第三方的jar,让当前的容器加载我这个第三方的bean怎么做呢?其实也是非常简单。
第一步:肯定是新建一个spring boot项目A
第二步:定义类
public class people{
}
@Configuration
public class MyConfig {
@Bean
public People people (){
return new People();
}
}
第三步:将A的jar放到B中
第四部:在B中写如下测试代码,并启动
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
ConfigurableApplicationContext context =SpringApplication.run(Application.class,args);
People people = context.getBean(People .class);
System.out.println(people );
}
}
不过这样启动会报错,找不到这个类,那是因为你的第五步没有做
第五步:在core-bean项目resource下新建文件夹META-INF,在文件夹下面新建spring.factories文件,文件中配置,key为自定配置类EnableAutoConfiguration的全路径,value是配置类的全路径(就上面分析spring boot自动配置的那个spring.factories文件一样的道理)
这样之后就可以实现了
到此为止大部分该讲的也都讲完了,如果有什么不对的地方,可以提出来讨论