Spring Boot自动装配原理超详细解析

news2024/11/19 6:34:09

目录

  • 前言
  • 一、什么是SPI?
    • 1. JDK中的SPI
    • 2. Spring中的SPI
      • 2.1 加载配置
      • 2.2 实例化
  • 二、@Import注解和ImportSelector是什么?
    • 1. 代码示例
    • 2. 过程解析
    • 3. 源码分析
  • 三、Spring Boot的自动装配
    • 1.源码分析
    • 2.代码示例
    • 3.Spring Boot自带的自动装配
  • 四、总结

前言

Spring Boot的自动装配原理是怎么样的?想必大家面试都会被问到这个问题,那么这篇文章将会带你们进入去了解spring boot的自动装配原理,在开始之前,我们先提几个问题,我们带着疑问去深究

  1. 为什么要自动装配
  2. 什么是自动装配
  3. 自动装配怎么实现

想必学过或者没学过的朋友都会带有这三个疑问,那么我们带着问题发车

友情提示,自己对着源码一步一步看会加深理解

一、什么是SPI?

1. JDK中的SPI

我们定义一个log接口,并且有三个实现类Log4j,Logback,Slf4J

public interface Log {

    void debug();

    void info();
}
public class Log4j implements Log{
    @Override
    public void debug() {
        System.out.println("======log4j debug=========");
    }

    @Override
    public void info() {
        System.out.println("======log4j info=========");
    }
}
public class Logback implements Log{
    @Override
    public void debug() {
        System.out.println("======Logback debug=========");
    }

    @Override
    public void info() {
        System.out.println("======Logback info=========");
    }
}
public class Slf4j implements Log{
    @Override
    public void debug() {
        System.out.println("======Slf4j debug=========");
    }

    @Override
    public void info() {
        System.out.println("======Slf4j info=========");
    }
}

然后在resource目录添加/META-INF/services目录,并且创建以Log接口全路径命名的文件,并把他的实现类全路径添加到文件中,如下所示

在这里插入图片描述

添加main方法,并运行

public static void main(String[] args) {
    ServiceLoader<Log> all = ServiceLoader.load(Log.class);
    Iterator<Log> iterator = all.iterator();


    while (iterator.hasNext()) {
        Log next = iterator.next();
        next.debug();
        next.info();
    }
}

在这里插入图片描述

我们可以看到我们通过ServiceLoader.load就可以将Log的所有实现类实例化

这里的实现原理我就简单略过下,因为这里不是重点,只是让大家有个印象

他在ServiceLoader.load中有一个常量,所以这就是为什么我们要在META-INF/services/这个路径下创建的原因

 private static final String PREFIX = "META-INF/services/";

而在他是一个懒加载的方式,他在迭代器的hasNext的时候加载配置

