面试:Springboot自动装配及实现

自动装配介绍

  1. SpringBoot通过定义自动配置接口,通过SPI机制加载外部自定义的自动配置类实现自动装配功能.做到了让我们只需要极少的配置或简单的注解就可以使用所需要的功能,开箱即用.
  2. Springboot通过SPI方式实现了自动装配,它规定了一套接口规范和配置类信息加载规范 : Springboot启动时会将启动类路径下所有有@Configuration注解的类注入容器中并且也会将所有依赖包中META-INF/spring-factories里定义的EnableAutoConfiguration这个key中所有的类加载至容器中(其他Bean也会加载通过@ComponentScan注解)
  3. 可通过spring.boot.enableautoconfiguration=false关闭自动配置,默认是开启的

Springboot的自动装配实现讲解

自动装配功能可从@SpringApplication注解分析,该注解功能在Springboot启动流程有讲解,其主要依赖的是@EnableAutoConfiguration上的@AutoConfigurationPackage和@Import({AutoConfigurationImportSelector.class})注解

注解负责功能

@AutoConfigurationPackage负责加载启动类路径下的@Configuration修饰的类
@Import({AutoConfigurationImportSelector.class})负责加载所有依赖包中META-INF/spring-factories里定义的EnableAutoConfiguration这个key中的类

加载限制

但是并不是所有的类都会被加载,扫描会被扫描进去,但是不符合该配置类的加载条件就不会被加载,Springboot启动中还会有一个条件筛选的过程,具体是下面这些注解条件.

满足加载条件的会被加载
@ConditionalOnBean:当容器里有指定 Bean 的条件下
@ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
@ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
@ConditionalOnClass:当类路径下有指定类的条件下
@ConditionalOnMissingClass:当类路径下没有指定类的条件下
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnExpression:基于 SpEL 表达式作为判断条件
@ConditionalOnJava:基于 Java 版本作为判断条件
@ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
@ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
@ConditionalOnWebApplication:当前项目是 Web 项 目的条件下

AutoConfigurationImportSelector

AutoConfigurationImportSelector实现了DeferredImportSelect继承的ImportSelector的selectImports()方法.然后主要是在118行的getCandidateConfigurations()方法完成spring-factories文件读取要加载的类全路径名交由容器加载

public String[] selectImports(AnnotationMetadata annotationMetadata) {
    // 加载前先看注解元数据配置是否进行配置加载
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}
protected boolean isEnabled(AnnotationMetadata metadata) {
    return this.getClass() == AutoConfigurationImportSelector.class ? (Boolean)this.getEnvironment().getProperty("spring.boot.enableautoconfiguration", Boolean.class, true) : true;
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 通过SpringFactoriesLoader.loadFactoryNames()提供的SPI功能完成配置类的全路径读取
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.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;
}

SPI概念介绍

SPI 全称为 Service Provider Interface,SPI技术原理是将接口实现类的全限定名配置在文件中,由服务加载器读取配置文件,并加载实现类。这样可以在运行时,动态为接口替换实现类.Java本身有实现一套SPI机制,但Springboot也是自己实现了一个自己的SPI机制让这个配置变更方便些.

JavaSPI机制(ServiceLoader.load(Interface.class))

JavaSPI规定在META-INF/services文件夹下创建接口全路径类名文件,在该文件中将该接口实现类加入(可多个,一行一个最好).然后通过ServiceLoader.load实现这些实现类加载,并通过返回的ServiceLoader获取或者调用

//定义一个动物接口以及两种动物实现类
public interface Animal {
    void speak();
}

public class Cat implements Animal{
    public Cat() {
        System.out.println("小猫类被加载了");
    }
    @Override
    public void speak() {
        System.out.println("小猫喵喵");
    }
}
public class Dog implements Animal{
    public Dog() {
        System.out.println("小狗类被加载了");
    }
    @Override
    public void speak() {
        System.out.println("小狗汪汪");
    }
}
// SPI测试类
public class JavaSpiTest {
    /**
     * 学习查看JavaSPI机制
     */
    @Test
    public void speak(){
        ServiceLoader<Animal> serviceLoader = ServiceLoader.load(Animal.class);
        System.out.println("Java SPI Running");
        serviceLoader.forEach(Animal::speak);
    }
}


SpringbootSPI机制

定义自动装配类,并将其写入MEAT-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration下即可.
如果要做starter,则需要引入starter依赖,如下两个图做好配置后直接引入该项目依赖即可

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.1.7.RELEASE</version>
</dependency>


