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

目录

  • 前言
  • 一、什么是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{@Overridepublic void debug() {System.out.println("======log4j debug=========");}@Overridepublic void info() {System.out.println("======log4j info=========");}
}
public class Logback implements Log{@Overridepublic void debug() {System.out.println("======Logback debug=========");}@Overridepublic void info() {System.out.println("======Logback info=========");}
}
public class Slf4j implements Log{@Overridepublic void debug() {System.out.println("======Slf4j debug=========");}@Overridepublic 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);elseconfigs = 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.Logreturn (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 {@PostConstructpublic void init(){System.out.println("==============DeferredBean.init=================");}
}

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

public class DeferredImportSelectorDemo implements DeferredImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {System.out.println("============DeferredImportSelectorDemo.selectImports=================");//如果需要实例化,就要把该类的完整姓名返回return new String[]{DeferredBean.class.getName()};}/*** 返回group类* @return*/@Overridepublic 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*/@Overridepublic 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*/@Overridepublic 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类型就进入ifif (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);}}//处理deferredImportSelectorthis.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的生命周期初始化成BeanDefinitionhandler.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,这个类没有实现ImportSelectorprocessImports(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类型就进入ifif (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 {};//需要排除类的urlString[] 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和excludeNamesAnnotationAttributes attributes = getAttributes(annotationMetadata);//加载所有需要自动装配的urlList<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);//移除重复的urlconfigurations = 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 {@PostConstructpublic 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.mzph.cn/news/88343.shtml

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

相关文章

算法基础之二分查找

原题链接 一 、二分查找中的mid1和mid-1的问题 二分查找中的边界问题处理不好很容易导致死循环和计算错误的问题&#xff0c;以题目 数的范围为例。 题目大意 ​ 二分查找重复数第一次出现的位置和最后一次出现的位置。 数学含义 ​ 第一次位置即 找到 一个长度最大的 >X 区…

golang入门笔记——pprof性能分析

文章目录 简介runtime/pprof的使用命令行交互网络服务性能分析pprof与性能测试结合压测工具go-wrk 简介 golang性能分析工具pprof的8个指标 1.性能分析的5个方面&#xff1a;CPU、内存、I/O、goroutine&#xff08;协程使用情况和泄漏检查&#xff09;、死锁检测以及数据竟态…

医院电子病历编辑器,EMRE(EMR Editor)源码

电子病历主要面向医院机构医生、护士&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。本系统基于云端SaaS服务方式&#xff0c;通过浏览器方式访问和使用系统功能&#xff0c;提供电子病历在线制作、管理和使用的一体化电子病历解决方案&#xff0c;为医疗…

asisctf 2023 web hello wp

hello 开题&#xff0c;直接给了源码。 <?php /* Read /next.txt Hint for beginners: read curls manpage. */ highlight_file(__FILE__); $url file:///hi.txt; if(array_key_exists(x, $_GET) &&!str_contains(strtolower($_GET[x]),file) && !str_c…

TVP专家谈腾讯云 Cloud Studio:开启云端开发新篇章

导语 | 近日&#xff0c;由腾讯云 TVP 团队倾力打造的 TVP 吐槽大会第六期「腾讯云 Cloud Studio」专场圆满落幕&#xff0c;6 位资深的 TVP 专家深度体验腾讯云 Cloud Studio 产品&#xff0c;提出了直击痛点的意见与建议&#xff0c;同时也充分肯定了腾讯云 Cloud Studio 的实…

el-table 指定层级展开

先来看看页面默认全部展开时页面的显示效果&#xff1a;所有节点被展开&#xff0c;一眼望去杂乱无章&#xff01; 那么如何实现只展开指定的节点呢&#xff1f;最终效果如下&#xff1a;一眼看去很舒爽。 干货上代码&#xff1a; <el-table border v-if"refreshTabl…

ROS2 从头开始:第 5 部分 - 并发、执行器和回调组

一、说明 让我们回到基础。并发意味着系统或软件可以同时运行许多任务。例如,在单核 CPU 机器上,可以通过使用线程来实现并发。本文探讨了

基于边缘智能网关的储充一体电站管理方案

在“2030碳达峰&#xff0c;2060碳中和”的目标下&#xff0c;我国持续加快推进能源转型&#xff0c;扩大新能源占比&#xff0c;全国各地都在部署建设光伏、储能、新能源汽车充电等应用。随着新能源汽车的广泛普及&#xff0c;充电站、充电桩的需求快速增加&#xff0c;行业也…

分享从零开始学习网络设备配置--任务3.8 使用动态路由OSPF实现网络连通

任务描述 某公司随着规模的不断扩大&#xff0c;路由器的数量在原有的基础上有所增加。网络管理员发现原有的路由协议已经不适合现有的网络环境&#xff0c;可实施动态路由OSPF协议配置&#xff0c;实现网络中所有主机之间互相通信。因为动态路由OSPF协议可以实现快速收敛&…

zabbix实现钉钉报警

首先钉钉创建一个团队 自定义关键词 查看zabbix-server脚本存放的位置&#xff1a; [rootcontrolnode ~]# grep ^AlertScriptsPath /etc/zabbix/zabbix_server.conf AlertScriptsPath/usr/lib/zabbix/alertscripts zabbix server设置 在配置文件书写脚本目录vim /etc/za…

Redis的softMinEvictableIdleTimeMillis和minEvictableIdleTimeMillis参数

背景&#xff1a; Redis的softMinEvictableIdleTimeMillis&#xff0c;minEvictableIdleTimeMillis是一个令人疑惑两个参数&#xff0c;特别是当它和minIdle组合起来时就更难理解了&#xff0c;本文就来梳理下他们的之间的关系 softMinEvictableIdleTimeMillis&#xff0c;mi…

最新AI写作系统ChatGPT源码/支持GPT4.0+GPT联网提问/支持ai绘画Midjourney+Prompt+MJ以图生图+思维导图生成

一、AI创作系统 SparkAi系统是基于很火的GPT提问进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT系统&#xff1f;小编这里写一个详细图文教程吧&#x…

华为乾坤区县教育安全云服务解决方案(1)

华为乾坤区县教育安全云服务解决方案&#xff08;1&#xff09; 课程地址方案背景客户痛点分析区县教育网概述区县教育网业务概述区县教育网业务安全风险分析区县教育网安全运维现状分析区县教育网安全建设痛点分析 安全解决方案功能概述架构概述方案架构设备选型 课程地址 本…

系统架构设计师(第二版)学习笔记----软件工程

【原文链接】系统架构设计师&#xff08;第二版&#xff09;学习笔记----软件工程 文章目录 一、软件工程1.1 软件危机的表现1.2 软件工程的内容 二、软件过程模型2.1 软件的声明周期2.2 瀑布模型2.3 瀑布模型的缺点2.4 原型模型2.5 原型模型开发阶段2.6 开发原型的途径2.7 螺旋…

OpenCV实现FAST算法角点检测 、ORB算法特征点检测

目录 1 Fast算法 1.1 Fast算法原理 1.2 实现办法 1.2.1 机器学习的角点检测器 1.2.2 非极大值抑制 1.3 代码实现 1.4 结果展示 2 &#xff0c;ORB算法 2.1代码实现 2.2 结果展示 1 Fast算法 1.1 Fast算法原理 1.2 实现办法 1.2.1 机器学习的角点检测器 1.2.2 …

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(三)

目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 数据增强3. 模型构建4. 模型训练及保存1&#xff09;模型训练2&#xff09;模型保存 5. 模型评估 相关其它博客工程源代码下载其它资料下载 前言 本项目依赖于Keras深度学习模型&#xff0c;旨在对…

Mybatis-Flex框架初体验

本篇文章内容主要包括&#xff1a; MyBatis-Flex 介绍 MyBatis-Flex 是一个优雅的 MyBatis 增强框架&#xff0c;它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用Mybaits-Flex链接任何数据库&#xff0c;其内置的QueryWrapper亮点帮助我们极大的减少了SQL编写的工…

企业虚拟化KVM的三种安装方式(1、完全文本2、模板镜像+配置文件3、gustos图形方式部署安装虚拟机)

一、安装完虚拟机后的操作 第一步: 第二步&#xff1a;分配的内存大一下&#xff0c;处理器多些 第三步&#xff1a;打开虚拟化 打开虚拟机、安装KVM 一般企业如果使用kvm虚拟化平台&#xff0c;都会把物理服务器装成Centos的操作系统&#xff0c;然后装上kvm&#xff0c;创建…

crypto:摩丝

题目 根据题目所给的压缩包下载后解压&#xff0c;打开文本提示 摩斯密码&#xff0c;对照表可解码得到flag

时序预测 | MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测

时序预测 | MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测 目录 时序预测 | MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 MATLAB实现NGO-GRU北方苍鹰算法优化门控循环单元时间序列预测&#…