public boolean hasNext() {
    if (acc == null) {
        //加载配置
        return hasNextService();
    } else {
        PrivilegedAction<Boolean> action = new PrivilegedAction<Boolean>() {
            public Boolean run() { return hasNextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}
private boolean hasNextService() {
  	//此处省略
    String fullName = PREFIX + service.getName();
    if (loader == null)
        //加载配置
        configs = ClassLoader.getSystemResources(fullName);
    else
        configs = loader.getResources(fullName);
  	//此处省略
    return true;
}

在next()的时候初始化实例

public S next() {
    if (acc == null) {
        //初始化实例
        return nextService();
    } else {
        PrivilegedAction<S> action = new PrivilegedAction<S>() {
            public S run() { return nextService(); }
        };
        return AccessController.doPrivileged(action, acc);
    }
}
private S nextService() {
    //此处省略
    //反射获取class类型
    Class<?> c = null;
    c = Class.forName(cn, false, loader);
·	//newInstance创建实例
    S p = service.cast(c.newInstance());
     //此处省略
}

JDK的SPI大概就是这样实现的

2. Spring中的SPI

META-INF目录下添加spring.factories,结构如下,接口全路径+实现类,多个实现类用逗号隔开,换行用\

在这里插入图片描述

添加main方法,并运行

public static void main(String[] args) {
    List<Log> logs = SpringFactoriesLoader.loadFactories(Log.class,ClassUtils.getDefaultClassLoader());
    for (Log log : logs) {
        log.debug();
        log.info();
    }
}

结果如下,我们可以看到还是可以把实现类正常加载

在这里插入图片描述

我们看下原理实现

2.1 加载配置

进入SpringFactoriesLoader.loadFactories方法,其他啥也不用看,就看loadFactoryNames这个方法

public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
    //此处省略
    //获取需要创建的类名
    List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
    
    List<T> result = new ArrayList<>(factoryNames.size());
    //循环类名创建实例
    for (String factoryName : factoryNames) {
        //在instantiateFactory方法初始化实例
        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
    }
    //此处省略
}

获取所有类的实现类,并通过getOrDefault方法拿到传入的接口或者实现类

public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
    String factoryClassName = factoryClass.getName();
    //获取所有类的实现类,并通过getOrDefault方法拿到传入的接口或者实现类,此时的factoryClassName为com.sise.demo.log.Log
    return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}

这里东西不多,继续往下看loadSpringFactories方法就行

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的所有url路径,这里包括自己定义的,和扩展的
         //这里的url指例如D:/demo1/target/classes/META-INF/spring.factories,每一个jar里面的spring.factories
         // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
        Enumeration<URL> urls = (classLoader != null ?
                                 classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                                 ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
        
         //用于接收某个抽象类或者接口的所有实现类,例如{"log":["Log4j","Logback","Slf4j"]}
        result = new LinkedMultiValueMap<>();
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            //解析这个url下的所有配置,获取对应的属性
            //此时的Properties长这样{"log":"Log4j,Logback,Slf4j"}
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryClassName = ((String) entry.getKey()).trim();
                 //将Log4j,Logback,Slf4j拆分成一个数组,并循环
                for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                    //把所有实现类的类名添加到result中
                    result.add(factoryClassName, factoryName.trim());
                }
            }
        }
        //放入缓存
        cache.put(classLoader, result);
        //返回map对象
        return result;
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                                           FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
}

最终的result返回是长这样的

在这里插入图片描述

上面的源码我已经写了注释了,我们重新理下思路

  1. 找到所有的spring.factories文件

  2. 使用PropertiesLoaderUtils.loadPropertiesspring.factories所有属性解析

  3. StringUtils.commaDelimitedListToStringArray((String)entry.getValue());Properties的值解析成一个数组

  4. 通过for循环将这些类的全名称加进result中

  5. 通过getOrDefault方法拿到对应的抽象类或者接口,就是我们传入的那个类名

2.2 实例化

他在loadFactories方法中的instantiateFactory中实例化,如下图所示

在这里插入图片描述

在这里插入图片描述

我们可以看到,此时就是将我们实现的三个log类名循环实例化,并加进List中

我们 看下instantiateFactory,很明显能看到他是在这里直接newInstance创建实例

private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
    try {
        //获取Class实例
        Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
        if (!factoryClass.isAssignableFrom(instanceClass)) {
            throw new IllegalArgumentException("Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
        } else {
            //创建实例
            return ReflectionUtils.accessibleConstructor(instanceClass, new Class[0]).newInstance();
        }
    } catch (Throwable var4) {
        throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), var4);
    }
}

二、@Import注解和ImportSelector是什么?

在开始之前,我再提个问题,我们知道spring中我们可以通过xml或者注解实现初始化bean,但是这两种方式都是知道包名才能初始化的,那外部的jar我们不知道包名,像redis,rabbitmq那些我们是不知道他源码的包名是什么的,所以使用@ComponentScan也扫码不出来,也不可能由spring官方一个一个加过去,只能由第三方自己去实现,那么如何实现呢

1. 代码示例

创建一个简单的bean

public class DeferredBean {

    @PostConstruct
    public void init(){
        System.out.println("==============DeferredBean.init=================");
    }
}

