面试篇:Spring

目录

一、Spring框架的单例bean是线程安全的吗?

1、Spring框架中的bean是单例的吗?

2、Spring框架中的单例bean是线程安全的吗?

3、概括

4、面试官: Spring框架中的单例bean是线程安全的吗?

二、AOP相关面试题

1、什么是AOP,你们项目中有没有使用到AOP ?

2、场景的AOP使用场景

3、记录操作日志思路

4、Spring事务是如何实现的?

5、概括

三、事务失效的场景

1、Spring中事务失效的场景有哪些

2、异常捕获处理实例

3、非public方法导致的事务失效

 4、概括

四、Bean的生命周期

1、BeanDefinition

2、Bean生命周期 

3、实例化阶段

4、属性赋值

5、初始化

6、使用阶段

7、销毁阶段 

8、概括

五、Bean的循环依赖

1、Spring中的循环引用

2、什么是Spring的循环依赖?

3、三级缓存解决循环依赖

​编辑 4、一级缓存解决单例问题

5、一级缓存+二级缓存解决循环依赖问题

6、一级缓存+二级缓存+三级缓存解决代理对象问题

7、三级缓存无法解决的循环依赖问题

8、概括

六、SpringMVC执行流程

1、流程

2、总结

七、SpringBoot 自动装配原理详解 

1、为什么要用自动装配?

2、什么是 SpringBoot 自动装配?

3、SpringBoot 是如何实现自动装配的?如何实现按需加载?

3.1、@EnableAutoConfiguration:实现自动装配的核心注解

 3.1.1、第一步

3.1.2、第二步

3.1.3、第三步

3.1.4、第四步


一、Spring框架的单例bean是线程安全的吗?

1、Spring框架中的bean是单例的吗?

spring框架中的bean是单例的,在默认情况下是singleton模式,即单例模式。如果需要更改则可以在@Scope注解设置为prototype为多例模式。

  • singleton:bean在每个Spring IOC容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。

2、Spring框架中的单例bean是线程安全的吗?

如上图的代码,在"getById/{id}"请求中,count变量是成员变量,会受到多个用户同时修改,就会出现线程安全问题。但UserService userService不会出现线程安全问题,因为userService是无状态bean,不能被修改。

Spring bean并没有可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。

3、概括

Spring框架中的单例bean是线程安全的吗?

  • 不是线程安全的。
  • Spring框架中有一个@Scope注解,默认的值就是singleton,单例的。因为一般在spring的bean的中都是注入无状态的对象,没有线程安全问题,如果在bean中定义了可修改的成员变量,是要考虑线程安全问题的,可以使用多例或者加锁来解决 。

