spring知识点
1. Spring Bean的生命周期#
Spring Bean的生命周期主要包括以下几个阶段:
- 实例化(Instantiation):当Spring容器接收到对Bean的请求时,它将使用Java反射机制创建一个新的Bean实例。
- 属性赋值(Properties Setting):Spring容器通过依赖注入(Dependency Injection)等方式为Bean设置属性值。
- 初始化(Initialization):在Bean初始化之前,Spring容器会检查是否有实现了InitializingBean接口的类和配置文件中的init-method方法,如果有则会执行其中定义的初始化逻辑。
- 使用(In Use):此时Bean已经可以被应用程序使用。
- 销毁(Destruction):在Bean实例不再需要或者容器关闭时,Spring容器会调用实现了DisposableBean接口的类和配置文件中的destroy-method方法来释放Bean占用的资源。
需要注意的是,后两个阶段的执行顺序与实现方式有关。如果同时存在实现了InitializingBean和init-method接口,那么它们会按照InitializingBean -> init-method的顺序执行;同样,如果同时存在实现了DisposableBean和destroy-method接口,那么它们会按照destroy-method -> DisposableBean的顺序执行。
此外,Spring还提供了BeanPostProcessor接口,它允许开发人员在Bean实例化、属性赋值和初始化过程中插入自己的代码逻辑,从而灵活地控制Bean的生命周期。
2.Spring bean 在什么场景会被销毁,在什么时候进行销毁#
Spring Bean在以下场景下需要被销毁:
- 资源占用:如果Bean持有一些资源,例如数据库连接对象、文件句柄等,这些资源在离开作用域后需要被及时释放,否则可能会导致资源浪费或者系统崩溃。
- 缓存清理:如果Bean负责缓存数据,那么在系统关闭前需要清空缓存,以避免数据泄露或者不一致。
- 生命周期管理:如果Bean的生命周期与应用程序上下文不同步,那么需要手动销毁Bean以确保内存和资源的正确释放。
- 安全性:如果Bean中包含敏感信息,例如密码、密钥等,需要确保在应用程序结束时将其安全地销毁,以避免信息泄漏。
当Bean不再需要使用或者系统即将关闭时,需要对其进行销毁操作,以保证系统的正常运行和资源的高效利用。
Spring Bean的销毁时机通常有两种情况:
-
当应用程序上下文(ApplicationContext)关闭时,所有的Bean都会被销毁。在应用程序上下文关闭之前,Spring容器会默认调用所有实现了DisposableBean接口的Bean的destroy()方法。也可以通过配置文件中的"destroy-method"属性或者@Bean注解的destroyMethod属性指定对应的方法来实现销毁。
-
当某个Bean不再被需要时,可以通过配置文件或者注解等方式来指定它的销毁时机。例如,在配置文件中使用
标签的destroy-method属性或者在Java类中使用@PreDestroy注解,指定一个特定的方法作为Bean的销毁方法。当Bean被移除时,Spring容器会自动调用该方法来释放资源。
在Spring Boot应用中,如果Bean实现了AutoCloseable接口,则Spring Boot会自动管理Bean的生命周期和销毁,无需手动配置销毁方法。
3.IOC和AOP的理解#
Spring框架中的IOC(Inversion of Control,控制反转)和AOP(Aspect-Oriented Programming,面向切面编程)是两个核心概念。
IOC指的是通过容器来管理对象及其依赖关系,将对象之间的依赖关系交由Spring容器来维护。通俗点说,就是将创建、组装对象的控制权交给了Spring容器,而不是我们手动创建和组装每一个对象。
AOP指的是一种编程范式,它可以将应用程序中的业务逻辑和系统级服务分离开来,并以横切面的方式将这些代码模块化。横切面的代码包括日志记录、安全性检查、事务处理等与业务无关的代码,通过AOP的技术实现,可以使得这些代码更加方便地维护和重用。
在Spring中,AOP主要通过切面(Aspect)、连接点(Join Point)、通知(Advice)、切入点(Pointcut)等概念来实现。AOP能够帮助我们将跨越多个类的功能分离出来,提高代码复用性和可维护性。
总的来说,IOC和AOP是Spring框架中非常重要的两个核心概念,IOC负责对象依赖的管理和创建,AOP负责横切关注点的解耦和模块化。
4.spring bean 自动装配有哪几种方式#
- 根据类型自动装配:根据bean的类型来自动装配,使用@Autowired注解实现。
- 根据名称自动装配:根据bean的名称来自动装配,使用@Autowired注解结合@Qualifier注解实现。
- 构造器自动装配:根据构造函数参数来自动装配,使用@Autowired注解在构造函数参数上实现。
- 使用@Resource注解自动装配:和@Autowired类似,也可以根据名称或类型来自动装配,但@Resource注解提供了更灵活的选项
5.spring的优点#
- 简化开发:Spring提供了强大的IOC容器和AOP机制,可以帮助我们更加方便地管理对象,将业务逻辑与系统级服务分离。
- 支持多种数据访问技术:Spring提供了对多种数据访问技术的支持,包括JDBC、ORM、NoSQL等,使得我们可以非常方便地访问数据库和其他外部资源。
- 提高代码重用性和可维护性:通过使用Spring的IOC容器和AOP机制,我们可以将代码模块化,达到更好的代码复用性和可维护性。
- 内置事务管理:Spring内置了事务管理机制,可以帮助我们更加方便地管理事务,确保数据的一致性。
- 可扩展性强:Spring采取模块化设计,允许我们根据需求选择不同的模块进行组合,从而实现更灵活的应用程序架构。
- 社区庞大:Spring拥有庞大的开发者社区和完善的文档资料,可以帮助我们更好地理解和使用框架。
6.spring的启动流程#
Spring容器的启动流程大概包括以下几个步骤:
- 加载配置文件:Spring通过读取XML、注解或者JavaConfig等方式来加载配置文件,获取Bean定义的信息。
- 创建Bean实例:根据Bean定义信息,Spring通过反射机制创建Bean实例。
- 注入属性:根据依赖注入的规则,Spring会将Bean所依赖的其他Bean或者简单类型属性进行注入。
- 调用初始化方法:如果Bean实现了InitializingBean接口或者在配置文件中指定了初始化方法,Spring会调用Bean的初始化方法进行一些预处理操作。
- 完成Bean的装配:当所有Bean实例被创建并完成初始化后,Spring容器将完成Bean之间的装配,即使用各个Bean之间的引用关系建立起完整的应用程序对象模型。
- 启动容器:完成Bean的装配后,Spring容器会执行特定的回调方法(如ApplicationContextAware)通知Bean自身容器的相关信息。然后,Spring容器开始对应用程序的控制权进行管理,此时容器已启动并进入运行状态。
以上是Spring容器的基本启动流程,其中涉及到很多细节和特殊情况,例如Spring的延迟加载、作用域、后置处理器等,需要具体问题具体分析。
7.spring的九大后置处理器:https://www.jianshu.com/p/56c24aa6e638#
- 第一次调用后置处理器org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#resolveBeforeInstantiation
调用的是InstantiationAwareBeanPostProcessor --> postProcessBeforeInstantiation方法
当一个bean在实例化之前,判断这个bean要不要产生一些新的对象,InstantiationAwareBeanPostProcessor的postProcessBeforeInstantiation方法可以返回任何类型,如果返回的对象不为null,就调用beanPostProcessor的postProcessAfterInitialization方法;后面的初始化、属性注入、实例化等方法都不会再调用
如果返回null,就正常的执行流程
在spring AOP当中,spring如果判断当前类100%不需要进行增强(判断当前bean是否是advice、pointcut、advisor、aopInfrastructureBean的子类,如果是,无需增强),会把这个bean放到一个map中,并将value置为false,那么在后面进行增强的时候,会排除这个map中的bean
-
第二次调用后置处理器,该后置处理器推断使用哪个构造函数来初始化bean对象
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#determineConstructorsFromBeanPostProcessors
在推断使用哪一个构造函数的时候,会首先判断当前构造函数是否有@Value和@Autowired注解,如果没有,那就校验当前构造方法对应的bean和传来的beanClass是否一样,如果是同一个,就把当前构造函数赋值给defaultConstructor
在第二次调用后置处理器的时候,会返回当前可用的构造函数,由此来决定,使用哪个构造函数来创建bean -
第三次调用后置处理器
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyMergedBeanDefinitionPostProcessors
调用的是MergedBeanDefinitionPostProcessor --> postProcessMergedBeanDefinition 第三个后置处理器,是后面生命周期流程中的某些流程缓存一些meta信息
比如:在CommonAnnotationBeanPostProcessor和AutowiredAnnotationBeanPostProcessor的postProcessorMergedBeanDefinition的方法中,会调用finAutowiringMetadata和findResourceMetadata方法,将当前bean所依赖的bean(@Autowired和@Resource注解)存到一个map中,后面在进行属性注入的时候,会先从这个map中找当前bean依赖的bean有哪些,如果map中为空,就再查找要注入的属性有哪些
再比如:在CommonAnnotationBeanPostProcessor的这个方法中,将@PostConstrct和@PreDestroy注解对应的方法,缓存起来,在后面调用初始化的后置处理器的时候,先从这里存的map中找方法,找到,就直接执行即可
这些提前缓存的操作,都是在这个后置处理器完成的 -
第四次调用后置处理器
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#getEarlyBeanReference
SmartInstantiationAwareBeanPostProcessor --> getEarlyBeanReference 第四个后置处理器(把创建的对象 放到earlySingletonObjects,解决循环依赖的),处理循环依赖问题会用到这个后置处理器
这里通过后置处理器,暴露出一个ObjectFactory(个人理解是一个bean工厂),可以完成bean的实例化等操作 -
第五次调用后置处理器
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean--InstantiationAwareBeanPostProcessor--postProcessAfterInstantiation
调用的是InstantiationAwareBeanPostProcessor --> postProcessAfterInstantiation 第五个后置处理器(判断是否需要填充属性)
如果我们需要在程序中自己注入属性,可以利用这个点,在这里返回false,那么spring就不会调用下面这个后置处理器来注入属性 -
第六次调用后置处理器
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean--InstantiationAwareBeanPostProcessor--postProcessPropertyValues
调用的是InstantiationAwareBeanPostProcessor --> postProcessPropertyValues 第六个(处理类的属性值)
主要是CommonAnnotationBeanPostProcessor(用来处理@Resource注解)和AutowiredAnnotationConigApplication(处理@Autowired和@Value注解);如果是自动注入(AutowireMode不为null),是无需通过后置处理器来进行属性注入的 -
第七次调用后置处理器
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization
调用的是BeanPostProcessor --> postProcessBeforeInitialization bean初始化方法执行之前执行这个方法
CommonAnnotationBeanPostProcessor 继承了 InitDestroyAnnotationBeanPostProcessor,在该后置处理器处理的是@postconstruct注解 -
第八次调用后置处理器
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization
调用的是BeanPostProcessor --> postProcessAfterInitialization bean初始化之后执行的方法(处理AOP) -
第九次是在销毁bean容器的时候调用的
在调用ac.close()方法的时候,会调用该后置处理器 org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor#postProcessBeforeDestruction
处理 @PreDestroy注解,destroy-method和destroy()方法
8.Spring 框架中都用到了哪些设计模式#
Spring框架中用到了很多设计模式,以下是其中一些常见的设计模式:
- 单例模式(Singleton Pattern):Spring默认情况下创建的Bean都是单例的。
- 工厂模式(Factory Pattern):Spring使用工厂模式创建和管理Bean实例。
- 代理模式(Proxy Pattern):Spring使用AOP代理实现切面编程。
- 模板方法模式(Template Method Pattern):Spring中的JdbcTemplate、HibernateTemplate等都是基于模板方法模式实现的。
- 观察者模式(Observer Pattern):Spring使用观察者模式实现事件驱动机制。例如,当ApplicationContext初始化完成时,会触发ContextRefreshedEvent事件。
- 装饰器模式(Decorator Pattern):Spring中的Interceptor就是装饰器模式的应用,可以在不修改原始类的情况下为其添加新的功能。
- 适配器模式(Adapter Pattern):Spring中的适配器模式通常用于将不兼容的接口转化为可兼容的接口,使得不同的组件能够协同工作。
- 策略模式(Strategy Pattern):Spring中的策略模式常常用于在运行时选择不同的算法或行为,例如使用TaskExecutor接口可以在运行时选择不同的线程池实现。
除此之外,还有许多其他的设计模式在Spring框架中也有应用到,例如工厂方法模式、建造者模式、迭代器模式、命令模式等等。
9.spring 的五大作用域#
- Singleton(单例):在整个应用程序中只创建一个 bean 实例。
- Prototype(原型):每次请求时都会创建一个新的 bean 实例。
- Request(请求):在一次 HTTP 请求中,每个 bean 都会被创建一个实例,并且仅在当前请求范围内有效。
- Session(会话):在用户会话期间创建一个 bean 实例,并且仅在该会话范围内有效。
- Global session(全局会话):在 Portlet 环境下使用,表示整个 Web 应用程序中只要有一个 Portlet 使用该 bean,就会创建一个实例,Spring5已经没有了。
10.spring的常用注解#
Spring框架中常用的注解如下:
- @Autowired:自动装配Bean,可以使用在字段、setter方法、构造函数上。
- @Component:标记类为一个可被Spring容器管理的Bean。
- @Controller:标记类为Spring MVC中的控制器。
- @Service:标记类为业务处理层组件。
- @Repository:标记类为数据访问层组件。
- @RequestMapping:映射请求路径到控制器的处理方法上。
- @PathVariable:将URL中的占位符参数绑定到处理方法的入参上。
- @RequestParam:获取请求参数并绑定到处理方法的入参上。
- @ResponseBody:将返回值序列化成JSON或XML格式并作为响应体返回客户端。
- @Transactional:声明事务性方法。
除了以上提到的注解,Spring还支持很多其他的注解,如@Value、@Profile、@Async等。这些注解可以帮助我们更加方便地进行配置和开发,提高开发效率。
11.@Autowired和@Resource以及@Inject等注解注入有何区别#
@Autowired、@Resource和@Inject都是Java中用来进行依赖注入的注解,它们的主要区别如下:
- @Autowired:由Spring提供,支持按照类型(Type)或名称(Name)进行自动注入。如果有多个相同类型或名称的bean,则需要使用@Qualifier注解指定具体的bean。
- @Resource:由JavaEE提供,支持按照名称(Name)进行自动注入。如果没有指定名称,则默认使用属性名称作为名称查找对应的bean。
- @Inject:与@Autowired类似,是由JavaEE提供的注解,支持按照类型(Type)进行自动注入。如果有多个相同类型的bean,则需要使用@Qualifier注解指定具体的bean。
总的来说,这些注解都可以完成依赖注入的功能,但具体使用哪种注解还是要根据实际情况来选择。在Spring框架中,一般使用@Autowired注解进行依赖注入。而在JavaEE环境下,推荐使用@Resource或@Inject注解。
12.BeanFactory和ApplicationContext有什么区别#
BeanFactory和ApplicationContext是Spring框架中两个重要的容器接口,它们有以下区别:
- BeanFactory是Spring的核心接口,提供了一种基本的方式来获取Bean对象。ApplicationContext是BeanFactory的子接口,在BeanFactory的基础上提供了更多的功能,例如事件发布、国际化处理、Web应用支持等。
- BeanFactory是延迟初始化的,即当需要获取某个Bean时才会进行实例化。而ApplicationContext在启动时就对所有的Bean进行了实例化。
- BeanFactory是轻量级的,只提供最基本的功能。ApplicationContext则是重量级的,提供了更多的Enterprise级特性。
- ApplicationContext可以自动绑定Bean之间的依赖关系,而BeanFactory则不能。
- 在资源的处理方式不同:ApplicationContext可以从更多类型的资源(例如文件系统、类路径、URL等)中加载配置信息,而BeanFactory只能从类路径下加载配置信息。
因此,如果仅需要基本的Bean容器功能,可以使用BeanFactory;如果需要更多的高级特性,例如自动绑定、事件发布等,则建议使用ApplicationContext。
13.如何使用 BeanFactory 创建bean#
使用 BeanFactory 创建 Bean 需要完成以下步骤:
- 导入 Spring 相关的 jar 包,并添加到项目的类路径下。
- 编写 XML 配置文件,定义要创建的 Bean 对象及其属性。
- 创建 Spring 的 BeanFactory 对象,并指定 XML 配置文件所在的路径。
- 通过 BeanFactory 对象获取 Bean 实例,可以使用 getBean() 方法来获取。
下面是一个简单的示例代码:
// 加载 XML 配置文件
Resource resource = new ClassPathResource("applicationContext.xml");
BeanFactory factory = new XmlBeanFactory(resource);
// 获取指定的 Bean 实例
HelloWorld obj = (HelloWorld) factory.getBean("helloWorld");
// 使用 Bean 实例
obj.getMessage();
其中,“applicationContext.xml” 是定义 Bean 的配置文件,"helloWorld" 是该 Bean 的 ID。注意,在使用 BeanFactory 创建 Bean 时,只有在获取 Bean 实例时才会进行实例化。
14.Spring框架中的Bean是线程安全的吗?如果线程不安全,要如何处理?#
Spring框架中的Bean是否线程安全取决于具体实现和配置。默认情况下,Spring容器中单例的Bean是线程不安全的,因为它们由多个线程共享。
要使Bean线程安全,可以使用以下方法之一:
- 将Bean的作用域(scope)更改为原型(prototype),这样每次请求Bean时都会创建一个新的实例,这些实例不会共享任何状态。
- 在Bean中使用同步(synchronization)来确保线程安全。
- 使用线程安全的类或数据结构来存储Bean的状态,例如ConcurrentHashMap。
需要注意的是,将Bean的作用域更改为原型可能会影响应用程序的性能,因为每次请求Bean时都需要创建新的实例。因此,在决定如何处理线程安全问题时,需要权衡性能和线程安全性。
15.spring的bean为什么线程不安全,不安全示例#
Spring框架中的默认作用域(scope)是singleton,也就是单例模式。这意味着在容器启动时,所有的singleton bean都会被实例化并且只有一个实例存在于应用程序的整个生命周期中。因此,当多个线程同时访问同一个singleton bean时,可能会发生线程安全问题。
以下是一个简单的示例,说明了在使用单例bean时可能会出现线程安全问题:
@Component
public class MyCounter {
private int count = 0;
public int incrementAndGet() {
return ++count;
}
}
上述代码创建了一个计数器类MyCounter
,其中incrementAndGet()
方法对计数器进行递增操作并返回当前计数值。由于此类被声明为Spring容器中的单例bean,默认情况下,在多个线程中同时调用incrementAndGet()
方法可能导致计数器出现线程安全问题,因为多个线程可能会同时修改和读取count
变量的值。
16.beanFactory 和 FactoryBean的区别#
BeanFactory是Spring框架中的一个核心接口,用于管理和获取Bean对象的实例。
而FactoryBean是一个特殊的Bean,在Spring容器启动时会自动调用其中的getObject()方法返回一个实例化的Bean对象。它允许开发人员在Bean实例化的过程中进行更加灵活的控制,例如可以通过FactoryBean创建代理对象、懒加载等。
因此,BeanFactory是负责管理和获取Bean对象实例的工厂接口,而FactoryBean则是在Bean实例化过程中进行更加灵活控制的一种机制。
17.使用 FactoryBean创建bean示例#
使用Spring中的FactoryBean创建Bean实例需要遵循以下步骤:
- 创建一个类,实现FactoryBean接口,并在该类中实现getObject()方法和getObjectType()方法。getObject()方法返回一个Bean实例,而getObjectType()方法返回该Bean的类型。
- 在XML配置文件中定义该FactoryBean,并指定FactoryBean所创建Bean的类名和相关属性信息。
- 将FactoryBean作为普通的Bean进行注入或自动装配,Spring容器将自动调用FactoryBean的getObject()方法创建Bean实例并注入到目标对象中。
示例代码如下:
public class MyBeanFactory implements FactoryBean<MyBean> {
@Override
public MyBean getObject() throws Exception {
return new MyBean();
}
@Override
public Class<?> getObjectType() {
return MyBean.class;
}
}
<bean id="myFactoryBean" class="com.example.MyBeanFactory">
</bean>
<bean id="myBean" factory-bean="myFactoryBean" factory-method="getObject">
</bean>
在上述示例中,MyBeanFactory实现了FactoryBean接口,并重写了getObject()方法和getObjectType()方法。然后,在XML配置文件中定义了一个名为myFactoryBean的Bean,并在该Bean中指定了要创建的Bean实例类型为MyBean。最后,在另一个名为myBean的Bean中直接引用了myFactoryBean,并通过factory-bean和factory-method属性告诉Spring容器使用myFactoryBean创建myBean实例。
18.spring BeanDefinition存储的信息#
在Spring框架中,BeanDefinition是用于描述一个Bean实例的元数据信息的接口。每个BeanDefinition都包含了该Bean实例的类名、作用域、构造函数参数、属性值、初始化方法、销毁方法等关键信息。
具体来说,BeanDefinition主要有以下作用:
- 定义Bean实例:通过BeanDefinition可以定义一个Bean实例的类名和相关属性,从而告诉Spring容器如何创建该实例。
- 管理Bean实例的作用域:BeanDefinition还可以定义Bean实例的作用域,例如Singleton、Prototype、Request、Session等多种作用域类型,从而控制Bean实例的生命周期和使用范围。
- 支持Bean实例化前后的处理:BeanDefinition还可以定义Bean实例化前后的处理,例如设置Bean实例化时需要注入的依赖、调用Bean初始化方法、执行Bean销毁方法等操作。
总之,BeanDefinition是Spring框架核心机制之一,它为Spring容器提供了管理和配置Bean实例的标准化接口,以达到灵活、可扩展的IoC(控制反转)和DI(依赖注入)效果。
19.spring 单例池中存的是bean 还是对象#
在Spring框架中,单例池(Singleton Pool)中存储的是Bean实例而不是对象。具体来说,Spring容器会根据配置信息和Bean定义创建对应的Bean实例,并将这些Bean实例缓存在单例池中供后续使用。
在Spring中,Bean实例是由Bean工厂根据BeanDefinition创建的。BeanDefinition描述了一个Bean实例的元数据信息,如类名、作用域、属性等等,而Bean工厂则根据BeanDefinition创建Bean实例并进行管理。
需要注意的是,Bean实例可以被多个对象引用,因此在单例池中存储的是共享的Bean实例而不是私有的对象。这意味着,在获取从单例池中获取Bean实例时,每个对象都将获得同一个Bean实例,这样就可以避免重复创建和浪费资源的问题。
20.spring 如何解决循环依赖:https://blog.51cto.com/u_14201949/2832860#
Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者或属性是可以延后设置的。
Spring单例对象的初始化其实可以分为三步:
createBeanInstance, 实例化,其实也就是调用对象的构造方法实例化对象
populateBean,填充属性,这一步主要是对bean的依赖属性进行注入(@Autowired)
initializeBean,调用spring xml中指定的init方法,比如一些形如initMethod、InitializingBean等方法
循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。
那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
首先我们看源码,三级缓存主要指:
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
这三级缓存分别指:
singletonObjects:单例对象的cache
earlySingletonObjects :提前暴光的单例对象的Cache,存放的是没有初始化完全的对象
singletonFactories : 单例对象工厂的cache,没有经过后置处理且没有初始化完全的对象的工厂。加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决 ,工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象,去获取包装后的bean,或者说,代理后的bean。
我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:
// allowEarlyReference: 是否允许从singletonFactories中通过getObject拿到对象
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
Object singletonObject = this.singletonObjects.get(beanName);
// isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
上面的代码需要解释两个参数:
isSingletonCurrentlyInCreation()判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。)
allowEarlyReference: 是否允许从singletonFactories中通过getObject拿到对象
分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取该对象的工厂,使用工厂获取对象,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。
比如A、B两个bean相互依赖,他们的注入过程如下:
A首先完成了初始化的第一步,即使用无参构造创建bean,并且将自己的工厂提前曝光到singletonFactories中,然后进行初始化的第二步属性填充,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以需要先创建B,B在属性填充的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories获取到了,因为A在属性填充之前就将自己的工厂提前曝光在了ObjectFactory中,所以B能够通过A对象的工厂拿到创建了一半的A对象,B拿到A对象后顺利完成了创建对象的三个步骤,完全初始化之后将自己放入到一级缓存singletonObjects中。此时返回A中,A此时能拿到B的对象顺利完成自己初始化的剩下阶段,最终A也完成了初始化,被装入了一级缓存singletonObjects中。所以循环依赖问题就被解决了。
简单来说就是:
为了避免循环依赖,在Spring中创建bean的原则是不等bean创建完成就会将创建bean的ObjectFactory提早曝光加入到缓存中,一旦下一个bean创建时候需要依赖上一个bean则直接使用ObjectFactory 。然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);随后将对象从ObjectFactory三级缓存放入earlySingletonObjects二级缓存以提高其他对象获取该引用的效率,因为可以少尝试一次缓存。
循环依赖是指两个或多个Bean之间相互依赖,形成一个环状结构。Spring框架通过使用三级缓存(三级Map)来解决循环依赖问题。具体地,当一个Bean需要依赖另一个Bean时,Spring会首先创建被依赖的Bean,并将其放入第一级缓存中。接着,Spring会创建依赖该Bean的Bean,并将其放入第二级缓存中,在这个过程中如果发现依赖的Bean已经在第一级缓存中存在,则直接返回第一级缓存中的Bean实例。如果第一、第二级缓存均未找到对应的Bean实例,Spring会尝试去创建这个Bean,并在创建完成后将其放入第三级缓存中。然后,Spring会回到第二级缓存中,继续创建其他的Bean,如果在这个过程中需要使用到第三级缓存中的Bean,则从第三级缓存中取出该Bean实例,并注入到所需对象中。
最后,当所有的Bean都创建完成后,Spring会将第三级缓存中的所有Bean注入到它们所依赖的对象中,完成整个Bean的初始化过程。这样,就可以有效地解决循环依赖问题。
下面是一个简单的Spring循环依赖示例:
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
在上述示例中,类A依赖于类B,同时类B也依赖于类A,这就形成了一个循环依赖。
在使用Spring容器创建这两个Bean时,会发生如下情况:
- Spring会先创建A对象,并将其放入一级缓存中。
- 接着,Spring会创建B对象,并注入A对象到B中。此时由于A还没有创建完成,因此Spring会将B对象放入二级缓存中。
- 然后,Spring继续创建A对象,并注入B对象到A中。此时由于B已经被创建过了,因此可以直接从一级缓存中获取B对象并注入到A中。
- 最后,Spring会将A和B对象放入一级缓存中,完成Bean的创建过程。
从上述过程可以看出,在存在循环依赖的情况下,Spring通过使用三级缓存来解决循环依赖的问题。
21.spring 为什么无法解决构造器循环依赖#
Spring无法解决构造器循环依赖是因为在构造器注入时,所有需要注入的依赖都需要先创建出来才能进行注入。如果存在构造器循环依赖,那么就会出现一个死锁的情况,即每个实例都在等待另一个实例被创建完毕才能完成自己的创建。
为了避免这种情况,Spring使用了一种延迟初始化的方式来解决循环依赖问题。具体来说,当Spring发现两个bean之间存在循环依赖时,它会先创建所有的bean对象,但不会立即注入它们之间的依赖关系。而是在创建完毕后,通过属性注入或者方法注入的方式来解决循环依赖问题。这样可以保证所有的bean对象都已经创建好了,避免了死锁的情况。
22.spring 为什么要用三级缓存处理循环依赖,为什么不用二级缓存即可#
其实粗略划分是两种对象,已创建完成的和未创建完成的。但是再细分其实可以划分了三种对象,或者三个阶段的对象,已创建完成的,未创建完成但已经后置处理过的,未创建完成且未后置处理的。
第二级缓存中存放的是没有经过后置处理且没有初始化完全的对象的工厂,如果把经过了后置处理的和没经过后置处理的对象都放在第二级缓存中,这显然会干扰我们程序的运行。所以需要先暴露在第三级缓存中,从第三级缓存中取出对象的时候判断是否需要经过后置处理,如果需要那么经过后置处理后再放入第二级缓存。使用三级而非二级缓存并非出于IOC的考虑,而是出于AOP的考虑,即若使用二级缓存,在AOP情形下,注入到其他bean的,不是最终的代理对象,而是原始对象。
在将三级缓存放入二级缓存的时候,getEarlyBeanReference()会判断是否有SmartInstantiationAwareBeanPostProcessor这样的后置处理器,判断是否用对对这个对象进行了应用逻辑修或者应用逻辑增强,比如说判断是否实现了AOP逻辑,如果实现了AOP逻辑,那就需要执行AOP逻辑后把代理对象放入第二级缓存,而非是原始对象。改换句话说 getEarlyBeanReference() 这里是给用户提供接口扩展的,所以采用了三级缓存
加个三级缓存,里面不存具体的bean,里面存一个工厂对象。通过工厂对象,是可以拿到最终形态的代理后的egg。
然后调用AOP的后置处理器类:getEarlyBeanReference,拿到代理后的bean(假设此处切面满足,要创建代理);
bean的工厂一直存在于第三级工厂缓存中,直到第一个对象需要该引用时才会获取该工厂,并将该工厂获取的对象放到第二级缓存中,这是实际上是起到了一种延时暴露的机制,在没有对象需要这个引用之前,这个对象都不会出现在第二级缓存中,可能这样可以提高性能
其实按道理是可以二级缓存就可以初始化一些内容,但是在这里
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
Object exposedObject = bean;
if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
}
}
}
return exposedObject;
}
getEarlyBeanReference的作用:调用SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference()这个方法 否则啥都不做,也就是给调用者个机会,自己去实现暴露这个bean的应用的逻辑,比如在getEarlyBeanReference()里可以实现AOP的逻辑 参考自动代理创建器AbstractAutoProxyCreator 实现了这个方法来创建代理对象
Spring容器会将每一个正在创建的Bean 标识符放在一个 singletonsCurrentlyInCreation“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,而对于创建完毕的Bean将从当前创建Bean池中清除掉。
23.spring的一,二,三级缓存分别存储什么内容#
- 一级缓存(singletonObjects):该缓存用于存储单例Bean实例。当一个单例Bean被创建后,它会被放入该缓存中,下次需要使用这个Bean时直接从该缓存中获取即可。
- 二级缓存(earlySingletonObjects):该缓存用于存储正在创建中的单例Bean实例。在创建单例Bean时,如果发现它所依赖的另一个单例Bean正在创建过程中,则将该Bean实例放入二级缓存中,等待其创建完成后再进行注入。
- 三级缓存(singletonFactories):单例对象工厂的cache,没有经过后置处理且没有初始化完全的对象的工厂。加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决 ,工厂对象中,有3个field,一个是beanName,一个是RootBeanDefinition,一个是已经创建好的,但还没有注入属性的bean,如果在2级缓存中,还是没找到,则在3级缓存中查找对应的工厂对象,利用拿到的工厂对象,去获取包装后的bean,或者说,代理后的bean
需要注意的是,以上缓存都是在Spring容器启动时就已经初始化好了的。同时,这些缓存都是线程安全的,因为Spring容器本身就是基于线程安全的设计实现的。
24.Spring事务的实现方式和原理以及隔离级别#
Spring事务的实现方式:
Spring中的事务实现是基于AOP(面向切面编程)和JDBC事务API的。在Spring中,我们可以通过声明式事务管理和编程式事务管理两种方式来进行事务操作。
- 声明式事务管理
声明式事务管理是指使用Spring AOP机制将事务切面织入到业务逻辑代码中,从而实现对事务的统一管理。它的核心是将事务的配置信息从业务逻辑中剥离出来,以声明的方式集中管理。在Spring中,我们可以通过XML配置或注解方式来实现声明式事务管理。
- 编程式事务管理
编程式事务管理是指通过编写代码来控制事务的提交和回滚。在Spring中,我们可以使用TransactionTemplate或者直接使用底层的事务管理器来实现编程式事务管理。
Spring事务的原理:
Spring事务的原理是通过AOP代理机制,在方法执行前后加入事务的处理逻辑。具体而言,对于被@Transactional注解标记的方法,Spring会根据注解的属性值创建一个事务拦截器,并将其织入到方法调用链中。当方法被调用时,事务拦截器会在方法执行前开启事务,在方法执行后根据执行结果决定是否提交或回滚事务。
Spring事务的隔离级别:
Spring事务支持以下五个隔离级别:
- DEFAULT:使用底层数据库的默认隔离级别。
- READ_UNCOMMITTED:读取未提交数据,可以看到其他事务尚未提交的数据变化。
- READ_COMMITTED:读取已提交数据,只能看到其他事务已经提交的数据变化。
- REPEATABLE_READ:可重复读,保证在同一事务中多次读取相同数据时得到的结果是一致的。
- SERIALIZABLE:序列化,最高的隔离级别,所有事务串行执行,避免了并发问题。
25.Spring事务在什么时候才能生效#
Spring事务在以下两种情况下才能生效:
- 事务管理器已经配置好了
Spring事务需要事务管理器来管理事务的开启、提交和回滚等操作。因此,必须先配置好相应的事务管理器后,才能使Spring事务生效。
- 目标方法被声明为@Transactional或者其调用链上存在@Transactional注解
只有当目标方法被@Transactional或者其调用链上存在@Transactional注解时,Spring才会对这个方法应用事务管理。如果没有这个注解,则不会进行任何事务管理,目标方法将以非事务方式执行。
26.Spring 如何配置事务管理器#
在Spring中,我们可以通过配置事务管理器来管理事务的开启、提交和回滚等操作。一般来说,配置事务管理器需要以下三个步骤:
- 配置数据源
首先需要配置数据库连接池和数据源相关的信息,例如数据库URL、用户名、密码等。
- 配置事务管理器
然后需要配置事务管理器,指定使用哪种事务管理器。Spring提供了多个事务管理器实现,包括DataSourceTransactionManager、JpaTransactionManager、HibernateTransactionManager等,我们可以根据自己的需要选择一个合适的事务管理器。
- 配置事务切面
最后需要将事务管理器配置为一个切面,并织入到需要进行事务管理的方法上。这里可以使用XML配置或注解方式进行配置。
下面是一个使用XML配置的示例:
<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="123456" />
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务切面 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(* com.example.service.*.*(..))"/>
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice"/>
</aop:config>
在以上示例中,我们首先配置了一个名为dataSource的数据源,然后配置了一个名为transactionManager的事务管理器
27.spring事务的传播机制#
Spring事务传播机制定义了在一个方法调用另一个方法时如何处理事务。当一个方法(称为外部事务)调用另一个方法(称为内部事务)时,Spring会根据预定义的规则来确定使用哪个事务。
以下是Spring事务传播机制的七种类型:
- REQUIRED:当前方法必须运行在事务中。如果当前没有事务,则创建一个新的事务。如果已经存在一个事务中,则加入该事务。
- SUPPORTS:当前方法支持事务,如果当前有事务,则在该事务中执行;否则,它可以不在事务中执行。
- MANDATORY:当前方法必须在事务中运行,如果当前没有事务,则抛出异常。
- REQUIRES_NEW:当前方法必须运行在它自己的事务中。如果当前存在一个事务,则将该事务挂起。
- NOT_SUPPORTED:当前方法不应该运行在事务中。如果当前存在一个事务,则将该事务挂起。
- NEVER:当前方法不应该运行在事务中。如果当前存在一个事务,则抛出异常。
- NESTED:当前方法必须在一个已经存在的事务中运行。该事务拥有一个保存点,可以回滚到该保存点。如果当前没有事务,则表现与REQUIRED一样。
28.spring是如何控制事务传播#
Spring通过AOP(面向切面编程)实现事务控制。在Spring中,TransactionInterceptor是一个拦截器类,它可以拦截被@Transactional注解标记的方法,并根据事务传播规则来决定是否开启或加入一个已有的事务。
当调用带有@Transactional注解的方法时,Spring会在方法执行前创建一个新的TransactionStatus对象,该对象表示当前事务的状态。如果该方法成功执行,则TransactionInterceptor会提交事务;否则,它将回滚事务。
当带有@Transactional注解的方法调用另一个带有@Transactional注解的方法时,TransactionInterceptor会根据事务传播规则来确定使用哪个事务。如果事务传播规则是REQUIRED,它将尝试加入当前事务;如果事务传播规则是REQUIRES_NEW,它将挂起当前事务并启动一个新事务。
总之,Spring通过TransactionInterceptor拦截被@Transactional注解标记的方法,在方法执行前后管理事务,并根据事务传播规则来决定使用哪个事务。
29.spring 什么时候事务会失效#
Spring事务可以失效的情况有以下几种:
- 未捕获异常:如果在带有@Transactional注解的方法中抛出了未捕获的异常,则事务将被标记为已回滚。
- 异常被catch并处理:如果在带有@Transactional注解的方法中捕获并处理了异常,那么事务不会回滚。
- 在非public方法中使用@Transactional注解:Spring只会拦截公共方法上的@Transactional注解,因此如果在非public方法上使用该注解,则事务将不起作用。
- 在同一个类中调用另一个带有@Transactional注解的方法:当一个带有@Transactional注解的方法调用同一个类中的另一个带有@Transactional注解的方法时,事务将不起作用。这是因为Spring无法拦截同一类中的方法调用。
- 多线程环境下使用ThreadLocal:如果在多个线程之间使用ThreadLocal来管理事务状态,则可能会导致事务失效。这是因为每个线程都有自己的ThreadLocal实例,它们之间无法共享事务状态。
- 使用第三方库操作数据库:如果在一个事务中使用第三方库来操作数据库,而该库没有受到Spring事务管理器的管理,则事务可能会失效。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了