创建一个DeferredImportSelectorDemo类,实现DeferredImportSelector接口,DeferredImportSelector是实现ImportSelector接口的,所以可以把DeferredImportSelector当做ImportSelector

public class DeferredImportSelectorDemo implements DeferredImportSelector {


    @Override
    public String[] selectImports(AnnotationMetadata annotationMetadata) {
        System.out.println("============DeferredImportSelectorDemo.selectImports=================");
        //如果需要实例化,就要把该类的完整姓名返回
        return new String[]{DeferredBean.class.getName()};
    }

    /**
     * 返回group类
     * @return
     */
    @Override
    public Class<? extends Group> getImportGroup() {
        return DeferredImportSelectorGroupDemo.class;
    }


    public static class DeferredImportSelectorGroupDemo implements DeferredImportSelector.Group{

       private final List<Entry> list=new ArrayList<>();
        /**
         * 收集需要实例化的类
         * @param annotationMetadata
         * @param deferredImportSelector
         */
        @Override
        public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
            System.out.println("============DeferredImportSelectorGroupDemo.process=================");
            String[] strings = deferredImportSelector.selectImports(annotationMetadata);
            for (String string : strings) {
                list.add(new Entry(annotationMetadata,string));
            }
        }

        /**
         * process收集的结果返回,返回结果必须包装成Entry对象
         * @return
         */
        @Override
        public Iterable<Entry> selectImports() {
            System.out.println("============DeferredImportSelectorGroupDemo.selectImports=================");
            return list;
        }
    }
}

创建一个配置类

@Component
//使用@Import注解导入DeferredImportSelectorDemo类
@Import(DeferredImportSelectorDemo.class)
public class ImportBean {
}

我们先把代码结果运行看看

在这里插入图片描述

我们可以看到最后bean是被加载到了,这里记录一下运行殊顺序

  1. DeferredImportSelectorGroupDemo.process
  2. DeferredImportSelectorDemo.selectImports
  3. DeferredImportSelectorGroupDemo.selectImports
  4. DeferredBean.init

2. 过程解析

  1. DeferredImportSelectors是一个spring的扩展接口,他会在spring生命周期里调用
  2. DeferredImportSelectors有两个实现方法,分别是selectImportsgetImportGroup,此时他直接执行selectImports,会先调用getImportGroup拿到一个DeferredImportSelector.Group对象
  3. 我们可以看到代码中有个内部类DeferredImportSelectorGroupDemo实现了DeferredImportSelector.Group,这也是一个DeferredImportSelectors的内部类,他也有两个方法实现,分别是processselectImports
  4. process方法中,我们需要拿到加载的bean的类名集合,封装成一个Entry对象
  5. 我们在process方法中调用了deferredImportSelector.selectImports(annotationMetadata)拿到需要加载bean的类名,此时的deferredImportSelector就是DeferredImportSelectorGroupDemo,所以DeferredImportSelectorGroupDemoselectImports是在这里我们手动调用的
  6. 然后底层会再调用DeferredImportSelector.GroupselectImports方法将list返回交给spring

过程有点绕,梳理一下,DeferredImportSelectorsDeferredImportSelector.Group都有个selectImports方法

  1. 先调用DeferredImportSelector.Groupprocess方法
  2. 在手动调用DeferredImportSelectorsselectImports方法
  3. 最后调用DeferredImportSelector.GroupselectImports方法

图解

idea屏幕不够大,大致方法都在这了

在这里插入图片描述

我猜看到这里,大家已经明白了?不,肯定还懵,因为我一开始也是这样哈哈,我这里说出大家的几个疑问

  1. 为什么在第三步要去手动调用一下,不直接new String[]

    答:没啥,看着优雅一点哈哈,分工明确

  2. 既然这样,为什么DeferredImportSelectors还要整个selectImports,然后又Group啥的这么复杂

    答:因为我们可以直接实现ImportSelectors接口,然后底层会直接调用ImportSelectorsselectImports,而实现了DeferredImportSelectors才会调用GroupselectImports,所以DeferredImportSelectorsselectImports需要保留

  3. 那既然有selectImports为什么还要有DeferredImportSelectors

    答: 那这里就要说下selectImportsDeferredImportSelectors的区别

    ImportSelector是在Spring容器初始化之前就会被调用的,而DeferredImportSelector则是在Spring容器初始化过程中被调用的

    简单来说ImportSelector 是立即执行,而 DeferredImportSelector是等所有的配置都加载完毕后再执行。

    所以相对于ImportSelector 来说,DeferredImportSelector会更加灵活,更适用于自动装配的场景

