spring 面试题
一、Spring 基础概念
- 什么是 Spring 框架?
- Spring 是一个开源的 Java 应用程序框架,它提供了一种轻量级的、非侵入式的方式来构建企业级应用。Spring 的核心功能包括依赖注入(Dependency Injection,DI)、面向切面编程(Aspect - Oriented Programming,AOP)、事务管理、数据访问等,旨在简化企业级 Java 开发,提高开发效率和代码的可维护性。
- Spring 框架的核心模块有哪些?
- 核心容器(Core Container)
- spring - core:提供了框架的基本核心功能,如 IOC(控制反转)和依赖注入(DI)的基础实现。
- spring - beans:提供了 BeanFactory,这是 Spring 框架的核心工厂接口,用于管理和配置应用程序中的对象(beans)。
- spring - context:建立在核心容器之上,提供了一种更高级的应用程序上下文(ApplicationContext),它是 BeanFactory 的子接口,除了具有 BeanFactory 的功能外,还提供了诸如国际化、事件传播、资源加载等附加功能。
- spring - context - support:提供了对第三方库(如缓存、邮件、调度等)的集成支持。
- AOP(Aspect - Oriented Programming)
- spring - aop:提供了面向切面编程的基础架构,允许在运行时将横切关注点(如日志记录、安全检查等)织入到应用程序的核心业务逻辑中。
- spring - aspectj:对 AspectJ 框架的集成支持,AspectJ 是一种功能强大的 AOP 实现,Spring - aspectj 使得在 Spring 应用中可以使用 AspectJ 的注解和语法进行更灵活的 AOP 编程。
- 数据访问与集成(Data Access/Integration)
- spring - jdbc:提供了对 JDBC(Java Database Connectivity)的简化操作,包括数据库连接的获取、SQL 语句的执行、结果集的处理等,减少了编写 JDBC 代码的繁琐。
- spring - tx:提供了事务管理的抽象层,支持编程式事务和声明式事务,可与多种底层事务管理器(如 JDBC 事务、JTA 事务等)集成。
- spring - orm:对多种对象 - 关系映射(ORM)框架(如 Hibernate、MyBatis 等)提供集成支持,方便在 Spring 应用中使用 ORM 框架进行数据库操作。
- Web(Spring Web)
- spring - web:提供了基础的 Web 开发功能,如 Web 应用上下文、Servlet 集成、文件上传等。
- spring - webmvc:Spring 的 MVC(Model - View - Controller)框架,用于构建 Web 应用程序,实现了请求处理、视图渲染、模型绑定等功能。
- spring - webflux:提供了基于反应式编程(Reactive Programming)的 Web 框架,适用于构建高性能、非阻塞的 Web 应用,如处理大量并发请求的微服务。
- 核心容器(Core Container)
- 解释一下控制反转(IOC)和依赖注入(DI)的概念。
- 控制反转(Inversion of Control,IOC)
- 在传统的编程模式中,对象的创建和对象间的依赖关系是由程序代码直接控制的。而在 IOC 模式下,这种控制权被转移到了容器(如 Spring 容器)中。程序代码不再负责对象的创建和依赖关系的维护,而是由容器来负责创建对象,并将它们装配在一起。这就好比在传统模式下,你自己做饭(自己创建和管理对象),而在 IOC 模式下,你去餐馆吃饭(由容器来提供和管理对象)。
- 依赖注入(Dependency Injection,DI)
- 依赖注入是实现控制反转的一种具体方式。它是指当一个对象(依赖方)需要依赖于另一个对象(被依赖方)时,由容器在创建依赖方对象时,将被依赖方对象注入到依赖方对象中。依赖注入有三种常见的方式:
- 构造函数注入(Constructor Injection):通过构造函数将依赖对象传入。例如:
public class MyService {private MyRepository myRepository;public MyService(MyRepository myRepository) {this.myRepository = myRepository;} }
** setter 注入(Setter Injection)**:通过 setter 方法将依赖对象传入。例如:
public class MyService {private MyRepository myRepository;public void setMyRepository(MyRepository myRepository) {this.myRepository = myRepository;} }
字段注入(Field Injection):直接在类的字段上使用注解注入依赖对象,但这种方式会使类与 Spring 框架耦合较紧密,例如:
public class MyService {@Autowiredprivate MyRepository myRepository; }
- 控制反转(Inversion of Control,IOC)
二、Spring Bean
- Spring Bean 的生命周期是怎样的?
- 实例化(Instantiation):
- Spring 首先根据配置(如 XML 配置、注解等)确定要创建的 Bean 类型,然后使用 Java 反射机制创建 Bean 的实例。例如,对于一个简单的使用 @Component 注解的类:
@Component public class MyBean {public MyBean() {// 这是构造函数,在实例化时被调用} }
属性赋值(Populate Properties):
- 在实例化后,Spring 会对 Bean 的属性进行赋值。如果使用了依赖注入,会将依赖的其他 Bean 注入到当前 Bean 中。例如:
@Component public class MyBean {@Autowiredprivate AnotherBean anotherBean; }
-
- 这里 Spring 会找到 AnotherBean 的实例并注入到 MyBean 的 anotherBean 属性中。
- 初始化(Initialization):
- 执行 Bean 的初始化方法。有两种常见的初始化方式:
- 实现 InitializingBean 接口
@Component public class MyBean implements InitializingBean {@Overridepublic void afterPropertiesSet() throws Exception {// 初始化逻辑} }
使用 @PostConstruct 注解:
@Component public class MyBean {@PostConstructpublic void init() {// 初始化逻辑} }
- 实现 InitializingBean 接口
- 执行 Bean 的初始化方法。有两种常见的初始化方式:
- Spring 首先根据配置(如 XML 配置、注解等)确定要创建的 Bean 类型,然后使用 Java 反射机制创建 Bean 的实例。例如,对于一个简单的使用 @Component 注解的类:
- 实例化(Instantiation):
- 使用(In - use):
- 初始化完成后,Bean 就可以被应用程序使用了,例如被其他组件调用方法等。
- 销毁(Destruction):
- 当容器关闭时,会销毁 Bean。如果 Bean 实现了 DisposableBean 接口,会调用其 destroy 方法:
@Component
public class MyBean implements DisposableBean {@Overridepublic void destroy() throws Exception {// 销毁逻辑}
}
或者使用 @PreDestroy 注解来定义销毁方法:
@Component
public class MyBean {@PreDestroypublic void cleanUp() {// 销毁逻辑}
}
- 如何配置 Spring Bean?有哪些方式?
- 基于 XML 配置:
- 在 XML 文件中定义 Bean,例如:
xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema - instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring - beans.xsd"><bean id="myBean" class="com.example.MyBean"><property name="propertyName" value="propertyValue"/></bean> </beans>
这里定义了一个 id 为 myBean 的 Bean,其类是 com.example.MyBean,并设置了一个属性。
- 在 XML 文件中定义 Bean,例如:
- 基于注解配置:
- 使用 Spring 的注解来标识 Bean 及其配置。常见的注解有:
- @Component:通用的组件注解,用于标识一个普通的 Spring 组件。例如:
java
@Component public class MyComponent {// 组件逻辑 }
- @Service:用于标识业务逻辑层的组件,通常在服务类上使用。例如:
java
@Service public class MyService {// 服务逻辑 }
- @Repository:用于标识数据访问层(如数据库操作)的组件,例如:
java
@Repository public class MyRepository {// 数据访问逻辑 }
- @Controller:用于标识 Web 应用中的控制器组件,例如在 Spring MVC 中:
java
@Controller public class MyController {// 控制器逻辑 }
- @Configuration 和 @Bean:用于 Java 配置类来定义 Bean。例如:
java
@Configuration public class AppConfig {@Beanpublic MyBean myBean() {return new MyBean();} }
- @Component:通用的组件注解,用于标识一个普通的 Spring 组件。例如:
- 使用 Spring 的注解来标识 Bean 及其配置。常见的注解有:
- 基于 Java 配置(Java - based configuration):
- 除了 @Configuration 和 @Bean 的方式外,还可以通过继承特定的抽象类来配置 Spring。例如,继承 WebMvcConfigurerAdapter(在 Spring 5 中已被 WebMvcConfigurer 取代)来配置 Spring MVC:
java
@Configuration public class WebMvcConfig extends WebMvcConfigurer {// 配置视图解析器等@Overridepublic void configureViewResolvers(ViewResolverRegistry registry) {// 配置逻辑} }
- 除了 @Configuration 和 @Bean 的方式外,还可以通过继承特定的抽象类来配置 Spring。例如,继承 WebMvcConfigurerAdapter(在 Spring 5 中已被 WebMvcConfigurer 取代)来配置 Spring MVC:
- 基于 XML 配置:
三、Spring AOP
- 什么是面向切面编程(AOP)?Spring AOP 的原理是什么?
- 面向切面编程(AOP)
- AOP 是一种编程范式,它允许将横切关注点(如日志记录、安全检查、事务管理等)从应用程序的核心业务逻辑中分离出来,以独立的模块(切面)进行处理。这样可以提高代码的模块化程度和可维护性。例如,在一个没有 AOP 的应用中,如果每个业务方法都需要添加日志记录代码,会导致日志记录代码分散在各个业务方法中,而使用 AOP,可以将日志记录逻辑定义在一个切面中,然后自动织入到需要的业务方法中。
- Spring AOP 原理
- Spring AOP 主要基于动态代理(Dynamic Proxy)技术。当一个目标对象(被代理对象)需要被织入切面逻辑时,Spring 会根据目标对象是否实现接口来选择使用 JDK 动态代理或 CGLIB 动态代理。
- JDK 动态代理:
- 如果目标对象实现了接口,Spring 会使用 JDK 动态代理。JDK 动态代理是通过实现目标对象的接口来创建代理对象的。例如,有一个接口和实现类:
java
public interface MyInterface {void doSomething(); } public class MyClass implements MyInterface {@Overridepublic void doSomething() {// 业务逻辑} }
Spring 会创建一个实现 MyInterface 的代理对象,在代理对象的 doSomething 方法中,会在调用目标对象的 doSomething 方法前后添加切面逻辑。
- 如果目标对象实现了接口,Spring 会使用 JDK 动态代理。JDK 动态代理是通过实现目标对象的接口来创建代理对象的。例如,有一个接口和实现类:
- CGLIB 动态代理:
- 如果目标对象没有实现接口,Spring 会使用 CGLIB(Code Generation Library)动态代理。CGLIB 是通过字节码生成技术来创建目标对象的子类作为代理对象。例如,有一个没有实现接口的类:
java
public class MyClass {public void doSomething() {// 业务逻辑} }
Spring 会生成一个 MyClass 的子类作为代理对象,在子类的 doSomething 方法中添加切面逻辑。
- 如果目标对象没有实现接口,Spring 会使用 CGLIB(Code Generation Library)动态代理。CGLIB 是通过字节码生成技术来创建目标对象的子类作为代理对象。例如,有一个没有实现接口的类:
- JDK 动态代理:
- Spring AOP 主要基于动态代理(Dynamic Proxy)技术。当一个目标对象(被代理对象)需要被织入切面逻辑时,Spring 会根据目标对象是否实现接口来选择使用 JDK 动态代理或 CGLIB 动态代理。
- 面向切面编程(AOP)
- Spring AOP 中的通知(Advice)有哪些类型?
- 前置通知(Before Advice):
- 在目标方法执行之前执行的通知。例如,在一个方法调用前记录日志:
java
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before;@Aspect public class LoggingAspect {@Before("execution(* com.example.service.MyService.*(..))")public void beforeAdvice(JoinPoint joinPoint) {System.out.println("Before method: " + joinPoint.getSignature().getName());} }
- 在目标方法执行之前执行的通知。例如,在一个方法调用前记录日志:
- 后置通知(After Advice):
- 在目标方法执行之后执行的通知。有两种类型:
- 正常返回后执行的后置通知(After Returning Advice):
- 当目标方法正常返回时执行。例如:
java
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect;@Aspect public class LoggingAspect {@AfterReturning(pointcut = "execution(* com.example.service.MyService.*(..))", returning = "result")public void afterReturningAdvice(JoinPoint joinPoint, Object result) {System.out.println("After returning method: " + joinPoint.getSignature().getName() + ", result: " + result);} }
- 当目标方法正常返回时执行。例如:
- 无论是否抛出异常都执行的后置通知(After Finally Advice):
- 这种通知类似于 try - finally 块中的 finally 语句块,无论目标方法是否抛出异常都会执行。例如:
java
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect;@Aspect public class LoggingAspect {@After("execution(* com.example.service.MyService.*(..))")public void afterFinallyAdvice(JoinPoint joinPoint) {System.out.println("After finally method: " + joinPoint.getSignature().getName());} }
- 这种通知类似于 try - finally 块中的 finally 语句块,无论目标方法是否抛出异常都会执行。例如:
- 正常返回后执行的后置通知(After Returning Advice):
- 在目标方法执行之后执行的通知。有两种类型:
- 环绕通知(Around Advice):
- 环绕通知可以在目标方法执行前后都添加逻辑,它可以完全控制目标方法的执行。例如:
java
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect;@Aspect public class LoggingAspect {@Around("execution(* com.example.service.MyService.*(..))")public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {System.out.println("Before method in around advice: " + pjp.getSignature().getName());Object result = pjp.proceed();System.out.println("After method in around advice: " + pjp.getSignature().getName());return result;} }
- 环绕通知可以在目标方法执行前后都添加逻辑,它可以完全控制目标方法的执行。例如:
- 异常通知(After Throwing Advice):
- 当目标方法抛出异常时执行的通知。例如:
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect;@Aspect public class LoggingAspect {@AfterThrowing(pointcut = "execution(* com.example.service.MyService.*(..))", throwing = "ex")public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {System.out.println("Method: " + joinPoint.getSignature().getName() + " threw exception: " + ex.getMessage());} }
- 当目标方法抛出异常时执行的通知。例如:
- 前置通知(Before Advice):