# spring面试问题

# Spring MVC的理解

  1. Spring MVC是是属于Spring Framework生态里面的一个模块,它是在Servlet基础上构建并且使用MVC模式设计的一个Web框架,主要的目的是简化传统Servlet+JSP模式下的Web开发方式
  2. Spring MVC天生就是为了MVC模式而设计的,因此在开发MVC应用的时候会更加方便和灵活。Spring MVC的整体架构设计对Java Web里面的MVC架构模式做了增强和扩展,主要有几个方面。
    • 把传统MVC框架里面的Controller控制器做了拆分,分成了前端控制器DispatcherServlet和后端控制器Controller。
    • 把Model模型拆分成业务层Service和数据访问层Repository。
    • 在视图层,可以支持不同的视图,比如Freemark、velocity、JSP等等。
  3. Spring MVC的具体工作流程是,浏览器的请求首先会经过SpringMVC里面的核心控制器DispatcherServlet,它负责对请求进行分发到对应的Controller。 Controller里面处理完业务逻辑之后,返回ModeAndView。然后DispatcherServlet寻找一个或者多个ViewResolver视图解析器,找到ModeAndView指定的视图,并把数据显示到客户端。

img.png

# Spring中事务的传播类型

Spring事务传播级别一般不需要定义,默认就是PROPAGATION_REQUIRED,除非在嵌套事务的情况下需要重点了解。

  1. REQUIRED:默认的Spring事物传播级别,如果当前存在事务,则加入这个事务,如果不存在事务,就新建一个事务。
  2. REQUIRE_NEW:不管是否存在事务,都会新开一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交。
  3. NESTED:如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
  4. SUPPORTS:表示支持当前事务,如果当前不存在事务,以非事务的方式执行。
  5. NOT_SUPPORTED:表示以非事务的方式来运行,如果当前存在事务,则把当前事务挂起。
  6. MANDATORY:强制事务执行,若当前不存在事务,则抛出异常.
  7. NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。

# spring的事务失效场景

  1. @Trscational 的注解没有注释pulblic的方法。
  2. 方法没有抛出的RuntimeException异常
  3. 代码异常是时候使用了try-catch 进行的捕获处理
  4. @Tractional 没有纳入spring的容易注解
  5. Mysql的没有使用InnoDB的存储引擎,可能使用的是的MySAIM引擎。
  6. 访问权限问题
  7. 方法用final修饰
  8. 方法内部调用
  9. 未被spring管理
  10. 多线程调用
  11. 表不支持事务
  12. 错误的传播特性
  13. 自己吞了异常
  14. 手动抛了别的异常
  15. 自定义了回滚异常
  16. 嵌套事务回滚多了

# Spring如何解决循依赖问题?

循环依赖是指一个或多个 Bean 实例之间存在直接或间接的依赖关系,构成循环调用。

通常表现为三种形态。

  1. 互相依赖,也就是A依赖B,B依赖A
  2. 间接依赖,两个以上的Bean存在间接依赖关系造成循环调用。
  3. 自我依赖,自己依赖自己造成了循环依赖

Spring本身也考虑到了这方面的问题,所以它设计了三级缓存来解决部分循环依赖的问题。

所谓三级缓存,其实就是用来存放不同类型的Bean。

  1. 第一级缓存存放完全初始化好的Bean,这个Bean可以直接使用了(单例对象缓存池,已经实例化并且属性赋值,这里的对象是成熟对象;)
  2. 第二级缓存存放原始的Bean对象,也就是说Bean里面的属性还没有进行赋值(单例对象缓存池,已经实例化但尚未属性赋值,这里的对象是半成品对象;)
  3. 第三级缓存存放Bean工厂对象,用来生成原始Bean对象并放入到二级缓存中(单例工厂的缓存)

假设BeanA和BeanB存在循环依赖,那么在三级缓存的设计下,我画了这样一个图来描述工作原理。

  • 初始化BeanA,先把BeanA实例化,然后把BeanA包装成ObjectFactory对象保存到三级缓存中。
  • 接着BeanA开始对属性BeanB进行依赖注入,于是开始初始化BeanB,同样做两件事,
  • 创建BeanB实例,以及加入到三级缓存。
  • 然后,BeanB也开始进行依赖注入,在三级缓存中找到了BeanA,于是完成BeanA的依赖注入
  • BeanB初始化成功以后保存到一级缓存,于是BeanA可以成功拿到BeanB的实例,从而完成正常的依赖注入。