参考:

从Java SPI机制实现到Dubbo SPI扩展
手写实现基于Spring的SPI及自动装配
淘宝一面:“说一下 Spring Boot 自动装配原理呗?”

全部评论

相关推荐

04-29 18:58
已编辑
门头沟学院 C++
4.10&nbsp;投递4.14&nbsp;约面4.16&nbsp;一面(面完半小时通知二面)4.22&nbsp;二面(面完第二天约三面)4.25&nbsp;三面(面完第2天约HR面)4.29&nbsp;hr面(当天晚上oc)字节劳动节后希望立刻到岗&nbsp;许愿许愿...&nbsp;&nbsp;❤一面&nbsp;❤面试45分钟&nbsp;以简历内容为主1&nbsp;项目拷打&nbsp;&nbsp;&nbsp;&nbsp;介绍一下消息队列项目,&nbsp;&nbsp;&nbsp;为什么选择rabbitMQ,&nbsp;&nbsp;&nbsp;&nbsp;消息持久化存储是如何实现的&nbsp;&nbsp;&nbsp;消费者轮询介绍一下&nbsp;&nbsp;&nbsp;影响性能的主要因素,如何提升性能&nbsp;&nbsp;&nbsp;对高并发的理解&nbsp;&nbsp;&nbsp;&nbsp;是否会继续维护项目&nbsp;&nbsp;&nbsp;介绍一下高并发服务器项目&nbsp;&nbsp;&nbsp;...2&nbsp;八股&nbsp;&nbsp;&nbsp;&nbsp;进程间通信的方案&nbsp;&nbsp;&nbsp;&nbsp;介绍一下多线程&nbsp;&nbsp;&nbsp;&nbsp;介绍一下&nbsp;epoll&nbsp;与&nbsp;select&nbsp;&nbsp;&nbsp;&nbsp;...3&nbsp;代码&nbsp;删除倒数第n个节点&nbsp;大文件处理&nbsp;❤二面&nbsp;❤大概35分钟,过程很短,面试官很和蔼。主要是项目拷打&nbsp;,手撕了一道题&nbsp;时长有点短&nbsp;有些担心要被挂1&nbsp;项目拷打&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;消息队列的设计思路&nbsp;&nbsp;&nbsp;&nbsp;为什么选择RabbitMQ&nbsp;其他有了解吗2&nbsp;手撕&nbsp;&nbsp;&nbsp;&nbsp;寻找一个序列中的比左边都大&nbsp;比右边都小的&nbsp;元素索引,时间复杂度O(n)3&nbsp;开放性交流&nbsp;&nbsp;&nbsp;&nbsp;谈一谈你对C/C++的理解,以及常用的特性。3&nbsp;反问&nbsp;❤三面&nbsp;❤大概45分钟&nbsp;,压力面,问的很深捏&nbsp;,感觉要被刷了哎1.&nbsp;谈一谈项目&nbsp;消息队列怎么实现的&nbsp;内部细节&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;消息队列如何保证至少一次读取&nbsp;保证最多一次读取&nbsp;只读取一次,有没有考虑过消息的安全&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;消息队列有没有测试过性能,怎么样?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;高并发服务器有没有测试过,什么机器上测试的,有没有关注cpu占用&nbsp;,延迟...&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;了解的锁有哪些&nbsp;可以谈一谈具体使用情况吗?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;文件系统的底层有了解过吗?对于高并发读写时如何实现的?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;高并发服务器是如何实现高并发的,谈一谈具体的框架2&nbsp;手撕大数减法&nbsp;20分钟差一点细节写完3&nbsp;谈一谈我的情况&nbsp;可以实现几个月&nbsp;学校课程&nbsp;...&nbsp;4&nbsp;反问&nbsp;❤hr面&nbsp;❤大概15分钟&nbsp;非常快速的一个面试。&nbsp;感觉主要是确认稳定性。1.&nbsp;自我介绍2.&nbsp;目前具备什么能力,可以快速接手业务吗3.&nbsp;学校这边可以处理好吗4.&nbsp;双向选择5.&nbsp;有无其他offer6.&nbsp;反问&nbsp;&nbsp;&nbsp;&nbsp;
投递字节跳动等公司10个岗位
点赞 评论 收藏
分享
评论
2
27
分享

创作者周榜

更多
牛客网
牛客企业服务