spring boot 自动配置功能解剖

    说起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文件一样的道理)

这样之后就可以实现了

到此为止大部分该讲的也都讲完了,如果有什么不对的地方,可以提出来讨论

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值