img.png

整个流程看起来很复杂,但是它的核心思想就是把Bean的实例化和Bean中属性的依赖注入这两个过程分离出来。 不过要注意的是,Spring本身只能解决单实例存在的循环引用问题,但是存在以下四种情况需要人为干预:

  • 多实例的Setter注入导致的循环依赖,需要把Bean改成单例。
  • 构造器注入导致的循环依赖,可以通过@Lazy注解
  • DependsOn导致的循环依赖,找到注解循环依赖的地方,迫使它不循环依赖。
  • 单例的代理对象Setter注入导致的循环依赖
  • 可以使用@Lazy注解。
  • 或者使用@DependsOn注解指定加载先后关系。

在实际开发中,出现循环依赖的根本原因还是在代码设计的时候,因为模块的耦合度较高,依赖关系复杂导致的, 我们应该尽可能的从系统设计角度去考虑模块之间的依赖关系,避免循环依赖的问题。

Spring设计了三级缓存来解决循环依赖问题。

  1. 第一级缓存里面存储完整的Bean实例,这些实例是可以直接被使用的。
  2. 第二级缓存里面存储的是实例化以后,但是还没有设置属性值的Bean实例,也就是Bean里面的依赖注入还没有做。
  3. 第三级缓存用来存放Bean工厂,它主要用来生成原始Bean对象并且放到第二级缓存里面。

三级缓存的核心思想,就是把Bean的实例化,和Bean里面的依赖注入进行分离。采用一级缓存存储完整的Bean实例, 采用二级缓存来存储不完整的Bean实例,通过不完整的Bean实例作为突破口,解决循环依赖的问题。至于第三级缓存,主要是解决代理对象的循环依赖问题。

# @Conditional注解有什么用?

@Conditional是Spring4版本里面提供的注解,它的作用是给需要装载的Bean增加一个条件判断,只有满足条件的Bean才会装载到IOC容器。

@Conditional注解的定义如图所示,从这个注解中可以了解到几个关键信息

  • @Conditional注解可以修饰在类或者方法上
  • @Conditional注解可以接收一个或多个实现了Condition接口的类。
//此注解可以标注在类和方法上
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention( RetentionPolicy. RUNTIME)
@Documented
public @interface Conditional {
    Class<? extends Condition>[ ] value( ) ;
}

Condition接口的定义如图所示,它提供了一个返回值为boolean的matches方法,基于@Conditional本身的作用,不难猜出它应该是用来实现Bean是否能被装载的判断逻辑的。

@FunctionalInterface
public interface Condition {
    boolean matches ( ConditionContext context,AnnotatedTypeMetadata metadata ) ;
}

@Conditional注解既然是用来判断Bean是否能被装载的条件,那么意味着我们可以在Bean的描述逻辑上增加这样一个注解然后通过重写Condition接口的matches方法,自定义Bean装载的条件。

比如下图这种使用方法,当Spring解析这个配置类的时候,HelloService这个bean是否能被装载到IOC容器,取决于CustomizeCondition里面的matches方法的返回值,返回true才可以被装载。

@Configuration
public class ConditionalConfiguration {
    
   @Conditional( customizeCondition.class )
   @Bean
   public HelloService helloService( ){
       return new HelloService( );
   }
}

总结:@Conditional注解的作用是为Bean的装载提供了一个条件判断。只有满足条件的情况下,Spring才会把当前Bean装载到IOC容器中。 这个条件的实现逻辑,我们可以实现Condition接口并重写matches方法自己去实现。所以@Conditional注解增加了Bean装载的灵活性。 在Spring Boot里面,对@Conditional注解做了更进一步的扩展,比如增加了@ConditionalOnClass、@ConditionalOnBean等注解, 使得我们在使用的过程中不再需要去写条件的逻辑。

# Spring Boot中自动装配机制的原理

自动装配,简单来说就是自动把第三方组件的Bean装载到Spring IOC器里面,不需要开发人员再去写Bean的装配配置。 在Spring Boot应用里面,只需要在启动类加上@SpringBootApplication注解就可以实现自动装配。 @SpringBootApplication是一个复合注解,真正实现自动装配的注解是@EnableAutoConfiguration。

img.png

