Q&A-05 Spring

动态代理的两种方式,以及区别

  • JDK动态代理:利用反射机制生成一个实现被代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理,只能对实现了接口的类生成代理
  • CGLIB动态代理:利用asm开源包,把被代理类的class文件加载进来,修改被代理类的字节码生成子类,覆盖其中的方法。不能对声明为 final 的方法进行代理,因为不能覆写final方法。

Spring 选择用 JDK 还是 CGLiB 的依据:
(1)当 Bean 实现接口时,Spring 就会用 JDK 的动态代理
(2)当 Bean 没有实现接口时,Spring 使用 CGlib 是实现
(3)可以强制使用 CGlib(在 spring 配置中加入)

CGlib 比 比 JDK 快??
(1)使用 CGLib 实现动态代理,CGLib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib 不能对声明为 final 的方法进行代理,因为 CGLib 原理是动态生成被代理类的子类。
(2)在对 JDK 动态代理与 CGlib 动态代理的代码实验中看,1W 次执行下,JDK7 及 8 的动态代理性能比 CGlib 要好 20%左右。

image.png

链接:
https://www.unknowtime.top/article/187
https://www.codenong.com/cs105851751/

IoC 和 DI

控制反转 IoC 是一个理论思想,原来的对象是由使用者控制,有了Spring后,可以把对象交给Spring来进行管理。

DI:依赖注入,把对应的属性值注入到具体的对象中,@Autowired,populateBean完成属性值的注入。

控制反转 IoC 是从容器的角度在描述:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
依赖注入 DI 是从应用程序的角度在描述:应用程序依赖容器创建并注入它所需要的外部资源。DI 是对 IoC 更准确的描述,即组件之间的依赖关系由容器在运行期决定,即由容器动态的将某种依赖关系注入到组件之中。

反射(reflection),它允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性,spring就是通过反射来实现注入的。
DI 可以通过setter方法注入(设值注入)、构造器注入和接口注入三种方式来实现,Spring 支持 setter 注入(field 注入)和构造器注入,通常使用构造器注入来注入必须的依赖关系,对于可选的依赖关系,则 setter 注入是更好的选择,setter 注入需要类提供无参构造器或者无参的静态工厂方法来创建对象。

实现IOC的步骤:

  1. 定义用来描述bean的配置的Java类。
  2. 解析bean的配置,将bean的配置信息转换为BeanDefinition对象保存到内存中,spring中采用HashMap进行对象存储,其中会用到一些xml的解析技术,如dom4j等。
  3. 遍历存放BeanDefinition的HashMap对象,逐条取出BeanDefinition对象,获取bean的配置信息,利用Java的反射机制实例化对象,将实例化后的对象保存到另外一个Map中即可。

容器

存储对象,使用Map结构来存储,Spring一般存在三层缓存,singletonObjects存放完整的bean对象,整个bean的生命周期,从创建到使用到销毁的过程全部由容器来管理。

容器的创建过程:

image.png

ClassPathXmlApplicationContext 的容器初始化我们大致分为下面几步:

  1. BeanDefinition 的 Resource 定位
    这里的Resource定位 是通过继承ResourceLoader 获得的,ResourceLoader代表了加载资源的一种方式,正是策略模式的实现。
  2. 从 Resource中解析、载入BeanDefinition
  3. BeanDefinition 在IoC 容器中的注册
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
        throws BeansException {

    super(parent);
    setConfigLocations(configLocations);
    if (refresh) {
        refresh();
    }
}

入参中的configLocations在这里就是你XML配置文件 的 classpath。
setConfigLocations(configLocations) 这里不展开讲,内容不复杂,就是把一些带有占位符的地址解析成实际的地址。
再之后就是refresh(),我们说的容器初始化,就是在这里面进行的,这里取名为refresh,是因为容器启动之后,再调用refresh()会刷新IoC 容器。