这就完了吗?

不,我们还有一个类ImportBean上面添加了一个@Import(DeferredImportSelectorDemo.class)这样的注解,并导入了DeferredImportSelectorDemo的class类型,为什么要这样做呢,因为spring底层扫到所有带有@Import注解的类,并解析

3. 源码分析

ps源码这里会比较复杂,我会带大家一步一步去解析,如果觉得枯燥的话可以直接看第三章

我们可以直接搜索到ConfigurationClassParser.processImports方法,具体怎么找到的我就简单说下

  1. SpringApplication.run方法进去,一直到具体实现
  2. 找到this.refreshContext(context)方法,进去refresh方法
  3. 直接到AbstractApplicationContextrefresh,然后进去invokeBeanFactoryPostProcessors方法
  4. 进入invokeBeanFactoryPostProcessors方法,进去之后再进入invokeBeanDefinitionRegistryPostProcessors方法
  5. 我们看到他调用了BeanDefinitionRegistryPostProcessorpostProcessBeanDefinitionRegistry方法,这是一个接口,我们看他的实现类ConfigurationClassPostProcessorpostProcessBeanDefinitionRegistry
  6. 再进去processConfigBeanDefinitions,这方法有点长,我们找下找到parser.parse(candidates)方法
  7. 一直parse进去,找到processConfigurationClass方法,进去之后点击doProcessConfigurationClass
  8. 然后核心代码来了processImports,就是在这里出现

上面过程是方便你们找源码跟踪,如果不想看前面流程的可以直接略过,直接看下面

这段代码就是@Imports注解的核心导入

processImports(configClass, sourceClass, getImports(sourceClass), true);

我们先来看getImports(sourceClass)方法

private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
    Set<SourceClass> imports = new LinkedHashSet<>();
    Set<SourceClass> visited = new LinkedHashSet<>();
    //收集包含Imports的类
    collectImports(sourceClass, imports, visited);
    return imports;
}

很明显的能看到,所有imports就是在这里加载的

在这里插入图片描述

我们再回过去看processImports方法

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

    //此处省略
    //如果为ImportSelector类型就进入if
    if (candidate.isAssignable(ImportSelector.class)) {  
        Class<?> candidateClass = candidate.loadClass();
        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        ParserStrategyUtils.invokeAwareMethods(
            selector, this.environment, this.resourceLoader, this.registry);
        //如果为DeferredImportSelector类型就进入handle方法
        if (selector instanceof DeferredImportSelector) {
            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
        }
        else {
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
            processImports(configClass, currentSourceClass, importSourceClasses, false);
        }
    }
    //此处省略
}

handle方法

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
    DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
        configClass, importSelector);
    if (this.deferredImportSelectors == null) {
        DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
        handler.register(holder);
        handler.processGroupImports();
    }
    //此时进入的是else方法,因为deferredImportSelectors是空的
    else {
        this.deferredImportSelectors.add(holder);
    }
}
  1. 先判断是否为ImportSelector类型
  2. 再判断是否为DeferredImportSelector类型,执行this.deferredImportSelectorHandler.handle方法
  3. 我们可以看到此时他并没有初始化bean,只是加进了deferredImportSelectors

那他在哪里调用呢,我们回到上面源码路径第七步那里有个parse方法

