当前位置: 移动技术网 > IT编程>开发语言>Java > Spring Boot @EnableAutoConfiguration解析

Spring Boot @EnableAutoConfiguration解析

2019年08月10日  | 移动技术网IT编程  | 我要评论

刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包<context:component-scan base-package="xxx" /> 或者增加注解@componentscan({ "xxx"})。当时觉得挺urgly的,但也没有去研究有没有更好的方式。

直到接触spring boot 后,发现其可以自动引入二方包的bean。不过一直没有看这块的实现原理。直到最近面试的时候被问到。所以就看了下实现逻辑。

 

使用姿势

讲原理前先说下使用姿势。

在project a中定义一个bean。

package com.wangzhi;

import org.springframework.stereotype.service;

@service
public class dog {
}

 

并在该project的resources/meta-inf/下创建一个叫spring.factories的文件,该文件内容如下

org.springframework.boot.autoconfigure.enableautoconfiguration=com.wangzhi.dog

 

然后在project b中引用project a的jar包。

projecta代码如下:

package com.wangzhi.springbootdemo;

import org.springframework.boot.springapplication;
import org.springframework.boot.autoconfigure.enableautoconfiguration;
import org.springframework.boot.autoconfigure.springbootapplication;
import org.springframework.context.configurableapplicationcontext;
import org.springframework.context.annotation.componentscan;

@enableautoconfiguration
public class springbootdemoapplication {

    public static void main(string[] args) {
        configurableapplicationcontext context = springapplication.run(springbootdemoapplication.class, args);
        system.out.println(context.getbean(com.wangzhi.dog.class));
    }

}

 

打印结果:

com.wangzhi.dog@3148f668

 

原理解析

总体分为两个部分:一是收集所有spring.factoriesenableautoconfiguration相关bean的类,二是将得到的类注册到spring容器中。

收集bean定义类

在spring容器启动时,会调用到autoconfigurationimportselector#getautoconfigurationentry

protected autoconfigurationentry getautoconfigurationentry(
        autoconfigurationmetadata autoconfigurationmetadata,
        annotationmetadata annotationmetadata) {
    if (!isenabled(annotationmetadata)) {
        return empty_entry;
    }
    // enableautoconfiguration注解的属性:exclude,excludename等
    annotationattributes attributes = getattributes(annotationmetadata);
    // 得到所有的configurations
    list<string> configurations = getcandidateconfigurations(annotationmetadata,
            attributes);
    // 去重
    configurations = removeduplicates(configurations);
    // 删除掉exclude中指定的类
    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会调用到方法loadfactorynames:

public static list<string> loadfactorynames(class<?> factoryclass, @nullable classloader classloader) {
        // factoryclassname为org.springframework.boot.autoconfigure.enableautoconfiguration
        string factoryclassname = factoryclass.getname();
        // 该方法返回的是所有spring.factories文件中key为org.springframework.boot.autoconfigure.enableautoconfiguration的类路径
        return loadspringfactories(classloader).getordefault(factoryclassname, collections.emptylist());
    }


public static final string factories_resource_location = "meta-inf/spring.factories";

private static map<string, list<string>> loadspringfactories(@nullable classloader classloader) {
        multivaluemap<string, string> result = cache.get(classloader);
        if (result != null) {
            return result;
        }

        try {
            // 找到所有的"meta-inf/spring.factories"
            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类似于hashmap,包含了属性的key和value
                properties properties = propertiesloaderutils.loadproperties(resource);
                for (map.entry<?, ?> entry : properties.entryset()) {
                    string factoryclassname = ((string) entry.getkey()).trim();
                    // 属性文件中可以用','分割多个value
                    for (string factoryname : stringutils.commadelimitedlisttostringarray((string) entry.getvalue())) {
                        result.add(factoryclassname, factoryname.trim());
                    }
                }
            }
            cache.put(classloader, result);
            return result;
        }
        catch (ioexception ex) {
            throw new illegalargumentexception("unable to load factories from location [" +
                    factories_resource_location + "]", ex);
        }
    }

 

注册到容器

在上面的流程中得到了所有在spring.factories中指定的bean的类路径,在processgroupimports方法中会以处理@import注解一样的逻辑将其导入进容器。

public void processgroupimports() {
    for (deferredimportselectorgrouping grouping : this.groupings.values()) {
        // getimports即上面得到的所有类路径的封装
        grouping.getimports().foreach(entry -> {
            configurationclass configurationclass = this.configurationclasses.get(
                    entry.getmetadata());
            try {
                // 和处理@import注解一样
                processimports(configurationclass, assourceclass(configurationclass),
                        assourceclasses(entry.getimportclassname()), false);
            }
            catch (beandefinitionstoreexception ex) {
                throw ex;
            }
            catch (throwable ex) {
                throw new beandefinitionstoreexception(
                        "failed to process import candidates for configuration class [" +
                                configurationclass.getmetadata().getclassname() + "]", ex);
            }
        });
    }
}

private void processimports(configurationclass configclass, sourceclass currentsourceclass,
            collection<sourceclass> importcandidates, boolean checkforcircularimports) {
    ...
    // 遍历收集到的类路径
    for (sourceclass candidate : importcandidates) {
       ...
        //如果candidate是importselector或importbeandefinitionregistrar类型其处理逻辑会不一样,这里不关注
         // candidate class not an importselector or importbeandefinitionregistrar ->
                        // process it as an @configuration class
                        this.importstack.registerimport(
                                currentsourceclass.getmetadata(), candidate.getmetadata().getclassname());
        // 当作 @configuration 处理            
        processconfigurationclass(candidate.asconfigclass(configclass));
   ...
}
            
    ...
}

 

可以看到,在第一步收集的bean类定义,最终会被以configuration一样的处理方式注册到容器中。

end

@enableautoconfiguration注解简化了导入了二方包bean的成本。提供一个二方包给其他应用使用,只需要在二方包里将对外暴露的bean定义在spring.factories中就好了。对于不需要的bean,可以在使用方用@enableautoconfigurationexclude属性进行排除。

 

本人免费整理了java高级资料,涵盖了java、redis、mongodb、mysql、zookeeper、spring cloud、dubbo高并发分布式等教程,一共30g,需要自己领取。
传送门:https://mp.weixin.qq.com/s/jzddfh-7ynudmkjt0irl8q

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网