# Spring 面试题
# 1.Spring 原理
- IoC(控制反转)和 DI(依赖注入):
- IoC(Inversion of Control):IoC 是一种设计思想,它将程序的控制权交给容器或框架,由容器来负责对象的创建和管理。Spring 的 IoC 容器,即 ApplicationContext,负责管理 Bean 的生命周期。
- DI(Dependency Injection):DI 是 IoC 的一种实现方式,它通过注入的方式将一个对象的依赖关系交给容器管理,而不是在对象内部直接创建依赖对象。这样做提高了组件的可重用性和可测试性。
- AOP(面向切面编程):
- AOP(Aspect-Oriented Programming):AOP 是一种编程范式,它允许将横切关注点(如日志、安全、事务管理等)从业务逻辑中剥离出来,使得系统关注点的变化不影响业务逻辑。在 Spring 中,AOP 通过代理机制实现,常见的代理方式有基于 JDK 动态代理和基于 CGLIB 的代理。
- Bean 的生命周期和作用域:
- Bean 的生命周期:Bean 的生命周期包括实例化、初始化、使用和销毁四个阶段。Spring 容器负责 Bean 的实例化和初始化,而销毁阶段则由容器负责。你可以提及
InitializingBean
和DisposableBean
接口以及@PostConstruct
和@PreDestroy
注解来控制 Bean 的初始化和销毁操作。 - Bean 的作用域:Spring 定义了多种 Bean 的作用域,包括 singleton(单例,默认)、prototype(原型)、request(每个 HTTP 请求一个实例)、session(每个 HTTP Session 一个实例)和 global session(全局 HTTP Session 一个实例)等。
- Bean 的生命周期:Bean 的生命周期包括实例化、初始化、使用和销毁四个阶段。Spring 容器负责 Bean 的实例化和初始化,而销毁阶段则由容器负责。你可以提及
- Spring 的事务管理:
- 声明式事务管理:通过使用
@Transactional
注解或 XML 配置来声明事务,使得事务的控制更加方便。Spring 提供了对编程式事务管理的支持,可以通过PlatformTransactionManager
接口进行编程式事务控制。
- 声明式事务管理:通过使用
- Spring 的数据访问与集成:
- 数据访问:Spring 提供了 JdbcTemplate 等模板类,简化了 JDBC 的操作,同时支持 ORM 框架(如 Hibernate、MyBatis)的集成,提供了对持久层的支持。
- 集成其他技术:Spring 能够与其他技术(如消息队列、缓存、搜索引擎等)进行集成,提供了丰富的集成方案。
- Spring 框架的核心组件:
- ApplicationContext:Spring 的 IoC 容器,负责管理 Bean 的生命周期。
- BeanFactory:是 IoC 容器的基础接口,提供了基本的 IoC 功能。
- BeanPostProcessor:在 Bean 初始化前后执行一些操作。
- BeanDefinition:定义了 Bean 的配置信息。
# 2. 什么是控制反转?什么是依赖注入
控制反转(IoC,Inversion of Control)和依赖注入(DI,Dependency Injection)是面向对象编程中两个重要的概念,它们是实现松耦合(Loose Coupling)的关键。
# 控制反转(IoC):
控制反转是一种设计原则,它将程序的控制权从程序代码本身转移到了外部容器或框架。在传统的程序设计中,程序代码通常负责对象的创建、依赖关系的管理以及资源的释放。而在 IoC 中,这些职责被反转,由外部容器负责。IoC 使得系统更加灵活,易于扩展和维护。
在 IoC 中,对象的创建和管理由 IoC 容器负责。Spring 的 ApplicationContext 就是一个经典的 IoC 容器。它负责实例化 Bean、注入 Bean 之间的依赖关系、管理 Bean 的生命周期等。开发者只需定义好 Bean 和它们之间的关系,由容器负责具体的实例化和管理。
# 依赖注入(DI):
依赖注入是 IoC 的一种具体实现方式。在依赖注入中,组件的依赖关系由外部容器在组件被创建时注入。依赖关系通常以构造函数、Setter 方法或接口注入的方式实现。
假设有一个类 UserService
依赖于 UserRepository
,传统的做法是在 UserService
类内部创建 UserRepository
的实例。但在依赖注入中, UserService
不再负责创建 UserRepository
,而是通过构造函数或 Setter 方法接受一个 UserRepository
的实例。容器在创建 UserService
时,将 UserRepository
的实例注入到 UserService
中。
public class UserService { | |
private final UserRepository userRepository; | |
// 通过构造函数注入依赖 | |
public UserService(UserRepository userRepository) { | |
this.userRepository = userRepository; | |
} | |
// 或者通过 Setter 方法注入依赖 | |
// public void setUserRepository(UserRepository userRepository) { | |
// this.userRepository = userRepository; | |
// } | |
} |
依赖注入使得对象之间的关系更加灵活,容易被替换和测试。Spring 框架提供了多种依赖注入的方式,包括构造函数注入、Setter 方法注入、接口注入等。
# 3.Spring 的生命周期
Spring 框架中的 Bean(Java 对象)的生命周期由 Spring 容器来管理,包括 Bean 的创建、初始化、使用和销毁等阶段。以下是 Spring Bean 的生命周期:
- 实例化(Instantiation): 当 Spring 容器接收到 Bean 的定义后,它会通过构造函数或工厂方法来实例化 Bean 对象。
- 设定 Bean 的属性(Population of Properties): Spring 容器将配置文件或注解中定义的属性值或引用注入到 Bean 中。
- Bean 的初始化(Bean Initialization): 如果 Bean 实现了
InitializingBean
接口,Spring 将调用其afterPropertiesSet
方法进行初始化。另外,如果在配置文件中使用了init-method
属性,指定了初始化方法,Spring 容器会在属性设置完成后调用这个指定的方法。 - Bean 的使用(Bean is ready to use): 此时,Bean 已经可以被应用程序使用了。
- Bean 的销毁(Bean Destruction): 如果 Bean 实现了
DisposableBean
接口,Spring 容器在 Bean 不再需要时调用其destroy
方法进行销毁。或者,如果在配置文件中使用了destroy-method
属性,指定了销毁方法,Spring 容器在需要销毁 Bean 时调用这个指定的方法。
在整个生命周期中,Spring 容器负责管理 Bean 的创建、依赖注入、初始化和销毁等工作,确保 Bean 在应用程序中的正确运作。
# 4.Spring 事务
Spring 框架提供了丰富的事务管理功能,支持编程式事务和声明式事务两种方式。事务是用来保持数据库的一致性和完整性的机制,当一组相关的操作要么全部成功,要么全部失败。
在 Spring 中,你可以使用注解或 XML 配置来声明式地管理事务。以下是一些关于 Spring 事务的关键概念:
事务管理器(Transaction Manager): 事务管理器负责实际管理事务。Spring 支持各种事务管理器,如
DataSourceTransactionManager
(针对关系型数据库)、JtaTransactionManager
(用于分布式事务)等。事务定义(Transaction Definition): 事务定义定义了事务的隔离级别、传播行为、超时等属性。Spring 使用
org.springframework.transaction.TransactionDefinition
接口来表示事务定义。事务传播行为(Transaction Propagation): 事务传播行为定义了在方法调用中的事务如何传播。例如,一个方法 A 调用另一个方法 B,B 是否应该加入 A 的事务。Spring 定义了多种传播行为,例如
REQUIRED
、REQUIRES_NEW
、NESTED
等。隔离级别(Isolation Level): 隔离级别定义了多个事务并发执行时,彼此之间的可见性。Spring 支持不同的隔离级别,包括
DEFAULT
(使用数据库默认隔离级别)、READ_UNCOMMITTED
(允许读取未提交的数据)、READ_COMMITTED
(只能读取已提交的数据)、REPEATABLE_READ
(可重复读取)、SERIALIZABLE
(串行化)等。声明式事务管理: 通过注解或 XML 配置,你可以在方法上声明事务的属性。例如,使用
@Transactional
注解来标识一个方法应该在事务中执行。@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void someTransactionalMethod() {
// 事务性操作
}
编程式事务管理: 在代码中通过编程的方式控制事务。Spring 提供了
TransactionTemplate
来简化编程式事务管理。transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 事务性操作
}
});
通过合适的配置,Spring 事务管理器可以确保事务的正确性和一致性,保障了数据操作的可靠性。
# 5.AOP 是什么?AOP 代理方式有哪些?
AOP(Aspect-Oriented Programming)是一种编程范式,它允许你定义横切关注点(cross-cutting concerns),如日志记录、性能监控、事务管理等,并将这些关注点模块化,然后将它们自动应用到应用程序的特定部分,而无需修改这些部分的代码。AOP 的目的是提高代码的模块性,降低耦合度,使代码更易于维护和扩展。
在 AOP 中,关注点(Aspect)是一个模块化的、可重用的模块,它包含了通知(Advice)和切点(Pointcut)两个主要概念。通知是关注点的具体行为,例如在方法执行前后执行的操作,而切点是指在何处应用通知的定义。
AOP 代理方式主要有两种:
- 基于代理的 AOP(Proxy-Based AOP): 这是最常见的 AOP 代理方式。在基于代理的 AOP 中,AOP 框架创建一个目标对象(被代理的对象)的代理对象,并将通知织入到代理对象的方法调用中。Spring 的 AOP 默认采用基于代理的 AOP 实现。基于代理的 AOP 主要有两种代理方式:
- JDK 动态代理:如果目标对象实现了接口,Spring 将使用 JDK 动态代理来创建代理对象。
- CGLIB 代理:如果目标对象没有实现接口,Spring 将使用 CGLIB 库来创建代理对象。
- 基于字节码的 AOP(AspectJ AOP): 基于字节码的 AOP 通过直接在类的字节码上织入切面,因此不需要代理对象。这种方式更为强大和灵活,但也更复杂,通常需要使用特定的编译器或者在运行时进行字节码操纵。AspectJ 是一个常用的基于字节码的 AOP 框架。
# 6.Spring 的 Bean 有哪些创建方式?
在 Spring 框架中,有多种方式可以创建 Bean:
XML 配置方式:在 XML 配置文件中使用
<bean>
标签定义 Bean 的配置信息。<bean id="myBean" class="com.example.MyBean"/>
注解方式:使用注解标记类,然后在配置类中使用
@ComponentScan
或者@Component
等注解进行扫描和定义 Bean。@Component
public class MyBean {
// ...
}
Java 配置方式:通过 Java 类配置 Bean,使用
@Configuration
和@Bean
注解。@Configuration
public class AppConfig {
@Bean
public MyBean myBean() {
return new MyBean();
}
}
工厂方法方式:使用工厂方法创建 Bean,即在 XML 配置文件或者 Java 配置类中定义工厂方法。
<bean id="myBean" class="com.example.MyBeanFactory" factory-method="createInstance"/>
public class MyBeanFactory {
public static MyBean createInstance() {
return new MyBean();
}
}
实例工厂方法方式:使用实例工厂方法创建 Bean,需要定义一个实例工厂,然后在 XML 配置文件或 Java 配置类中引用该工厂。
<bean id="myBean" factory-bean="myBeanFactory" factory-method="createInstance"/>
public class MyBeanFactory {
public MyBean createInstance() {
return new MyBean();
}
}
以上是常见的 Spring Bean 创建方式,选择适合项目需求的方式可以提高代码的灵活性和可维护性。
# 7.Spring 的 Bean 作用域有哪些?
在 Spring 框架中,Bean 的作用域(Scope)定义了 Bean 实例的生命周期范围。Spring 支持以下几种 Bean 的作用域:
Singleton(单例):在整个 Spring 容器中,只存在一个 Bean 实例。无论有多少个 Bean 的定义,Spring 容器都只会创建一个 Bean 实例,并在需要时返回给每个请求。
<bean id="myBean" class="com.example.MyBean" scope="singleton"/>
Prototype(原型):每次请求 Bean 时,容器都会创建一个新的 Bean 实例。每个请求都会得到一个全新的 Bean 对象。
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>
Request(请求):在一次 HTTP 请求中,容器会返回同一个 Bean 实例。该作用域仅在使用 Spring Web 应用时有效。
<bean id="myBean" class="com.example.MyBean" scope="request"/>
Session(会话):在一个 HTTP 会话中,容器会返回同一个 Bean 实例。该作用域同样只在 Spring Web 应用时有效。
<bean id="myBean" class="com.example.MyBean" scope="session"/>
Global Session(全局会话):在一个全局 HTTP 会话中,容器会返回同一个 Bean 实例。该作用域通常用于 Portlet 应用环境。
<bean id="myBean" class="com.example.MyBean" scope="globalSession"/>
Application(应用):在整个 Web 应用中,容器会返回同一个 Bean 实例。
<bean id="myBean" class="com.example.MyBean" scope="application"/>
这些作用域允许你控制 Bean 实例的生命周期和共享程度,根据需求选择合适的作用域可以提高系统的性能和资源利用率。
# 8.Spring 如何解决 Bean 循环依赖问题?
在 Spring 中,Bean 的循环依赖是指两个或多个 Bean 相互依赖,形成一个循环引用的关系。Spring 容器默认是不支持循环依赖的,但是它提供了一种机制来处理部分循环依赖情况。
Spring 使用了三级缓存解决 Bean 的循环依赖问题:
- SingletonObjects 缓存:Spring 容器创建 Bean 时,会将正在创建的 Bean 放入 SingletonObjects 缓存中,标记为正在创建中。
- EarlySingletonObjects 缓存:当发现循环依赖时,Spring 会将早期暴露的 Bean 放入 EarlySingletonObjects 缓存中,标记为早期暴露的 Bean。
- SingletonFactories 缓存:当 Bean 创建完成后,会放入 SingletonFactories 缓存中,表示该 Bean 可以被其他 Bean 引用。
Spring 容器在创建 Bean 时,会先从 SingletonObjects 缓存中查找是否存在 Bean 的实例。如果存在,直接返回。如果不存在,会先创建一个 ObjectFactory,用于生成 Bean 的实例。然后,将该 ObjectFactory 放入 SingletonFactories 缓存中。接着,Spring 会递归地创建 Bean 的依赖关系。如果在创建依赖关系的过程中,发现了循环依赖,Spring 会从 EarlySingletonObjects 缓存中获取早期暴露的 Bean,而不是直接创建新的实例。这样,就避免了循环依赖的问题。
需要注意的是,虽然 Spring 提供了这种机制来处理部分循环依赖情况,但是过多的循环依赖可能会导致系统设计存在问题,因此在设计时,尽量避免复杂的循环依赖关系。