public void parse(Set<BeanDefinitionHolder> configCandidates) {
    for (BeanDefinitionHolder holder : configCandidates) {
        BeanDefinition bd = holder.getBeanDefinition();
        try {
            if (bd instanceof AnnotatedBeanDefinition) {
                //进入parse方法
                parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
                parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
            }
            else {
                parse(bd.getBeanClassName(), holder.getBeanName());
            }
        }
        catch (BeanDefinitionStoreException ex) {
            throw ex;
        }
        catch (Throwable ex) {
            throw new BeanDefinitionStoreException(
                "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
        }
    }

    //处理deferredImportSelector
    this.deferredImportSelectorHandler.process();
}

我们可以看到,在parse会调用一个this.deferredImportSelectorHandler.process()方法,我们进去看看

public void process() {
    List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
    this.deferredImportSelectors = null;
    try {
        //这里deferredImports不为空,因为this.deferredImportSelectors在上面this.deferredImportSelectorHandler.handle那里已经赋值
        if (deferredImports != null) {
            DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
            deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
            deferredImports.forEach(handler::register);
            //获取deferredImports需要加载的类并放入configurationClasses中,也就是配置里,然后会通过spring的生命周期初始化成BeanDefinition
            handler.processGroupImports();
        }
    }
    finally {
        this.deferredImportSelectors = new ArrayList<>();
    }
}

如何初始化成BeanDefinition我就不多叙述,这是spring生命周期的内容,我们进去processGroupImports继续看

public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
        //获取Group的entry并循环
        grouping.getImports().forEach(entry -> {
            ConfigurationClass configurationClass = this.configurationClasses.get(
                entry.getMetadata());
            try {
                //这个是之前加载DeferredImportSelector的方法,但是走的不是那个if,因为传入的是需要初始化的bean,也就是DeferredBean,这个类没有实现ImportSelector
                processImports(configurationClass, asSourceClass(configurationClass),
                               asSourceClasses(entry.getImportClassName()), false);
            }
        	//此处省略
        });
    }
}

这里有个grouping.getImports()再去forEach遍历,我们先看下getImports方法

public Iterable<Group.Entry> getImports() {
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        //这里先调用了group的process方法
        this.group.process(deferredImport.getConfigurationClass().getMetadata(),
                           deferredImport.getImportSelector());
    }
    //然后再调用group的selectImports方法
    return this.group.selectImports();
}

我们可以看到,group是先调用process方法再selectImports方法的,也验证了我们一开始运行的先后顺序

此时我们回到processGroupImports方法,他在forEach的时候将entry(也就是DeferredBean)传入了processImports方法

因为entry没有实现ImportSelector,所以会进入processImports的下一个判断

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
                            Collection<SourceClass> importCandidates, boolean checkForCircularImports) {

    //此处省略
    //如果为ImportSelector类型就进入if
    if (candidate.isAssignable(ImportSelector.class)) {  
        Class<?> candidateClass = candidate.loadClass();
        ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
        ParserStrategyUtils.invokeAwareMethods(
            selector, this.environment, this.resourceLoader, this.registry);
        //如果为DeferredImportSelector类型就进入handle方法
        if (selector instanceof DeferredImportSelector) {
            this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
        }
        else {
            String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
            Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
            processImports(configClass, currentSourceClass, importSourceClasses, false);
        }
        //因为此时传入的是DeferredBean,没有实现ImportSelector或者,所以下面的方法
    }
    else {
        this.importStack.registerImport(
            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
        //将需要实例化的类加入到配置类中
        processConfigurationClass(candidate.asConfigClass(configClass));
    }
    //此处省略
}

往下继续看processConfigurationClass方法

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
	//此处省略
    this.configurationClasses.put(configClass, configClass);
}

所以最后会将需要初始化的类加入到configurationClasses中,然后交给spring的生命周期去初始化

三、Spring Boot的自动装配

结合上两章我们重新理一下,我们现在知道两个重要的东西

  1. SPI,可以通过配置文件去拿到对应的类
  2. 使用spring的内置注解和接口,实现bean的初始化

那我们不是可以在DeferredImportSelector的实现类中,使用SPI将所有外部需要自动装配的bean都加载进来就可以了呢,我们去看下Spring Boot的源码验证一下我们的猜想