@Override
public void refresh() throws BeansException, IllegalStateException {
   // 来个锁,不然 refresh() 还没结束,你又来个启动或销毁容器的操作,那不就乱套了嘛
   synchronized (this.startupShutdownMonitor) {

      // 准备工作,记录下容器的启动时间、标记“已启动”状态、处理配置文件中的占位符
      prepareRefresh();

      // 这步比较关键,这步完成后,配置文件就会解析成一个个 Bean 定义,注册到 BeanFactory 中,
      // 当然,这里说的 Bean 还没有初始化,只是配置信息都提取出来了,
      // 注册也只是将这些信息都保存到了注册中心(说到底核心是一个 beanName-> beanDefinition 的 map)
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 设置 BeanFactory 的类加载器,添加几个 BeanPostProcessor,手动注册几个特殊的 bean
      // 这块待会会展开说
      prepareBeanFactory(beanFactory);

      try {
         // 【这里需要知道 BeanFactoryPostProcessor 这个知识点,Bean 如果实现了此接口,
         // 那么在容器初始化以后,Spring 会负责调用里面的 postProcessBeanFactory 方法。】

         // 这里是提供给子类的扩展点,到这里的时候,所有的 Bean 都加载、注册完成了,但是都还没有初始化
         // 具体的子类可以在这步的时候添加一些特殊的 BeanFactoryPostProcessor 的实现类或做点什么事
         postProcessBeanFactory(beanFactory);
         // 调用 BeanFactoryPostProcessor 各个实现类的 postProcessBeanFactory(factory) 方法
         invokeBeanFactoryPostProcessors(beanFactory);

         // 注册 BeanPostProcessor 的实现类,注意看和 BeanFactoryPostProcessor 的区别
         // 此接口两个方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
         // 两个方法分别在 Bean 初始化之前和初始化之后得到执行。注意,到这里 Bean 还没初始化
         registerBeanPostProcessors(beanFactory);

         // 初始化当前 ApplicationContext 的 MessageSource,国际化不是重点,不展开
         initMessageSource();

         // 初始化当前 ApplicationContext 的事件广播器,这里也不展开了
         initApplicationEventMulticaster();

         // 从方法名就可以知道,典型的模板方法(钩子方法),
         // 具体的子类可以在这里初始化一些特殊的 Bean(在初始化 singleton beans 之前)
         onRefresh();

         // 注册事件监听器,监听器需要实现 ApplicationListener 接口。这也不是我们的重点,过
         registerListeners();

         // 重点,重点,重点
         // 初始化所有的 singleton beans
         //(lazy-init 的除外)
         finishBeanFactoryInitialization(beanFactory);

         // 最后,广播事件,ApplicationContext 初始化完成
         finishRefresh();
      }

      catch (BeansException ex) {
         if (logger.isWarnEnabled()) {
            logger.warn("Exception encountered during context initialization - " +
                  "cancelling refresh attempt: " + ex);
         }

         // Destroy already created singletons to avoid dangling resources.
         // 销毁已经初始化的 singleton 的 Beans,以免有些 bean 会一直占用资源
         destroyBeans();
         // Reset 'active' flag.
         cancelRefresh(ex);
         throw ex;
      }

      finally {
         // Reset common introspection caches in Spring's core, since we
         // might not ever need metadata for singleton beans anymore...
         resetCommonCaches();
      }
   }
}

我会从上述流程中,挑以下几个进行分析:

  1. prepareRefresh() 创建容器前的准备工作
  2. obtainFreshBeanFactory() 创建 BeanFactory
  3. prepareBeanFactory(beanFactory) 对BeanFactory进行一些特征的设置工作
  4. finishBeanFactoryInitialization(beanFactory); 初始化所有的 singleton beans(DI的入口)

参考链接:

  • Spring容器IOC初始化过程---今天终于进行总结了
  • Spring IoC - IoC 容器初始化 源码解析
  • Spring IoC - 依赖注入源码解析

Spring IOC 的底层实现

image.png

