一、小林-Spring面试题
1、spring是如何解决循环依赖的?
循环依赖指的是两个类中的属性相互依赖对方:例如 A 类中有 B 属性,B 类中有 A属性,从而形成了一个依赖闭环,如下图。
循环依赖问题在Spring中主要有三种情况:
- 第一种:通过构造方法进行依赖注入时产生的循环依赖问题。
- 第二种:通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
- 第三种:通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
只有【第三种方式】的循环依赖问题被 Spring 解决了,其他两种方式在遇到循环依赖问题时,Spring都会产生异常。
Spring 解决单例模式下的setter循环依赖问题的主要方式是通过三级缓存解决循环依赖。三级缓存指的是 Spring 在创建 Bean 的过程中,通过三级缓存来缓存正在创建的 Bean,以及已经创建完成的 Bean 实例。具体步骤如下:
- 实例化 Bean:Spring 在实例化 Bean 时,会先创建一个空的 Bean 对象,并将其放入一级缓存中。
- 属性赋值:Spring 开始对 Bean 进行属性赋值,如果发现循环依赖,会将当前 Bean 对象提前暴露给后续需要依赖的 Bean(通过提前暴露的方式解决循环依赖)。
- 初始化 Bean:完成属性赋值后,Spring 将 Bean 进行初始化,并将其放入二级缓存中。
- 注入依赖:Spring 继续对 Bean 进行依赖注入,如果发现循环依赖,会从二级缓存中获取已经完成初始化的 Bean 实例。
通过三级缓存的机制,Spring 能够在处理循环依赖时,确保及时暴露正在创建的 Bean 对象,并能够正确地注入已经初始化的 Bean 实例,从而解决循环依赖问题,保证应用程序的正常运行。
2、spring三级缓存的数据结构是什么?
都是 Map类型的缓存,比如Map {k:name; v:bean}。
- 一级缓存(Singleton Objects) :这是一个Map类型的缓存,存储的是已经完全初始化好的bean,即完全准备好可以使用的bean实例。键是bean的名称,值是bean的实例。这个缓存在
DefaultSingletonBeanRegistry类中的singletonObjects属性中。 - 二级缓存(Early Singleton Objects) :这同样是一个Map类型的缓存,存储的是早期的bean引用,即已经实例化但还未完全初始化的bean。这些bean已经被实例化,但是可能还没有进行属性注入等操作。这个缓存在
DefaultSingletonBeanRegistry类中的earlySingletonObjects属性中。 - 三级缓存(Singleton Factories) :这也是一个Map类型的缓存,存储的是ObjectFactory对象,这些对象可以生成早期的bean引用。当一个bean正在创建过程中,如果它被其他bean依赖,那么这个正在创建的bean就会通过这个ObjectFactory来创建一个早期引用,从而解决循环依赖的问题。这个缓存在
DefaultSingletonBeanRegistry类中的singletonFactories属性中。
3、spring框架中都用到了哪些设计模式
- 工厂设计模式 : Spring使用工厂模式通过 BeanFactory、ApplicationContext 创建 bean 对象。
- 代理设计模式 : Spring AOP 功能的实现。
- 单例设计模式 : Spring 中的 Bean 默认都是单例的。
- 模板方法模式 : Spring 中 jdbcTemplate、hibernateTemplate 等以 Template 结尾的对数据库操作的类,它们就使用到了模板模式。
- 包装器设计模式 : 我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
- 观察者模式: Spring 事件驱动模型就是观察者模式很经典的一个应用。
- 适配器模式 :Spring AOP 的增强或通知(Advice)使用到了适配器模式、spring MVC 中也是用到了适配器模式适配Controller。
4、spring 常用注解有什么?
@Autowired 注解
@Autowired:主要用于自动装配bean。当Spring容器 (opens new window)中存在与要注入的属性类型匹配的bean时,它会自动将bean注入到属性中。就跟我们new 对象一样。
用法很简单,如下示例代码:
@Component
public class MyService {
}
@Component
public class MyController {
@Autowired
private MyService myService;
}
在上面的示例代码中,MyController类中的myService属性被@Autowired注解标记,Spring会自动将MyService类型的bean注入到myService属性中。
@Component
这个注解用于标记一个类作为Spring的bean。当一个类被@Component注解标记时,Spring会将其实例化为一个bean,并将其添加到Spring容器中。在上面讲解@Autowired的时候也看到了,示例代码:
@Component
public class MyComponent {
}
在上面的示例代码中,MyComponent类被@Component注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。
@Configuration
@Configuration,注解用于标记一个类作为Spring的配置类。配置类可以包含@Bean注解的方法,用于定义和配置bean,作为全局配置。示例代码:
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
@Bean
@Bean注解用于标记一个方法作为Spring的bean工厂方法。当一个方法被@Bean注解标记时,Spring会将该方法的返回值作为一个bean,并将其添加到Spring容器中,如果自定义配置,经常用到这个注解。
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
@Service
@Service,这个注解用于标记一个类作为服务层的组件。它是@Component注解的特例,用于标记服务层的bean,一般标记在业务service的实现类。
@Service
public class MyServiceImpl {
}
@Repository
@Repository注解用于标记一个类作为数据访问层的组件。它也是@Component注解的特例,用于标记数据访问层的bean。这个注解很容易被忽略,导致数据库无法访问。
@Repository
public class MyRepository {
}
在上面的示例代码中,MyRepository类被@Repository注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。
@Controller
@Controller注解用于标记一个类作为控制层的组件。它也是@Component注解的特例,用于标记控制层的bean。这是MVC结构的另一个部分,加在控制层
@Controller
public class MyController {
}
在上面的示例代码中,MyController类被@Controller注解标记,Spring会将其实例化为一个bean,并将其添加到Spring容器中。
5、Spring的事务什么情况下会失效?
Spring Boot通过Spring框架的事务管理模块来支持事务操作。事务管理在Spring Boot中通常是通过 @Transactional 注解来实现的。事务可能会失效的一些常见情况包括:
- 未捕获异常: 如果一个事务方法中发生了未捕获的异常,并且异常未被处理或传播到事务边界之外,那么事务会失效,所有的数据库操作会回滚。
- 非受检异常: 默认情况下,Spring对非受检异常(RuntimeException或其子类)进行回滚处理,这意味着当事务方法中抛出这些异常时,事务会回滚。
- 事务传播属性设置不当: 如果在多个事务之间存在事务嵌套,且事务传播属性配置不正确,可能导致事务失效。特别是在方法内部调用有 @Transactional 注解的方法时要特别注意。
- 多数据源的事务管理: 如果在使用多数据源时,事务管理没有正确配置或者存在多个 @Transactional 注解时,可能会导致事务失效。
- 跨方法调用事务问题: 如果一个事务方法内部调用另一个方法,而这个被调用的方法没有 @Transactional 注解,这种情况下外层事务可能会失效。
- 事务在非公开方法中失效: 如果 @Transactional 注解标注在私有方法上或者非 public 方法上,事务也会失效。
6、Spring的事务,使用this调用是否生效?
不能生效。
因为Spring事务是通过代理对象来控制的,只有通过代理对象的方法调用才会应用事务管理的相关规则。当使用this直接调用时,是绕过了Spring的代理机制,因此不会应用事务设置。
7、Bean的生命周期说一下?
- Spring启动,查找并加载需要被Spring管理的bean,进行Bean的实例化
- Bean实例化后对将Bean的引入和值注入到Bean的属性中
- 如果Bean实现了BeanNameAware接口的话,Spring将Bean的Id传递给setBeanName()方法
- 如果Bean实现了BeanFactoryAware接口的话,Spring将调用setBeanFactory()方法,将BeanFactory容器实例传入
- 如果Bean实现了ApplicationContextAware接口的话,Spring将调用Bean的setApplicationContext()方法,将bean所在应用上下文引用传入进来。
- 如果Bean实现了BeanPostProcessor接口,Spring就将调用他们的postProcessBeforeInitialization()方法。
- 如果Bean 实现了InitializingBean接口,Spring将调用他们的afterPropertiesSet()方法。类似的,如果bean使用init-method声明了初始化方法,该方法也会被调用
- 如果Bean 实现了BeanPostProcessor接口,Spring就将调用他们的postProcessAfterInitialization()方法。
- 此时,Bean已经准备就绪,可以被应用程序使用了。他们将一直驻留在应用上下文中,直到应用上下文被销毁。
- 如果bean实现了DisposableBean接口,Spring将调用它的destory()接口方法,同样,如果bean使用了destory-method 声明销毁方法,该方法也会被调用。
8、Bean是否单例?
Spring 中的 Bean 默认都是单例的。
就是说,每个Bean的实例只会被创建一次,并且会被存储在Spring容器的缓存中,以便在后续的请求中重复使用。这种单例模式可以提高应用程序的性能和内存效率。
但是,Spring也支持将Bean设置为多例模式,即每次请求都会创建一个新的Bean实例。要将Bean设置为多例模式,可以在Bean定义中通过设置scope属性为"prototype"来实现。
需要注意的是,虽然Spring的默认行为是将Bean设置为单例模式,但在一些情况下,使用多例模式是更为合适的,例如在创建状态不可变的Bean或有状态Bean时。此外,需要注意的是,如果Bean单例是有状态的,那么在使用时需要考虑线程安全性问题。
9、Bean的单例和非单例,生命周期是否一样
不一样的,Spring Bean 的生命周期完全由 IoC 容器控制。Spring 只帮我们管理单例模式 Bean 的完整生命周期,对于 prototype 的 Bean,Spring 在创建好交给使用者之后,则不会再管理后续的生命周期。
具体区别如下:
| 阶段 | 单例(Singleton) | 非单例(如Prototype) |
|---|---|---|
| 创建时机 | 容器启动时创建(或首次请求时,取决于配置)。 | 每次请求时创建新实例。 |
| 初始化流程 | 完整执行生命周期流程(属性注入、Aware接口、初始化方法等)。 | 每次创建新实例时都会完整执行生命周期流程(仅到初始化完成)。 |
| 销毁时机 | 容器关闭时销毁,触发DisposableBean或destroy-method。 | 容器不管理销毁,需由调用者自行释放资源(Spring不跟踪实例)。 |
| 内存占用 | 单实例常驻内存,高效但需注意线程安全。 | 每次请求生成新实例,内存开销较大,需手动管理资源释放。 |
| 适用场景 | 无状态服务(如Service、DAO层)。 | 有状态对象(如用户会话、临时计算对象)。 |
10、Spring bean的作用域有哪些?
Spring框架中的Bean作用域(Scope)定义了Bean的生命周期和可见性。不同的作用域影响着Spring容器如何管理这些Bean的实例,包括它们如何被创建、如何被销毁以及它们是否可以被多个用户共享。
Spring支持几种不同的作用域,以满足不同的应用场景需求。以下是一些主要的Bean作用域:
- Singleton(单例) :在整个应用程序中只存在一个 Bean 实例。默认作用域,Spring 容器中只会创建一个 Bean 实例,并在容器的整个生命周期中共享该实例。
- Prototype(原型) :每次请求时都会创建一个新的 Bean 实例。次从容器中获取该 Bean 时都会创建一个新实例,适用于状态非常瞬时的 Bean。
- Request(请求) :每个 HTTP 请求都会创建一个新的 Bean 实例。仅在 Spring Web 应用程序中有效,每个 HTTP 请求都会创建一个新的 Bean 实例,适用于 Web 应用中需求局部性的 Bean。
- Session(会话) :Session 范围内只会创建一个 Bean 实例。该 Bean 实例在用户会话范围内共享,仅在 Spring Web 应用程序中有效,适用于与用户会话相关的 Bean。
- Application:当前 ServletContext 中只存在一个 Bean 实例。仅在 Spring Web 应用程序中有效,该 Bean 实例在整个 ServletContext 范围内共享,适用于应用程序范围内共享的 Bean。
- WebSocket(Web套接字) :在 WebSocket 范围内只存在一个 Bean 实例。仅在支持 WebSocket 的应用程序中有效,该 Bean 实例在 WebSocket 会话范围内共享,适用于 WebSocket 会话范围内共享的 Bean。
- Custom scopes(自定义作用域) :Spring 允许开发者定义自定义的作用域,通过实现 Scope 接口来创建新的 Bean 作用域。
11、Spring容器里存的是什么?
在Spring容器中,存储的主要是Bean对象。
Bean是Spring框架中的基本组件,用于表示应用程序中的各种对象。当应用程序启动时,Spring容器会根据配置文件或注解的方式创建和管理这些Bean对象。Spring容器会负责创建、初始化、注入依赖以及销毁Bean对象。
12、在Spring中,在bean加载/销毁前后,如果想实现某些逻辑,可以怎么做
在Spring框架中,如果你希望在Bean加载(即实例化、属性赋值、初始化等过程完成后)或销毁前后执行某些逻辑,你可以使用Spring的生命周期回调接口或注解。这些接口和注解允许你定义在Bean生命周期的关键点执行的代码。
使用init-method和destroy-method
在XML配置中,你可以通过init-method和destroy-method属性来指定Bean初始化后和销毁前需要调用的方法。
<bean id="myBean" class="com.example.MyBeanClass"
init-method="init" destroy-method="destroy"/>
然后,在你的Bean类中实现这些方法:
public class MyBeanClass {
public void init() {
// 初始化逻辑
}
public void destroy() {
// 销毁逻辑
}
}
实现InitializingBean和DisposableBean接口
你的Bean类可以实现org.springframework.beans.factory.InitializingBean和org.springframework.beans.factory.DisposableBean接口,并分别实现afterPropertiesSet和destroy方法。
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class MyBeanClass implements InitializingBean, DisposableBean {
@Override
public void afterPropertiesSet() throws Exception {
// 初始化逻辑
}
@Override
public void destroy() throws Exception {
// 销毁逻辑
}
}
使用@PostConstruct和@PreDestroy注解
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
public class MyBeanClass {
@PostConstruct
public void init() {
// 初始化逻辑
}
@PreDestroy
public void destroy() {
// 销毁逻辑
}
}
使用@Bean注解的initMethod和destroyMethod属性
在基于Java的配置中,你还可以在@Bean注解中指定initMethod和destroyMethod属性。
@Configuration
public class AppConfig {
@Bean(initMethod = "init", destroyMethod = "destroy")
public MyBeanClass myBean() {
return new MyBeanClass();
}
}
13、Bean注入和xml注入最终得到了相同的效果,它们在底层是怎样做的
在Spring框架中,基于注解的Bean注入(如@Autowired、@Resource)和基于XML的依赖注入虽然在配置方式上不同,但在底层最终都通过Spring容器的统一机制实现依赖注入。它们的核心流程可以归纳为以下步骤:
| 阶段 | 注解注入 | XML注入 |
|---|---|---|
| 配置解析 | 通过注解处理器扫描类路径,解析@Component、@Autowired等注解。 | 解析XML文件中的<bean>、<property>、<constructor-arg>标签。 |
| 生成BeanDefinition | 将注解信息转换为AnnotatedBeanDefinition。 | 将XML配置转换为GenericBeanDefinition。 |
| 依赖注入 | 由AutowiredAnnotationBeanPostProcessor等后处理器处理。 | 在BeanDefinition中直接记录属性或构造器参数,由容器直接注入。 |
| 最终结果 | 生成完整的Bean实例,完成依赖注入。 | 生成完整的Bean实例,完成依赖注入。 |
XML 注入
使用 XML 文件进行 Bean 注入时,Spring 在启动时会读取 XML 配置文件,以下是其底层步骤:
-
Bean 定义解析:Spring 容器通过
XmlBeanDefinitionReader类解析 XML 配置文件,读取其中的<bean>标签以获取 Bean 的定义信息。 -
注册 Bean 定义:解析后的 Bean 信息被注册到
BeanDefinitionRegistry(如DefaultListableBeanFactory)中,包括 Bean 的类、作用域、依赖关系、初始化和销毁方法等。 -
实例化和依赖注入:当应用程序请求某个 Bean 时,Spring 容器会根据已经注册的 Bean 定义:
- 首先,使用反射机制创建该 Bean 的实例。
- 然后,根据 Bean 定义中的配置,通过 setter 方法、构造函数或方法注入所需的依赖 Bean。
注解注入
使用注解进行 Bean 注入时,Spring 的处理过程如下:
- 类路径扫描:当 Spring 容器启动时,它首先会进行类路径扫描,查找带有特定注解(如
@Component、@Service、@Repository和@Controller)的类。 - 注册 Bean 定义:找到的类会被注册到
BeanDefinitionRegistry中,Spring 容器将为其生成 Bean 定义信息。这通常通过AnnotatedBeanDefinitionReader类来实现。 - 依赖注入:与 XML 注入类似,Spring 在实例化 Bean 时,也会检查字段上是否有
@Autowired、@Inject或@Resource注解。如果有,Spring 会根据注解的信息进行依赖注入。
尽管使用的方式不同,但 XML 注入和注解注入在底层的实现机制是相似的,主要体现在以下几个方面:
-
BeanDefinition:无论是 XML 还是注解,最终都会生成
BeanDefinition对象,并存储在同一个BeanDefinitionRegistry中。 -
后处理器:
- Spring 提供了多个 Bean 后处理器(如
AutowiredAnnotationBeanPostProcessor),用于处理注解(如@Autowired)的依赖注入。 - 对于 XML,Spring 也有相应的后处理器来处理 XML 配置的依赖注入。
- Spring 提供了多个 Bean 后处理器(如
-
依赖查找:在依赖注入时,Spring 容器会通过
ApplicationContext中的 BeanFactory 方法来查找和注入依赖,无论是通过 XML 还是注解,都会调用类似的查找方法。
14、Spring给我们提供了很多扩展点,这些有了解吗?
Spring框架提供了许多扩展点,使得开发者可以根据需求定制和扩展Spring的功能。以下是一些常用的扩展点:
- BeanFactoryPostProcessor:允许在Spring容器实例化bean之前修改bean的定义。常用于修改bean属性或改变bean的作用域。
- BeanPostProcessor:可以在bean实例化、配置以及初始化之后对其进行额外处理。常用于代理bean、修改bean属性等。
- PropertySource:用于定义不同的属性源,如文件、数据库等,以便在Spring应用中使用。
- ImportSelector和ImportBeanDefinitionRegistrar:用于根据条件动态注册bean定义,实现配置类的模块化。
- Spring MVC中的HandlerInterceptor:用于拦截处理请求,可以在请求处理前、处理中和处理后执行特定逻辑。
- Spring MVC中的ControllerAdvice:用于全局处理控制器的异常、数据绑定和数据校验。
- Spring Boot的自动配置:通过创建自定义的自动配置类,可以实现对框架和第三方库的自动配置。
- 自定义注解:创建自定义注解,用于实现特定功能或约定,如权限控制、日志记录等