1.源码分析

我们知道Spring Boot的启动类,上面有个注解叫@SpringBootApplication,进去看看

//此处省略
@EnableAutoConfiguration
@//此处省略
public @interface SpringBootApplication {

	//此处省略

}

我把不必要的代码删掉,来看重点,这里有个@EnableAutoConfiguration,再点进去看看

//此处省略
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	//需要排除的类
	Class<?>[] exclude() default {};

    //需要排除类的url
	String[] excludeName() default {};

}

你看着不就来了@Import(AutoConfigurationImportSelector.class)AutoConfigurationImportSelector肯定就是实现DeferredImportSelector的,我们打开看看是怎么样的呢

在这里插入图片描述

在这里插入图片描述

是不是可以看到他不仅实现了一个DeferredImportSelector,也是有一个内部类实现了DeferredImportSelector.Group接口,是不是看着和我们第二章节写的一模一样


我们继续往下看,看到Groupprocess方法

public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
    Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
                 () -> String.format("Only %s implementations are supported, got %s",
                                     AutoConfigurationImportSelector.class.getSimpleName(),
                                     deferredImportSelector.getClass().getName()));
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
        .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
        this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
}

看着还是有点懵是不是,感觉不太像,没事,我们进去到他的getAutoConfigurationEntry方法,核心代码来啦

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
                                                           AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    }
    //获取注解属性exclude和excludeNames
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    //加载所有需要自动装配的url
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    //移除重复的url
    configurations = removeDuplicates(configurations);
    //获取需要排除的类
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    //校验排除类
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
}

这里就是自动装配的核心代码,我们看下加载所有url的方法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;
}

看到这里是不是很清晰了,他的配置都是从spring.factories这个文件读取的,通过SpringFactoriesLoader.loadFactoryNames这个就是Spring的SPI我们第一张讲过吧

SpringFactoriesLoader.loadFactoryNamesSpringFactoriesLoader.loadFactories一样,都是需要两个参数,一个是key和类加载器,在前面示例中,我们用的是Log.class,而这里用了个getSpringFactoriesLoaderFactoryClass()方法,我们进去看看

protected Class<?> getSpringFactoriesLoaderFactoryClass() {
    return EnableAutoConfiguration.class;
}

这里获取的是EnableAutoConfiguration的class类型,所以是不是我们只要在spring.factories加入这个EnableAutoConfiguration的类名和对应需要实例化的bean的url就可以了呢,我们来尝试一下

2.代码示例

创建一个TestBean,用于测试

public class TestBean {

    @PostConstruct
    public void init(){
        System.out.println("==============TestBean.init=================");
    }
}

spring.factories中添加以下代码

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.sise.demo.test.TestBean

运行代码

在这里插入图片描述

结果成功加载bean,到此整个spring boot的自动装配原理是不是已经很清晰来

3.Spring Boot自带的自动装配

后面我们来看看Spring Boot自带那些自动装配的类,这里就是闲聊的,与原理没啥关系

在这里插入图片描述

我们可以看到在Spring Boot中已经写好了很多自动装配的类,比如Aop,RabbitMq,Es,JDBC等等,大部分需要自动装配的类已经有了,但是如果再需要外部的jar实现自动装配,就像我们写的一样,自己去添加配置来完成

四、总结

我们回顾下发车之前的三个问题

  1. 为什么要自动装配?

    答:我们在加载第三方jar的时候,并不知道第三方的包名,所以要使用自动装配来帮我们来帮忙初始化第三方的bean

  2. 什么是自动装配?

    答:自动装配会通过扫描spring.factories下面的类自动初始化bean,自动配置大大简化了Spring应用的配置,如果没有自动装配我们就要自己去实现ImportSelector,

  3. 自动装配怎么实现?

    答:spring.factories文件中添加org.springframework.boot.autoconfigure.EnableAutoConfiguration对应需要初始化的bean的Url即可

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1032238.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

LabVIEW报表生成工具包时出现错误-41106