Spring Bean 生命周期

Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 bean ,Spring 在创建好交给使用者之后则不会再管理后续的生命周期。

  1. Bean 容器找到配置⽂件中 Spring Bean 的定义。
  2. Bean 容器利⽤ Java Reflection API 创建⼀个Bean的实例。
  3. 如果涉及到⼀些属性值,populateBean() 填充属性值,可以解决循环依赖问题。
  4. 如果 Bean 实现了 *.Aware 接⼝,就调⽤相应的⽅法。
  5. 如果有和加载这个 Bean 的 Spring 容器相关的 BeanPostProcessor 对象,执⾏ postProcessBeforeInitialization() ⽅法。
  6. 如果Bean实现了 InitializingBean 接⼝,执⾏ afterPropertiesSet() ⽅法。
  7. 如果 Bean 在配置⽂件中的定义包含 init-method 属性,执⾏指定的⽅法。
  8. 如果有和加载这个 Bean的 Spring 容器相关的 BeanPostProcessor 对象,执⾏ postProcessAfterInitialization() ⽅法。
  9. 当要销毁 Bean 的时候,如果 Bean 实现了 DisposableBean 接⼝,执⾏ destroy() ⽅法。
  10. 当要销毁 Bean 的时候,如果 Bean 在配置⽂件中的定义包含 destroy-method 属性,执⾏指定的⽅法。
image.png
image.png

链接:
Spring Bean 生命周期
《javaguide面试突击版》

Spring如何解决循环依赖

spring中循环依赖有三种情况:

  1. 构造器注入形成的循环依赖。也就是beanB需要在beanA的构造函数中完成初始化,beanA也需要在beanB的构造函数中完成舒适化,这种情况的结果就是两个bean都不能完成初始化,循环依赖难以解决。

  2. setter注入构成的循环依赖。beanA需要在beanB的setter方法中完成初始化,beanB也需要在beanA的setter方法中完成初始化,spring设计的机制主要就是解决这种循环依赖。

  3. prototype作用域bean的循环依赖。这种循环依赖同样无法解决,因为spring不会缓存‘prototype’作用域的bean,而spring中循环依赖的解决正是通过缓存来实现的。

spring只能解决单例 bean + setter 注入构成的依赖,setter注入构成依赖的解决方案:三级缓存(singletonBeanFactory)。

Spring在InstantiateBean时执行构造器方法,构造出实例,如果是单例的话,会将它放入一个singletonBeanFactory的缓存中,再进行populateBean方法,设置属性。通过一个singletonBeanFactory的缓存解决了循环依赖的问题。

详细:
小白也看得懂的 Spring IoC 核心流程介绍
Spring IOC 容器源码分析
Spring 如何解决循环依赖的问题

缓存的放置时间和删除时间

BeanFactory 和 FactoryBean 的区别

image.png
  • BeanFactory:Spring中的IoC容器,所有Spring Bean 的Factory
  • FactoryBean:一个Bean,一个不简单的Bean,一个能产生对象或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

Spring 中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean 即 FactoryBean。

一般情况下,Spring 通过反射机制利用bean的class属性指定实现类来实例化bean 。在某些情况下,实例化bean 过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息,配置方式的灵活性是受限的, 这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 org.Springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。(看后面的一些例子会理解更深刻)

所以说,当配置一个的过程非常复杂,创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean。

package org.springframework.beans.factory;

public interface FactoryBean {
    T getObject() throws Exception;

    Class getObjectType();

    boolean isSingleton();
}

给定一个id=mybean的FactoryBean,getBean("mybean")得到的就是这个FactoryBean创建的对象实例,而getBean("&mybean")得到的确实FactoryBean自身对象。

参考链接:Spring源码 - FactoryBean 应用拓展(附源码解析)

AOP

AOP,即面向切面编程,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",所谓"切面"是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开。

实现方式

AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理。

  1. AspectJ

Spring中嵌入了AspectJ。

  • 切面语法
  • 织入工具

参考链接:原生AspectJ用法分析以及Spring-AOP原理分析 - Mythsman

  1. Spring AOP

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:

  • JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。核心是InvocationHandler接口和Proxy类。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
  • CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。

如果要代理的对象,实现了某个接⼝,那么Spring AOP会使⽤JDKProxy,去创建代理对象,⽽对于没有实现接⼝的对象,就⽆法使⽤ JDK Proxy 去进⾏代理了,这时Spring AOP会使⽤Cglib ,这时候Spring AOP会使⽤ Cglib ⽣成⼀个被代理对象的⼦类来作为代理。