自动装配的实现主要依靠三个核心关键技术。

  1. 引入Starter启动依赖组件的时候,这个组件里面必须要包含@Configuration配置类,在这个配置类里面通过@Bean注解声明需要装配到IOC容器的Bean对象。
  2. 这个配置类是放在第三方的jar包里面,然后通过SpringBoot中的约定优于配置思想,把这个配置类的全路径放在classpath:/META-INF/spring.factories文件中。 这样SpringBoot就可以知道第三方jar包里面的配置类的位置,这个步骤主要是用到了Spring里面的SpringFactoriesLoader来完成的。
  3. SpringBoot拿到所第三方jar包里面声明的配置类以后,再通过Spring提供的ImportSelector接口,实现对这些配置类的动态加载。

在我看来,SpringBoot是约定优于配置这一理念下的产物,所以在很多的地方,都会看到这类的思想。它的出现,让开发人员更加聚焦在了业务代码的编写上,而不需要去关心和业务无关的配置。 其实,自动装配的思想,在SpringFramework3.x版本里面的@Enable注解,就有了实现的雏形。@Enable注解是模块驱动的意思,我们只需要增加某个@Enable注解, 就自动打开某个功能,而不需要针对这个功能去做Bean的配置,@Enable底层也是帮我们去自动完成这个模块相关Bean的注入。

# 你对Spring IOC 和DI的理解?

首先,Spring IOC,全称控制反转(Inversion of Control)。在传统的Java程序开发中,我们只能通过new关键字来创建对象,这种导致程序中对象的依赖关系比较复杂,耦合度较高。

img.png

而IOC的主要作用是实现了对象的管理,也就是我们把设计好的对象交给了IOC容器控制,然后在需要用到目标对象的时候,直接从容器中去获取。

img.png

有了IOC容器来管理Bean以后,相当于把对象的创建和查找依赖对象的控制权交给了容器,这种设计理念使得对象与对象之间是一种松耦合状态,极大提升了程序的灵活性以及功能的复用性。

然后,DI表示依赖注入,也就是对于IOC容器中管理的Bean,如果Bean之间存在依赖关系,那么IOC容器需要自动实现依赖对象的实例注入,通常有三种方法来描述Bean之间的依赖关系。

  1. 接口注入
  2. setter注入
  3. 构造器注入 另外,为了更加灵活的实现Bean实例的依赖注入,Spring还提供了@Resource和@Autowired这两个注解。分别是根据bean的id和bean的类型来实现依赖注入。

# Spring 里面的事务和分布式事务的使用如何区分?

首先, 在Spring里面并没有提供事务,它只是提供了对数据库事务管理的封装。通过声明式的事务配置,使得开发人员可以从一些复杂的事务处理中得到解脱, 我们不再需要关心连接的获取、连接的关闭、事务提交、事务回滚这些操作。更加聚焦在业务开发层面。所以,Spring里面的事务,本质上就是数据库层面的事务, 这种事务的管理,主要是针对单个数据库里面多个数据表操作的,去满足事务的ACID特性。

分布式事务,是解决多个数据库的事务操作的数据一致性问题,传统的关系型数据库不支持跨库事务的操作,所以需要引入分布式事务的解决方案。 而Spring并没有提供分布式事务场景的支持,所以Spring事务和分布式事务在使用上并没有直接的关联性。 但是我们可以使用一些主流的事务解决框架,比如Seata,集成到Spring生态里面,去解决分布式事务的问题。

# Spring中,有两个id相同的bean会报错吗?

首先,在同一个XML配置文件里面,不能存在id相同的两个bean,否则spring容器启动的时候会报错。 因为id这个属性表示一个Bean的唯一标志符号,所以Spring在启动的时候会去验证id的唯一性,一旦发现重复就会报错,

这个错误发生Spring对XML文件进行解析转化为BeanDefinition的阶段。 但是在两个不同的Spring配置文件里面,可以存在id相同的两个bean。IOC容器在加载Bean的时候,默认会多个相同id的bean进行覆盖。

在Spring3.x版本以后,这个问题发生了变化,我们知道Spring3.x里面提供@Configuration注解去声明一个配置类,然后使用@Bean注解实现Bean的声明, 这种方式完全取代了XMl。在这种情况下,(如图)如果我们在同一个配置类里面声明多个相同名字的bean,在Spring IOC容器中只会注册第一个声明的Bean的实例。 后续重复名字的Bean就不会再注册了。像这样一段代码,在Spring IOC容器里面,只会保存UserService01这个实例,后续相同名字的实例不会再加载。