LabVIEW报表生成工具包时出现错误-41106 使用LabVIEW报表生成工具包创建Excel报告或Word文档时&#xff0c;收到以下错误&#xff1a;Error -41106 occurred at NI_Excel.lvclass:new report subVI.vi ->NI_report.lvclass:New Report.vi -> Export Report With JKI.vi …

【Vue】模块基本语法「上篇」

【Vue】模块基本语法「上篇」 一、插值1.1 文本1.2 v-html1.3数据双向绑定(v-model) 二、指令2.1 v-if|v-else|v-else-if2.2 v-show2.3 v-for2.4 动态参数 三、过滤器3.1 局部过滤器3.2 全局过滤器 四、计算属性&监听属性4.1 计算属性4.2 监听属性 五、案例实操5.1 购物车案…

怒刷LeetCode的第6天(Java版)

目录 第一题 题目来源 题目内容 解决方法 方法一&#xff1a;哈希表 方法二&#xff1a;逐个判断字符 方法三&#xff1a;模拟减法 第二题 题目来源 题目内容 解决方法 方法一&#xff1a;水平扫描法 方法二&#xff1a;垂直扫描法 方法三&#xff1a;分治法 方…

2023-09-22 LeetCode每日一题(将钱分给最多的儿童)

2023-09-22每日一题 一、题目编号 2591. 将钱分给最多的儿童二、题目链接 点击跳转到题目位置 三、题目描述 给你一个整数 money &#xff0c;表示你总共有的钱数&#xff08;单位为美元&#xff09;和另一个整数 children &#xff0c;表示你要将钱分配给多少个儿童。 你…

