欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring Boot @EnableAutoConfiguration解析

程序员文章站 2022-07-05 14:18:45
刚做后端开发的时候,最早接触的是基础的spring,为了引用二方包提供bean,还需要在xml中增加对应的包 或者增加注解@ComponentScan({ "xxx"})。当时觉得挺urgly的,但也没有去研究 ......

刚做后端开发的时候,最早接触的是基础的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