img.png

如果使用@Autowired注解根据类型实现依赖注入,因为IOC容器只有UserService01的实例,所以启动的时候会提示找不到UserService02这个实例。

img.png

如果使用@Resource注解根据名词实现依赖注入,在IOC容器里面得到的实例对象是UserService01, 于是Spring把UserService01这个实例赋值给UserService02,就会提示类型不匹配错误。

img.png

这个错误,是在Spring IOC容器里面的Bean初始化之后的依赖注入阶段发生的。

# @Resource 和 @Autowired 的区别

@Resource@Autowired这两个注解的作用都是在Spring生态里面去实现Bean的依赖注入。

  • @Autowired是Spring里面提供的一个注解,默认是根据类型来实现Bean的依赖注入。@Autowired注解里面有一个required属性默认值是true,表示强制要求bean实例的注入, 在应用启动的时候,如果IOC容器里面不存在对应类型的Bean,就会报错。当然,如果不希望自动注入,可以把这个属性设置成false。

  • 如果在Spring IOC容器里面存在多个相同类型的Bean实例。由于@Autowired注解是根据类型来注入Bean实例的 所以Spring启动的时候,会提示一个错误,大概意思原本只能注入一个单实例Bean,但是在IOC容器里面却发现有多个,导致注入失败。 当然,针对这个问题,我们可以使用 @Primary或者@Qualifier这两个注解来解决。

  • @Primary表示主要的bean,当存在多个相同类型的Bean的时候,优先使用声明了@Primary的Bean。

  • @Qualifier的作用类似于条件筛选,它可以根据Bean的名字找到需要装配的目标Bean。

@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持。它的使用方式和@Autowired完全相同,最大的差异于@Resource可以支持ByName和ByType两种注入方式。

如果使用name,Spring就根据bean的名字进行依赖注入,如果使用type,Spring就根据类型实现依赖注入。 如果两个属性都没配置,就先根据定义的属性名字去匹配,如果没匹配成功,再根据类型匹配。两个都没匹配到,就报错。

总结一下。

  1. @Autowired是根据type来匹配,@Resource可以根据name和type来匹配,默认是name匹配。
  2. @Autowired是Spring定义的注解,@Resource是JSR 250规范里面定义的注解,而Spring对JSR 250规范提供了支持。
  3. @Autowired如果需要支持name匹配,就需要配合@Primary或者@Qualifier来实现。

# Spring IoC的工作流程

IOC是什么

IOC的全称是Inversion Of Control, 也就是控制反转,它的核心思想是把对象的管理权限交给容器。应用程序如果需要使用到某个对象实例, 直接从IOC容器中去获取就行,这样设计的好处是降低了程序里面对象与对象之间的耦合性。使得程序的整个体系结构变得更加灵活。

img.png

Bean的声明方式

Spring里面很多方式去定义Bean,比如XML里面的<bean>标签、@Service、@Component、@Repository、@Configuration配置类中的@Bean注解等等。 Spring在启动的时候,会去解析这些Bean然后保存到IOC容器里面。

img.png

IOC的工作流程

第一个阶段,就是IOC容器的初始化:这个阶段主要是根据程序中定义的XML或者注解等Bean的声明方式,通过解析和加载后生成BeanDefinition,然后把BeanDefinition注册到IOC容器。

img.png

通过注解或者xml声明的bean都会解析得到一个BeanDefinition实体,实体中包含这个bean中定义的基本属性。 最后把这个BeanDefinition保存到一个Map集合里面,从而完成了IOC的初始化。IoC容器的作用就是对这些注册的Bean的定义信息进行处理和维护,它IoC容器控制反转的核心。

第二个阶段,完成Bean初始化及依赖注入:然后进入到第二个阶段,这个阶段会做两个事情,通过反射针对没有设置lazy-init属性的单例bean进行初始化。完成Bean的依赖注入。

img.png

第三个阶段,Bean的使用:通常我们会通过@Autowired或者BeanFactory.getBean()从IOC容器中获取指定的bean实例。 另外,针对设置layy-init属性以及非单例bean的实例化,是在每次获取bean对象的时候,调用bean的初始化方法来完成实例化的,并且Spring IOC容器不会去管理这些Bean。

img.png

# 如何理解Spring Boot中的Starter?