华为OD机试 - 事件推送(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、Java算法源码五、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多…

联合作战模式下的信息过滤方法

源自&#xff1a;《指挥信息系统与技术》 作者&#xff1a;马雷鸣&#xff0c;张道伟 “人工智能技术与咨询” 发布 摘要 引言 1 相关工作 2 基于虚词的信息过滤方法 图1 本文方法流程 2.1 云-边-端架构 图2 云-边-端架构 2.2 作战信息特征提取 图3 常用虚词表 2.3 …

数字藏品系统平台怎么样挣钱?

数字藏品系统平台是为用户提供数字艺术品、收藏品、虚拟物品等数字化资产的交易和管理服务的平台。这类平台通常有以下方式来挣钱&#xff1a; 1.手续费&#xff1a;平台可以在用户之间进行数字藏品的买卖交易时收取手续费。这通常是基于交易金额的一定比例或固定费用。这是数字…

华为全联接大会2023 | 尚宇亮:携手启动O3社区发布

2023年9月20日&#xff0c;在华为全联接大会2023上&#xff0c;华为正式发布“联接全球服务工程师&#xff0c;聚合用户服务经验”的知识经验平台&#xff0c;以“Online 在线、Open 开放、Orchestration 协同”为理念&#xff0c;由华为、伙伴和客户携手&#xff0c;共同构建知…

Mysql 按照每小时,每天,每月,每年,不存在数据也显示

文章目录 按照每小时按照每天按照每月 按照每小时 SELECTdate : date_add( date, INTERVAL 1 HOUR ) AS DAY FROM( SELECT date : DATE_ADD( 2023-09-22, INTERVAL - 1 HOUR ) FROM xt_user LIMIT 24 ) t按照每天 SELECTdate : date_add( date, INTERVAL 1 day ) AS DAY FRO…

量子计算基础知识—Part1

1.什么是量子计算机&#xff1f; 量子计算机是基于量子力学原理构建的机器&#xff0c;采用了一种新的方法来处理信息&#xff0c;从而使其具有超强的功能。量子计算机使用Qubits处理信息。 2. 什么是量子系统&#xff1f; 一个量子系统指的是由量子力学规则描述和控制的物理…

计算机视觉与深度学习-卷积神经网络-卷积图像去噪边缘提取-图像去噪 [北邮鲁鹏]

目录标题 参考学习链接图像噪声噪声分类椒盐噪声脉冲噪声对椒盐噪声&脉冲噪声去噪使用高斯卷积核中值滤波器 高斯噪声减少高斯噪声 参考学习链接 计算机视觉与深度学习-04-图像去噪&卷积-北邮鲁鹏老师课程笔记 图像噪声 噪声点&#xff0c;其实在视觉上看上去让人感…

2023-9-22 滑雪

题目链接&#xff1a;滑雪 #include <cstring> #include <algorithm> #include <iostream>using namespace std;const int N 310;int n, m; int h[N][N]; int f[N][N];int dx[4] {-1, 0, 1, 0}, dy[4] {0, 1, 0, -1};int dp(int x, int y) {int &v f…

启山智软/一款包含主流商城类型的一款电商中台系统100%开源

文章目录 介绍一、Smart Shop JAVA 微服务电商中台优势二、电商中台包含那些主流商城模式1.S2B2C供应链商城2.B2B2C多商户商城3.B2C单商户商城4.O2O外卖配送商城5.社区团购商城 6.演示地址总结 介绍 想要了解代码规范&#xff0c;学习商城解决方案&#xff0c;点击下方官网链接…

acwing算法基础-chapter01-差分

差分介绍 结论&#xff1a;差分是前缀和的逆运算 举例 一维差分 //一维前缀和 a[i]部分就是一维差分数组 s[i] s[i-1]a[i]; //一维差分 a[i] s[i]-s[i-1];二维差分 //二维前缀和 a[i][j]部分就是一维差分数组 s[i][j] s[i-1][j]s[i][j-1]-s[i-1][j-1]a[i][j]; //二维差分…

Cordova插件开发:集成南方测绘RTK实现高精度卫星定位

文章目录 1.最终效果预览2.页面持续展示获取的坐标3.公共类utilsTools中封装得获取坐标方法4.南方测绘坐标获取封装5.插件js方法封装6.Java方法封装1.最终效果预览 说明:南方测绘RTK设备厂家提供的SDK中封装了蓝牙搜索连接、Cross账号登录等功能,我们通过Cordova插件进一步封…

Python150题day09

③多条件分支 使用input函数接收用户的输入数据&#xff0c;如果用户输入python&#xff0c;则输出90&#xff0c;如果用户输入java.输出95&#xff0c;如果用户输入php&#xff0c;输出85&#xff0c;其他输入&#xff0c;程序输出0 解答&#xff1a; if...elif...else val…

centos 上安装 kafka 与 python 调用

step0: 环境准备 1、 安装jdk 1.8 以上版本 yum -y install java-1.8.0-openjdk.x86_642、 安装配置ZooKeeper wget https://dlcdn.apache.org/zookeeper/zookeeper-3.8.2/apache-zookeeper-3.8.2-bin.tar.gz --no-check-certificate tar -zxf apache-zookeeper-3.8.2-bin.t…

首购2元起!CDN与加速特惠专场来啦~

还在为内容分发、加速成本发愁吗&#xff1f;看过来&#xff01;火山引擎边缘云CDN与加速特惠专场来啦&#xff01; 限时活动&#xff1a;首购2元起&#xff0c;新老低至7折&#xff01; 限时优惠&#xff01;错过后悔&#xff01;这波折扣实实在在&#xff01; 首购专区 新…

用户体验测试:确保产品满足用户期望的关键步骤

在当今竞争激烈的市场中&#xff0c;为了确保产品的成功和用户的满意度&#xff0c;用户体验测试变得至关重要。通过系统化地评估产品在用户使用过程中的可用性、便利性和满意度&#xff0c;用户体验测试有助于发现潜在问题并改进产品设计。本文将介绍用户体验测试的关键步骤和…

有什么推荐使用的企业上网行为管理软件?

在当今信息化社会&#xff0c;企业的上网行为管理越来越重要。企业上网行为软件是一种能够监控和管理企业员工上网行为的工具&#xff0c;它可以帮助企业更好地管理网络资源&#xff0c;提高工作效率&#xff0c;保护企业信息安全&#xff0c;并符合相关的法律法规。本文将深入…