AOP中的一些概念

使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开。

a. 连接点(Joinpoint):程序执行的某个特定位置(如:某个方法调用前、调用后,方法抛出异常后)。一个类或一段程序代码拥有一些具有边界性质的特定点,这些代码中的特定点就是连接点。
b. 切点:如果连接点相当于数据中的记录,那么切点相当于查询条件,一个切点可以匹配多个连接点。
c. 增强(Advice):增强是织入到目标类连接点上的一段程序代码。
d. 引介(Introduction):引介是一种特殊的增强,它为类添加一些属性和方法。
e. 织入(Weaving):织入是将增强添加到目标类具体连接点上的过程,AOP有三种织入方式
f. 切面:切面是由切点和增强(引介)组成的,它包括了对横切关注功能的定义,也包括了对连接点的定义。

Spring AOP 方法执行顺序

image.png

Spring 的AOP 的底层实现原理

Spring 中的 bean 的作⽤域

  • singleton : 唯⼀ bean 实例,Spring 中的 bean 默认都是单例的。
  • prototype : 每次请求都会创建⼀个新的 bean 实例。
  • request : 每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request 内有效。
  • session : 每⼀次HTTP请求都会产⽣⼀个新的 bean,该bean仅在当前 HTTP session 内有效。
  • global-session: 全局session作⽤域,仅仅在基于portlet的web应⽤中才有意义,Spring5已经没有了。Portlet是能够⽣成语义代码(例如:HTML)⽚段的⼩型Java Web插件。它们基于portlet容器,可以像servlet⼀样处理HTTP请求。但是,与 servlet 不同,每个 portlet 都有不同的会话

Spring 中的单例 bean 的线程安全问题

单例 bean 存在线程问题,主要是因为当多个线程操作同⼀个对象的时候,对这个对象的⾮静态成员变量的写操作会存在线程安全问题。

常⻅的有两种解决办法:

  1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。
  2. 在类中定义⼀个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的⼀种⽅式)。

@Component 和 @Bean 的区别

image.png
image.png

SpringMVC的流程

DispatcherServlet
HandlerMapping
HandlerAdapter
Handler 处理器
ViewResolver

SpringMVC的流程 javaguide
  1. 客户端(浏览器)发送请求,直接请求到 DispatcherServlet 。
  2. DispatcherServlet 根据请求信息调⽤ HandlerMapping ,解析请求对应的 Handler 。
  3. 解析到对应的 Handler (也就是我们平常说的 Controller 控制器)后,开始由 HandlerAdapter 适配器处理。
  4. HandlerAdapter 会根据 Handler 来调⽤真正的 Handler 处理器处理请求,并处理相应的业务逻辑。
  5. 处理器处理完业务后,会返回⼀个 ModelAndView 对象, Model 是返回的数据对象, View是个逻辑上的 View 。
  6. ViewResolver 会根据逻辑 View 查找实际的 View 。
  7. DispaterServlet 把返回的 Model 传给 View (视图渲染)。
  8. 把 View 返回给请求者(浏览器)

SpringMVC的控制器 Controller 是不是单例模式,如果是,有什么问题,怎么解决?

是单例模式,所以在多线程访问的时候有线程安全问题,不要用同步,会影响性能,解决方案是在控制器里面不能写字段。

Spring 框架中⽤到了哪些设计模式

image.png
image.png

Spring 事务

开启事务方式

Spring提供了很好事务管理机制,主要分为编程式事务和声明式事务两种。

  1. 编程式事务:是指在代码中手动的管理事务的提交、回滚等操作,代码侵入性比较强,如下示例:
try {
    //TODO something
     transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
    throw new InvoiceApplyException("异常失败");
}
  1. 声明式事务:基于AOP面向切面的,它将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional注解了。
    @Transactional
    @GetMapping("/test")
    public String test() {
    
        int insert = cityInfoDictMapper.insert(cityInfoDict);
    }

参考链接:一口气说出 6种,@Transactional注解的失效场景