4、面试官: Spring框架中的单例bean是线程安全的吗?

  • 不是线程安全的,是这样的。
  • 当多用户同时请求一个服务时,容器会给每一个请求分配一个线程,这是多个线程会并发执行该请求对应的业务逻辑(成员方法),如果该处理逻辑中有对该单列状态的修改(体现为该单例的成员属性),则必须考虑线程同步问题。
  • Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。
  • 比如:我们通常在项目中使用的Spring bean都是不可可变的状态(比如Service类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。
  • 如果你的bean有多种状态的话(比如 View Model对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用由“singleton”变更为“prototype”。 

二、AOP相关面试题

1、什么是AOP,你们项目中有没有使用到AOP ?

  • AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

2、场景的AOP使用场景

  • 记录操作日志
  • 缓存处理
  • Spring中内置的事务处理 

3、记录操作日志思路

这种情况可以使用Spring的AOP思想来解决问题。记录日志需要记录当前用户的各种信息到数据库的日志表中去,因此可以在不改变原代码的情况下,使用AOP封装一个模块来对此操作。

大概思路就是创建一个切面类,找到要添加相应功能的类的地址,然后在切面类中对要添加的功能进行封装,可以使用前置通知,后置通知,异常通知,返回通知和环绕通知对次进行添加。一般而言使用环绕通知,获取要添加功能的类的相应信息,比如添加用户,就可以获取添加用户的用户名,请求方式,访问地址等待来将它们添加到数据库的日志表中。

4、Spring事务是如何实现的?

编程式事务

  • 通过TransactionTemplate或TransactionManager手动管理事务,因为是手动,所以实际开发中很少使用。

声明式事务(主要)

  • 声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

如上代码,比如要保存一个用户,其中的Object proceed=joinPoint.proceed();方法就是执行保存用户的代码,在代码之前会开启事务,然后在保存之后是提交事务。如果出现异常会在catch代码块执行回滚事务,这是Spring的事务的原理。

如果需要开启事务,就可以使用 @Transactional注解添加到方法上进行开启。

5、概括

什么是AOP?

  • 面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取公共模块复用,降低耦合

你们项目中有没有使用到AOP?

  • 记录操作日志,缓存,spring实现的事务核心是:使用aop中的环绕通知+切点表达式(找到要记录日志的方法)通过环绕通知的参数获取请求方法的参数(类、方法、注解、请求方式等),获取到这些参数以后,保存到数据库

Spring中的事务是如何实现的?

  • 其本质是通过AOP功能,对方法前后进行拦截,在执行方法之前开启事务,在执行完目标方法之后根据执行情况提交或者回滚事务

三、事务失效的场景

1、Spring中事务失效的场景有哪些

  • 异常捕获处理
  • 抛出检查异常
  • 非public方法

2、异常捕获处理实例

如上图代码,这是正常转帐的情况

  • 这是执行前的数据

  • 这是代码执行后的数据

如上图,进行模拟异常,那么就会事务回滚

  • 这是执行结果:事务会回滚

如上图代码,这是将try-catch包裹异常的代码

这是执行后的数据:事务没有被回滚

原因:

  • 事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉。

这是Spring的事务底层,AOP层无法捕获到异常就不会进行回滚,因为业务层已经将异常捕获了。

解决方法:

在catch块添加throw new RuntimeException(e)抛出

2、抛出检查异常

如上图,代码抛出了 FileNotFoundException异常,这个是检查异常

运行前结果:

运行后结果:事务没有回滚

原因:

  • Spring 默认只会回滚非检查异常

解决:

  • 配置rollbackFor属性
    • @Transactional(rollbackFor=Exception.class)

3、非public方法导致的事务失效

如上代码,访问权限修饰为default,不是public

运行前数据:

运行后数据:事务失效

原因:

  • Spring为方法创建代理、添加事务通知、前提条件都是该方法是 public的

解决方法:

  • 改为 public方法

 4、概括

Spring中事务失效的场景有哪些

  • 异常捕获处理,自己处理了异常,没有抛出,解决: 手动抛出
  • 抛出检查异常,配置rollbackFor属性为Exception
  • 非public方法导致的事务失效,改为public

四、Bean的生命周期

1、BeanDefinition

BeanDefinition的作用是保存Bean的相关信息,包括属性、构造函数参数值等等

有三种面向资源的bean定义信息读取方式,常用的方式:

  • XML方式配置
    • 会将xml配置的<bean>的信息封装成一个BeanDefinition对象,Spring根据BeanDefinition来创建Bean对象,里面有很多的属性用来描述Bean。
  • 面向注解
    • @component
    • @import
    • @bean

 这是BeanDefinition的类的方法

其中比较重要的有:

  • beanClassName: bean 的类名
  • initMethodName:初始化方法名称
  • properryValues: bean 的属性值
  • scope:作用域
  • lazylnit:延迟初始化

2、Bean生命周期 

Spring Bean 生命周期

Bean生命周期主要分为三个大阶段分别为:

  • 实例化 Instantiation
  • 属性赋值 Populate
  • 初始化 Initialization
  • 销毁 Destruction

3、实例化阶段

首先通过loadBeanDefinitions()方法,用xml、注解等方法将定义的Bean类全部都找出来,并且放到beanDefinitionMap集合中。

通过遍历beanDefinitionMap集合,然后使用creatBean()方法创建一个个Bean对象,创建Bean分为构造对象、填充属性、初始化实例、注册销毁四个步骤。

首先,对于构造对象,通过createBeanInstance()方法进行对象的构造,先用反射机制从beanDefinitionMap中的BeanClass拿到这个类的构造方法。如果有多个构造方法,那么就会造成可读性低,理解和维护困难。对于获取构造方法有响应的规则:

  • 如果Bean只有一个构造方法,那么就使用该构造方法。
  • 如果Bean有多个构造方法,那么就会优先选择有@Autowired注解的构造方法,如果多个构造方法都有@Autowired,那么就会报错。
  • 如果构造方法没有@Autowired注解,那么会优先使用无参数的构造方法,如果多个构造方法都有参数,那么就会报错。

在确定构造方法之后,就需要准备构造方法的参数了,首先在Bean的单例池中根据参数的Class类进行查找 ,如果这个类有多个实例,则会根据参数名进行匹配。如果没有找到,就会认为构造信息不完整之间报错。在参数准备好以后就可以通过反射进行Bean的构造了,即实例化。如果是无参构造,则无需参数之间构造。

4、属性赋值

接下来是进行属性赋值 ,通过调用populateBean()方法为Bean内部的所需的属性进行复制填充,通常就是@Autowired注解这些变量,通过三级缓存机制进行填充,三级缓存就是依赖注入。

5、初始化

然后是初始化实例。如果实现了其他 *.Aware接口,就调用相应的方法。

如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。

如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执行postProcessAfterInitialization() 方法

在Spring内部使用了很多后置处理器,比较典型的是当一个类被增强了使用到了AOP,那么这个类通常使用后置处理器来增强的。

6、使用阶段

因此到了BeanPostProcessors这一步之后,基本Bean实例已经被创建好了,并且可以使用。

7、销毁阶段 

对于销毁,通过实现DisposableBean接口执行destory方法进行销毁。

8、概括

Spring的bean的生命周期

  • 通过BeanDefinition获取bean的定义信息
  • 调用构造函数实例化bean
  • bean的依赖注入
  • 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
  • Bean的后置处理器BeanPostProcessor-前置
  • 初始化方法(lnitializingBean、init-method)
  • Bean的后置处理器BeanPostProcessor-后置
  • 销毁bean

解释: 

  • 通过BeanDefinition获取bean的定义信息:获取Bean的元信息,包括Bean的名称、别名、作用域、依赖关系等。
  • 调用构造函数实例化bean:根据BeanDefinition中的信息,调用相应的构造函数实例化Bean。
  • bean的依赖注入:将Bean所依赖的其他Bean注入到当前Bean中。
  • 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware):如果当前Bean实现了相应的Aware接口,Spring容器会将相关资源注入到当前Bean中。
  • Bean的后置处理器BeanPostProcessor-前置:在初始化方法执行之前,Spring容器会调用所有实现了BeanPostProcessor接口的类中的postProcessBeforeInitialization方法。
  • 初始化方法(lnitializingBean、init-method):执行初始化方法,包括InitializingBean接口中定义的afterPropertiesSet方法和init-method属性指定的方法。
  • Bean的后置处理器BeanPostProcessor-后置:在初始化方法执行之后,Spring容器会调用所有实现了BeanPostProcessor接口的类中的postProcessAfterInitialization方法。
  • 销毁bean:当容器关闭时,Spring容器会调用所有实现了DisposableBean接口和destroy-method属性指定的方法。

五、Bean的循环依赖

1、Spring中的循环引用

对于两个Bean,A引用B,B引用A,当然这是典例,还有以下几种情况:

 A依赖于B,B依赖于C,C依赖于A

A依赖于A

2、什么是Spring的循环依赖?

首先是实例化A,在堆中开启其内存,目前还是半成品,还没有经过后置处理器和初始化。然后初始化A,但是A中有b属性,然后再Spring容器找B对象,没有找到去实例化B对象。B对象实例化后进行B对象的初始化,B对象有a属性,在Spring容器中没有找到A对象,然后实例化A对象,这样就陷入死循环。

3、三级缓存解决循环依赖

Spring解决循环依赖是通过三级缓存,对应的三级缓存如下所示:

 4、一级缓存解决单例问题

一级缓存作用:限制bean在beanFactory中只存一份,即实现sinleton scope,解决不了循环依赖

 

在这个图中,没有对象能够走完生命周期,所以无法解决依赖注入

5、一级缓存+二级缓存解决循环依赖问题

如果需要打破依赖注入,需要一级缓存和二级缓存同时使用才可以 

如上图,因为使用二级缓存,就可以将半成品对象放入二级缓存中,因此实例化A后获得A的半成品对象,就是原始对象A,将其放入二级缓存中

如上图,然后A对象有b属性,需要B初始化对象,因此在Spring容器中没有找到b变量的对象即B对象。就开始创建B对象,对其实例化后获得原始对象B,将原始对象B放入二级缓存。这个时候原始对象需要初始化对象B,因为B对象有A对象的变量a,需要在Spring容器找到A对象。结果在二级缓存找到了A对象,然后注入A。

如上图,将半成品A从二级缓存注入到B对象中。

如上图,B对象创建成功,然后存入了单例池中,并将原始对象B从二级缓存删除。

如上图,B对象创建成功,就可以将B对象注入给A对象,A对象也创建成功了,将二级缓存中的A对象删除,在单例池添加A对象。 

6、一级缓存+二级缓存+三级缓存解决代理对象问题

虽然一级缓存+二级缓存解决了循环依赖,但是只能解决一般对象的循环依赖,无法解决代理对象的循环依赖。如果一个对象被增强了,那么就是代理对象,这个时候就需要借助三级缓存。

首先是实例化A,然后原始对象A会生成A的工厂对象,将其放入三级缓存中。

然后是A对象有B对象的属性b,需要实例化B对象,但是Spring容器没有B对象,因此需要实例化B对象,生成B的原始对象,原始对象B生成B对象的工厂对象,并将其放入三级缓存中。

这个时候B对象有A对象的属性a,然后去Spring容器查找A对象,然后三级缓存有A对象的工厂对象,生产出A对象的代理对象。然后将A对象的代理对象放入二级缓存中,并从三级缓存删除A对象的工厂对象。

然后将A对象的代理对象注入给B对象,B对象创建成功,将B对象(普通对象)放入单例池中

 然后将创建成功的B对象注入给A对象,A对象也创建成功,最后将A对象(代理对象)放入单例池中。

7、三级缓存无法解决的循环依赖问题

三级缓存只能解决初始化依赖问题,无法解决构造器依赖的问题

出现这种情况: 

就需要使用延时加载:

延时加载就是什么时候需要对象再进行对象的创建,而不是实例化对象的时候直接注入进去 

8、概括

Spring中的循环引用

  • 循环依赖:循环依赖其实就是循环引用,也就是两个或两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于A
  • 循环依赖在spring中是允许存在,spring框架依据三级缓存已经解决了大部分的循环依赖
    • 一级缓存:单例池,缓存已经经历了完整的生命周期,已经初始化完成的bean对象
    • 二级缓存:缓存早期的bean对象(生命周期还没走完)
    • 三级缓存:缓存的是ObjectFactory,表示对象工厂,用来创建某个对象的

构造方法出现了循环依赖怎么解决?

  • A依赖于B,B依赖于A,注入的方式是构造函数
  • 原因:
    • 由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入。
  • 解决方案:
    • 使用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建 。

六、SpringMVC执行流程

1、流程

如上图,当前端发送一个请求的时候,首先经过前端控制器 DispatcherServlet ,这个前端控制器是一个调度中心。

然后前端控制器去查询handle,handle就是某一路径对应的方法名,比如/user/getById/1这个路径,找到这个controller中这个路径所对应的方法名就是handle。其中处理器映射器保存着很多handle和一些类名、方法名等。

当从处理器映射器查到了这个handle所对应的value的时候,即找到所对应的方法名,然后返回处理器执行链给前端控制器。因为在执行某个方法的时候可能会遇到拦截器,因此将拦截器和handle一起封装到处理器执行链返回给前端控制器。

然后前端控制器将从处理器映射器获取到的处理器执行链,将其发送到处理器适配器进行处理。在处理器适配器中要处理两个部分,分别为处理参数和处理返回值。因为在请求路径中的请求参数不知道是什么类型,这个类型最终决定为什么类型就由处理器适配器去决定。还有返回值的决定,也是相同的道理由处理器适配器来决定其返回值。        

当处理完这两个参数以后将handle发送给处理器handler,用来在对应的controller 找到handler方法名所对应的方法进行执行,如果有拦截器先执行拦截器然后再执行方法。

 最后在处理器handler方法添加@ResponseBody,并且转换结果为JSON并响应结果给前端。

2、总结

  • 用户发送出请求到前端控制器DispatcherServlet。
  • DispatcherServlet收到请求调用HandlerMapping (处理器映射器)。
  • HandlerMapping找到具体的处理器,生成处理器对象及处理器拦截器(如果有),再一起返回给DispatcherServlet。
  • DispatcherServlet调用HandlerAdapter (处理器适配器)。
  • HandlerAdapter经过适配调用具体的处理器 (Handler/Controller)。
  • 方法上添加了@ResponseBody。
  • 通过HttpMessageConverter来返回结果转换为JSON并响应。

七、SpringBoot 自动装配原理详解 

1、为什么要用自动装配?

在Spring使用的是xml配置来进行装配,某些 Spring 特性或者引入第三方依赖的时候,还是需要用 XML 或java进行显式配置。

没有 Spring Boot 的时候,我们写一个 RestFul Web 服务,还首先需要进行如下配置。

@Configuration
public class RESTConfiguration
{
    @Bean
    public View jsonTemplate() {
        MappingJackson2JsonView view = new MappingJackson2JsonView();
        view.setPrettyPrint(true);
        return view;
    }

    @Bean
    public ViewResolver viewResolver() {
        return new BeanNameViewResolver();
    }
}

spring-servlet.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd
    http://www.springframework.org/schema/mvc/ http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="com.howtodoinjava.demo" />
    <mvc:annotation-driven />

    <!-- JSON Support -->
    <bean name="viewResolver" class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
    <bean name="jsonTemplate" class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>

</beans>

但是,Spring Boot 项目,我们只需要添加相关依赖,无需配置,通过启动下面的 main 方法即可。

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

并且,我们通过 Spring Boot 的全局配置文件 application.propertiesapplication.yml即可对项目进行设置比如更换端口号,配置 JPA 属性等等。

为什么 Spring Boot 使用起来这么酸爽呢? 这得益于其自动装配。自动装配可以说是 Spring Boot 的核心,那究竟什么是自动装配呢?

2、什么是 SpringBoot 自动装配?

SpringBoot 定义了一套接口规范,这套规范规定:SpringBoot 在启动时会扫描外部引用 jar 包中的META-INF/spring.factories文件,将文件中配置的类型信息加载到 Spring 容器(此处涉及到 JVM 类加载机制与 Spring 的容器知识),并执行类中定义的各种操作。对于外部 jar 来说,只需要按照 SpringBoot 定义的标准,就能将自己的功能装置进 SpringBoot。

没有 Spring Boot 的情况下,如果我们需要引入第三方依赖,需要手动配置,非常麻烦。但是,Spring Boot 中,我们直接引入一个 starter 即可。比如你想要在项目中使用 redis 的话,直接在项目中引入对应的 starter 即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

引入 starter 之后,我们通过少量注解和一些简单的配置就能使用第三方组件提供的功能了。 

自动装配可以简单理解为:通过注解或者一些简单的配置就能在 Spring Boot 的帮助下实现某块功能。

3、SpringBoot 是如何实现自动装配的?如何实现按需加载?

我们先看一下 SpringBoot 的核心注解 SpringBootApplication 。

大概可以把 @SpringBootApplication看作是 @Configuration@EnableAutoConfiguration@ComponentScan 注解的集合。根据 SpringBoot 官网,这三个注解的作用分别是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
  • @ComponentScan:扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描启动类所在的包下所有的类 ,可以自定义不扫描某些 bean。如下图所示,容器中将排除TypeExcludeFilterAutoConfigurationExcludeFilter
3.1、@EnableAutoConfiguration:实现自动装配的核心注解

EnableAutoConfiguration 只是一个简单地注解,自动装配核心功能的实现实际是通过 AutoConfigurationImportSelector类。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}