Starter是Spring Boot的四大核心功能特性之一,除此之外,Spring Boot还有自动装配、Actuator监控等特性。 Spring Boot里面的这些特性,都是为了让开发者在开发基于Spring生态下的企业级应用时,只需要关心业务逻辑,减少对配置和外部环境的依赖。

Starter是启动依赖,它的主要作用有几个。

  • Starter组件以功能为纬度,来维护对应的jar包的版本依赖,使得开发者可以不需要去关心这些版本冲突这种容易出错的细节。
  • Starter组件会把对应功能的所有jar包依赖全部导入进来,避免了开发者自己去引入依赖带来的麻烦。
  • Starter内部集成了自动装配的机制,也就说在程序中依赖对应的starter组件以后,这个组件自动会集成到Spring生态下,并且对于相关Bean的管理,也是基于自动装配机制来完成。
  • 依赖Starter组件后,这个组件对应的功能所需要维护的外部化配置,会自动集成到Spring Boot里面,我们只需要在application.properties文件里面进行维护就行了,比如Redis这个starter,只需要在application.properties 文件里面添加redis的连接信息就可以直接使用了。

在我看来,Starter组件几乎完美的体现了Spring Boot里面约定优于配置的理念。

img.png

另外,Spring Boot官方提供了很多的Starter组件,比如Redis、JPA、MongoDB等等。 但是官方并不一定维护了所有中间件的Starter,所以对于不存在的Starter,第三方组件一般会自己去维护一个。

官方的starter和第三方的starter组件,最大的区别在于命名上。官方维护的starter的以spring-boot-starter开头的前缀。 第三方维护的starter是以spring-boot-starter结尾的后缀,这也是一种约定优于配置的体现。

# Spring中 BeanFactory和FactoryBean的区别

首先,Spring 里面的核心功能是IOC容器,所谓IOC容器呢,本质上就是一个Bean的容器或者是一个Bean的工厂。 它能够根据xml里面声明的Bean配置进行bean的加载和初始化,然后BeanFactory来生产我们需要的各种各样的Bean。

所以我对BeanFactory的理解了有两个。

  1. BeanFactory是所有Spring Bean容器的顶级接口,它为Spring的容器定义了一套规范,并提供像getBean这样的方法从容器中获取指定的Bean实例。
  2. BeanFactory在产生Bean的同时,还提供了解决Bean之间的依赖注入的能力,也就是所谓的DI

FactoryBean是一个工厂Bean,它是一个接口,主要的功能是动态生成某一个类型的Bean的实例,也就是说,我们可以自定义一个Bean并且加载到IOC容器里面。 它里面有一个重要的方法叫getObject(),这个方法里面就是用来实现动态构建Bean的过程。Spring Cloud里面的OpenFeign组件,客户端的代理类,就是使用了FactoryBean来实现的。

# Spring中有哪些方式可以把Bean注入到IOC容器?

把Bean注入到IOC容器里面的方式有7种方式:

  1. 使用xml的方式来声明Bean的定义,Spring容器在启动的时候会加载并解析这个xml,把bean装载到IOC容器中。
  2. 使用@CompontScan注解来扫描声明了@Controller、@Service、@Repository、@Component注解的类。
  3. 使用**@Configuration**注解声明配置类,并使用@Bean注解实现Bean的定义,这种方式其实是xml配置方式的一种演变,是Spring迈入到无配置化时代的里程碑。
  4. 使用@Import注解,导入配置类或者普通的Bean
  5. 使用FactoryBean工厂bean,动态构建一个Bean实例,Spring Cloud OpenFeign里面的动态代理实例就是使用FactoryBean来实现的。
  6. 实现ImportBeanDefinitionRegistrar接口,可以动态注入Bean实例。这个在Spring Boot里面的启动注解有用到。
  7. 实现ImportSelector接口,动态批量注入配置类或者Bean对象,这个在Spring Boot里面的自动装配机制里面有用到。

# Spring中Bean的作用域有哪些?

首先呢,Spring 框架里面的IOC容器,可以非常方便的去帮助我们管理应用里面的Bean对象实例。 我们只需要按照Spring里面提供的xml或者注解等方式去告诉IOC容器,哪些Bean需要被IOC容器管理就行了。 其次呢,既然是Bean对象实例的管理,那意味着这些实例,是存在生命周期,也就是所谓的作用域。

理论上来说,常规的生命周期只有两种:

  1. singleton, 也就是单例,意味着在整个Spring容器中只会存在一个Bean实例。
  2. prototype,翻译成原型,意味着每次从IOC容器去获取指定Bean的时候,都会返回一个新的实例对象。