Transactional

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

  • timeout :事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

  • readOnly :指定事务是否为只读事务,默认值为 false, @Transactional(readOnly = true) 将事务设置成只读后,当前只读事务就不能进行写的操作,否则报错 Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed;

  • rollbackFor :用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

  • noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

Transactional 隔离级别

TransactionDefinition 接⼝中定义了五个表示隔离级别的常量:

  1. TransactionDefinition.ISOLATION_DEFAULT: 使⽤后端数据库默认的隔离级别,Mysql默认采⽤的 REPEATABLE_READ隔离级别,Oracle 默认采⽤的 READ_COMMITTED隔离级别.(默认配置)

  2. TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读.

  3. TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻⽌脏读,但是幻读或不可重复读仍有可能发⽣.

  4. TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同⼀字段的多次读取结果都是⼀致的,除⾮数据是被本身事务⾃⼰所修改,可以阻⽌脏读和不可重复读,但幻读仍有可能发⽣。

  5. TransactionDefinition.ISOLATION_SERIALIZABLE: 最⾼的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执⾏,这样事务之间就完全不可能产⽣⼲扰,也就是说,该级别可以防⽌脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会⽤到该级别。

事务传播行为

image.png
TransactionDefinition 当前存在事务 当前没有事务
PROPAGATION_REQUIRED 加⼊该事务 创建⼀个新的事务
PROPAGATION_SUPPORTS 加⼊该事务 以⾮事务的⽅式继续运⾏
PROPAGATION_MANDATORY 加⼊该事务 抛出异常
PROPAGATION_REQUIRES_NEW 把当前事务挂起,创建⼀个新的事务 创建⼀个新的事务
PROPAGATION_NOT_SUPPORTED 把当前事务挂起,以⾮事务⽅式运⾏ 以⾮事务⽅式运⾏
PROPAGATION_NEVER 抛出异常 以⾮事务⽅式运⾏
PROPAGATION_NESTED 创建⼀个事务,作为当前事务的嵌套事务来运⾏ 创建⼀个新的事务

Transactional(rollbackFor = Exception.class)

我们知道:Exception分为运⾏时异常RuntimeException和⾮运⾏时异常。事务管理对于企业应⽤来说是⾄关重要的,即使出现异常情况,它也可以保证数据的⼀致性。

当 @Transactional 注解作⽤于类上时,该类的所有 public ⽅法将都具有该类型的事务属性,同时,我们也可以在⽅法级别使⽤该标注来覆盖类级别的定义。如果类或者⽅法加了这个注解,那么这个类⾥⾯的⽅法抛出异常,就会回滚,数据库⾥⾯的数据也会回滚。

在 @Transactional 注解中如果不配置 rollbackFor 属性,那么事物只会在遇到 RuntimeException 的时候才会回滚,加上 rollbackFor=Exception.class ,可以让事物在遇到⾮运⾏时异常时也回滚。

关于 @Transactional 注解推荐阅读的⽂章:
透彻的掌握 Spring 中@transactional 的使⽤

@Transactional注解在什么情况下会失效,为什么

  1. @Transactional 应用在非 public 修饰的方法上;
  2. @Transactional 注解属性 propagation 设置错误;
  3. @Transactional 注解属性 rollbackFor 设置错误;
  4. 同一个类中方法调用,导致@Transactional失效
    同一个类里面的方法 A 调用方法 B,A 没有声明注解事务,B 声明注解事务,外部调用方法 A,方法B的事务不会起作用。
  5. 异常被你的 catch“吃了”导致 @Transactional 失效
  6. 数据库引擎不支持事务
    innodb引擎支持事务,myisam引擎不支持事务

链接:一口气说出 6种,@Transactional注解的失效场景

Spring 事务是如何回滚的?

image.png

SpringBoot 对 Spring 做了哪些改进?

image.png

参考链接:剖析面试最常见问题之 Spring Boot

SpringBoot的启动过程

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

@SpringBootApplication 入口类和方法;
准备监听器、环境、上下文;
Spring IOC 的流程。

介绍一下@SpringBootApplication 注解

package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
        @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   ......
}

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

  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类

你可能感兴趣的:(Q&A-05 Spring)