我们现在重点分析下AutoConfigurationImportSelector 类到底做了什么

3.2、AutoConfigurationImportSelector:加载自动装配类

AutoConfigurationImportSelector类的继承体系如下:

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

}

public interface DeferredImportSelector extends ImportSelector {

}

public interface ImportSelector {
    String[] selectImports(AnnotationMetadata var1);
}

可以看出,AutoConfigurationImportSelector 类实现了 ImportSelector接口,也就实现了这个接口中的 selectImports方法,该方法主要用于获取所有符合条件的类的全限定类名,这些类需要被加载到 IoC 容器中

private static final String[] NO_IMPORTS = new String[0];

public String[] selectImports(AnnotationMetadata annotationMetadata) {
        // <1>.判断自动装配开关是否打开
        if (!this.isEnabled(annotationMetadata)) {
            return NO_IMPORTS;
        } else {
          //<2>.获取所有需要装配的bean
            AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
            AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
            return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
        }
    }

这里我们需要重点关注一下getAutoConfigurationEntry()方法,这个方法主要负责加载自动配置类的。

该方法调用链如下:

现在我们结合getAutoConfigurationEntry()的源码来详细分析一下:

private static final AutoConfigurationEntry EMPTY_ENTRY = new AutoConfigurationEntry();

AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
        //<1>.判断自动装配开关是否打开
        if (!this.isEnabled(annotationMetadata)) {
            return EMPTY_ENTRY;
        } else {
            //<2>.用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName
            AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
            //<3>.获取需要自动装配的所有配置类,读取META-INF/spring.factories
            List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
            //<4>.获取需要自动装配的所有配置类,读取META-INF/spring.factories
            configurations = this.removeDuplicates(configurations);
            Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
            this.checkExcludedClasses(configurations, exclusions);
            configurations.removeAll(exclusions);
            configurations = this.filter(configurations, autoConfigurationMetadata);
            this.fireAutoConfigurationImportEvents(configurations, exclusions);
            return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
        }
    }
 3.1.1、第一步