但是在基于Spring框架下的Web应用里面,增加了一个会话纬度来控制Bean的生命周期,主要有三个选择

  1. request, 针对每一次http请求,都会创建一个新的Bean
  2. session,以sesssion会话为纬度,同一个session共享同一个Bean实例,不同的session产生不同的Bean实例
  3. globalSession,针对全局session纬度,共享同一个Bean实例

# Spring Boot的约定优于配置

  1. 首先, 约定优于配置是一种软件设计的范式,它的核心思想是减少软件开发人员对于配置项的维护,从而让开发人员更加聚焦在业务逻辑上。
  2. Spring Boot就是约定优于配置这一理念下的产物,它类似于Spring框架下的一个脚手架,通过Spring Boot,我们可以快速开发基于Spring生态下的应用程序。
  3. 基于传统的Spring框架开发web应用,我们需要做很多和业务开发无关并且只需要做一次的配置,比如:
    1. 管理jar包依赖
    2. web.xml维护
    3. Dispatch-Servlet.xml配置项维护
    4. 应用部署到Web容器
    5. 第三方组件集成到Spring IOC容器中的配置项维护 而在Spring Boot中,我们不需要再去做这些繁琐的配置,Spring Boot已经自动帮我们完成了,这就是约定由于配置思想的体现。
  4. Spring Boot约定由于配置的体现有很多,比如
    1. Spring Boot Starter启动依赖,它能帮我们管理所有jar包版本.
    2. 如果当前应用依赖了spring mvc相关的jar,那么Spring Boot会自动内置Tomcat容器来运行web应用,我们不需要再去单独做应用部署。
    3. Spring Boot的自动装配机制的实现中,通过扫描约定路径下的spring.factories文件来识别配置类,实现Bean的自动装配。
    4. 默认加载的配置文件application.properties 等等。

总的来说,约定优于配置是一个比较常见的软件设计思想,它的核心本质都是为了更高效以及更便捷的实现软件系统的开发和维护。

# Spring中的Bean是线程安全的么?

Spring 中 bean 的默认作用域就是 singleton(单例)的。 除了singleton作用域,Spring 中 bean 还有下面几种作用域:

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

多例Bean:对于prototype作用域的Bean,因为每次getBean的时候,都会创建一个新的对象,也就是线程之间不存在Bean共享问题,因此多例作用域的Bean不存在线程安全问题。

单例Bean:对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,即多线程操作中不会对Bean的成员变量进行查询以外的操作(不存在多个线程同时写这个成员变量的场景),这个单例Bean是线程安全的。如果是有状态的Bean(就是有实例变量的对象,可以保存数据,是非线程安全的。)

解决Spring中单例Bean的有状态的Bean对象的线程的安全问题的解决方案:

  1. 最简单的解决办法就是将有状态的bean的作用域“singleton”改为“prototype” 。
  2. 在bean对象中尽量避免定义可变的成员变量,不过这不太现实。
  3. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中。采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量

# Springboot的启动原理

SpringBoot启动整体可分为两步:

  1. 初始化一个SpringApplication对象。
  2. 执行该对象的run()方法。
@Target({ElementType.TYPE})//注解的适用范围,其中TYPE用于描述类、接口(包括注解类型)或枚举类型
@Retention(RetentionPolicy.RUNTIME)//注解的声明周期,保留到class文件中(三个声明周期)
@Documented//表名这个注解应该被javadoc记录
@Inherited//表名子类可以继承这个注解
@SpringBootConfiguration//继承了@Configuration注解,表示当前类是注解类
@EnableAutoConfiguration//开启SpringBoot的自动注解功能,器主要借助@import注解实现的
@ComponentScan(excludeFilters = {//扫描路径配置(具体使用待配置)
	@Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), 
	@Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})
	}
)
public @interface SpringBootApplication {
	...
}

从上述我们可以看出,实际上@SpringBootApplication是一复合的注解,其中起到主要作用的注解是:

  1. @SpringBootConfiguration:继承了@Configuration注解,表示当前类是注解类
  2. @EnableAutoConfiguration:开启SpringBoot的自动注解功能,器主要借助@import注解实现的
  3. @ComponentScan:扫描路径配置(具体使用待配置)

@EnableAutoConfiguration注解解析