判断自动装配开关是否打开。默认spring.boot.enableautoconfiguration=true,可在 application.propertiesapplication.yml 中设置

3.1.2、第二步

用于获取EnableAutoConfiguration注解中的 exclude 和 excludeName

3.1.3、第三步

获取需要自动装配的所有配置类,读取META-INF/spring.factories

spring-boot/spring-boot-project/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories
 

从下图可以看到这个文件的配置内容都被我们读取到了。XXXAutoConfiguration的作用就是按需加载组件。

不光是这个依赖下的META-INF/spring.factories被读取到,所有 Spring Boot Starter 下的META-INF/spring.factories都会被读取到。

所以,你可以清楚滴看到, druid 数据库连接池的 Spring Boot Starter 就创建了META-INF/spring.factories文件。

3.1.4、第四步

到这里可能面试官会问你:“spring.factories中这么多配置,每次启动都要全部加载么?”。

很明显,这是不现实的。我们 debug 到后面你会发现,configurations 的值变小了

因为,这一步有经历了一遍筛选,@ConditionalOnXXX 中的所有条件都满足,该类才会生效。

@Configuration
// 检查相关的类:RabbitTemplate 和 Channel是否存在
// 存在才会加载
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import(RabbitAnnotationDrivenConfiguration.class)
public class RabbitAutoConfiguration {
}

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值