@EnableAutoConfiguration:开启自动注解的理念和工作原理和他们是一脉相承。简单的来说该注解是借助@Import注解的支持来实现的,Spring的IoC容器会收集和注册特定场合相关的Bean实例:

  1. @EnableScheduling自动调度:是通过@import将Spring调度框架相关的Bean都加载到IoC容器中。
  2. @EnableMBeanExport监控JVM运行时状态是通过@Import将JMX相关的Bean定义加载到IoC容器中。
  3. @EnableAutoConfiguration开启自动注解:是通过@Import将所有复合配置条件的Bean定义加载到IoC容器中,仅此而已。

最关键的@EnableAutoConfiguration主要就是借助AutoConfigurationImportSelector.class来帮助SpringBoot应用将所有符合条件的@Configuration标注的配置类都加载到当前SpringBoot创建并使用的IoC容器中,就像一个收割机一样,全文搜索配置类:

img.png

SpringFactoriesLoader

SpringFactoriesLoader属于Spring框架专属的一种扩展方案,其功能和使用方式类似于Java的SPI方案:java.util.ServiceLoader, 它的主要功能就是从指定的配置文件META-INF/spring.factories中加载配置,spring.factories是一个非常经典的java.properties文件, 内容格式是key-value形式,只不过这key以及value都非常特殊,为Java类的完整类名和包名,(Fully qualified name)

对于@EnableAutoConfiguration来说,SpringFactoriesLoader的用途和其本意稍有不同,他本意是为了提供SPI扩展,而在@EnableAutoConfiguration场景下, 它更多的是提供一种配置查找的功能的支持,也就是根据@EnableAutoConfiguration的完整类名org.springframework.boot.autoconfigure.EnableAutoConfiguration作为key来获取一组对应的@Configuration类:

总结来说,@EnableAutoConfiguration能实现自动配置的原理就是:SpringFactoriesLoader从classpath类路径下去搜寻所有的META-INF/spring.factories文件, 并将其中key为org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的Value配置项通过反射的方式实例化为对应的标注为@Configuration的JavaConfig形式的IoC容器配置类,然后汇总到当前使用的IoC容器中。

@ComponentScan 注解解析

@ComponentScan注解很重要,它对应XML配置中的元素,@ComponentScan的功能就是自动扫描并加载符合条件的组件(如@Component和@Repository)或Bean定义, 最终将这些Bean定义加载到当前使用的IoC容器中。 我们可以通过basePackages等属性来细粒度的定制@ComponentScan自动扫描的范围,如果不指定,则默认spring框架实现会从声明@ComponentScan所在类的package进行递归扫描。

# springboot的流程

  1. 加载启动类:Spring Boot应用程序的入口是一个Java类,通常带有'main'方法,在这个类中,你需要创建一个Spring Boot应用程序上下文并启动它。这个类被称为启动类,它会加载Spring Boot的基础设置。
  2. 构建应用程序上下文:启动类中的main方法通常会使用SpringpopApplication.run()方法来构建Springboot应用留序上下文,这个方法会启动 SpingBoot应用程序,加载各种配置和组件。
  3. 自动配置:在构建应用程序上下文的过程中,Spring Boot会自动扫描类路径上的各种组件、配置文件和类,并根据条件进行自动配置。这包括自动装配、属性值填充等操作。
  4. 加载外部属性:Spring Boot会加载各种外部属性文件,包括(aplication.poperties 或 aplication.ymnl,这些文件中包含了应用程序的配置信息。这些属性可以在应用程序中使用,以控制不同组件的行为。
  5. 创建Bean实例: Spring Boot使用Spring loC(控制反转)容器来管理应用程序中的各种Bean实例。在应用程序上下文构建过程中,SprinBoot会创建和管理这些Bean,以供后续的组件使用。
  6. 启动内嵌服务器:如果应用程序是一个Web应用程序,Spring Boot会在启动过程中自动配置和自动内嵌的Web服务器(如Tomcat.Jetty或Undetow),这使得应用程序是可以直接通过浏览器或客户端访问。
  7. 执行初始化和回调:在应用程序上下文构建完成后,Spring Boot会执行各种初始化和回调操作,例如调用ApplicatiorRunner或comandineRunner实现类的方法,以执行一些应用程序启动时的逻辑。
  8. 应用程序运行:一旦应用程序上下文构建完成,Web服务器启动并监听请求,应用程序便开始运行,等待客户端请求的到来.

img.png

img.png

# springboot的配置流程

img_1.png