Spring框架面试问题及详细回答

第一部分:Spring Framework 核心 (IoC, DI, AOP)

1. 什么是Spring框架?它的核心是什么?

  • 回答要点: Spring是一个开源的、轻量级的Java应用开发框架,其核心是控制反转(IoC)面向切面编程(AOP)。它提供了一个容器(ApplicationContext),用于管理应用组件(Beans)的生命周期和配置,从而简化企业级应用开发。

2. 解释一下控制反转(IoC)和依赖注入(DI),它们有什么关系?

  • 回答要点:

    • IoC(控制反转) :是一种设计原则,将对象创建和依赖绑定的控制权从应用程序代码"反转"到外部容器(Spring容器)。传统编程中,对象自己控制其依赖的创建(new关键字),而在IoC中,容器负责控制。

    • DI(依赖注入):是IoC原则的一种具体实现方式。容器通过构造函数、Setter方法或接口等方式,将依赖对象"注入"到目标对象中。

    • 关系:DI是实现IoC的主要手段。我们说Spring实现了IoC,具体是通过DI机制来完成的。

一、 控制反转(IoC)

1. 核心思想: "交出控制权"

在传统的应用程序开发中,当对象A需要依赖对象B时,对象A需要自己主动去创建(new)对象B,或者从工厂中获取。这时,程序的控制权在对象A自己手里。

传统代码示例(没有IoC):

java 复制代码
public class OrderService {
    // OrderService 自己负责创建 UserService 的实例
    private UserService userService = new UserService();

    public void createOrder() {
        userService.verifyUser();
        // ... 创建订单的逻辑
    }
}

问题: OrderServiceUserService 紧密耦合在一起。如果我想测试 OrderService,或者想把 UserService 替换成一个 mock 实现,会非常困难,因为我必须修改 OrderService 的源代码。

IoC的解决方案:

控制反转将这个创建和组装的控制权"反转"了 。不再是对象A自己去控制依赖的创建,而是由一个外部的容器(在Spring中就是IoC容器)来负责。容器会统一管理和创建所有对象,并在需要的时候,将依赖"注入"到对象中。

IoC后的变化:

java 复制代码
public class OrderService {
    // OrderService 不再自己创建,而是等待容器"注入"进来
    private UserService userService;

    // 通过构造器或Setter方法,接收外部传来的依赖
    public OrderService(UserService userService) {
        this.userService = userService;
    }

    public void createOrder() {
        userService.verifyUser();
        // ... 创建订单的逻辑
    }
}

现在,OrderService 不再关心 UserService 是从哪来的,它只声明:"我需要一个 UserService"。控制权从 OrderService 转移到了外部的IoC容器。

所以,IoC是一种设计原则/思想,它的目标是实现程序的松耦合。


二、 依赖注入(DI)

1. 核心思想: "一种实现方式"

依赖注入是实现控制反转(IoC)这一思想的最常见、最典型的方式 。它描述了实现"控制反转"的具体动作:由容器通过构造函数、Setter方法或接口等方式,将依赖对象"注入"到目标对象中。

关键点: "注入"是一个被动的过程。对象只是被动地接收它的依赖,而不是主动去寻找或创建它们。

Spring中依赖注入的主要方式:

  1. 构造器注入(推荐)

    java 复制代码
    @Component
    public class OrderService {
        private final UserService userService;
    
        // 容器会调用这个构造器,并传入一个UserService实例
        public OrderService(UserService userService) {
            this.userService = userService;
        }
    }
  2. Setter注入

    java 复制代码
    @Component
    public class OrderService {
        private UserService userService;
    
        // 容器会调用这个Setter方法,并传入一个UserService实例
        @Autowired
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    }
  3. 字段注入(不推荐,但常见)

    java 复制代码
    @Component
    public class OrderService {
        @Autowired // 容器通过反射机制直接为这个字段赋值
        private UserService userService;
    }

三、 IoC 和 DI 的关系

这是一个非常关键的区别,可以用一个简单的类比来理解:

  • 控制反转(IoC)是一个目标、一个设计思想。 就像你的目标是 "不用自己做饭也能吃到饭"

  • 依赖注入(DI)是实现这个目标的一种具体手段。 就像 "点外卖" 是实现"不用自己做饭"这个目标的一种手段。

关系总结:

  • IoC是"道",DI是"术"。IoC是理论,DI是实践。

  • DI是实现IoC的方式之一,但并非唯一方式。服务定位器模式(Service Locator Pattern)也可以实现IoC,但在Spring生态和现代Java开发中,DI是绝对的主流。

  • 当我们在谈论Spring框架的IoC时,实际上我们谈论的就是它的DI机制 。Spring通过其强大的IoC容器(ApplicationContext),使用DI来管理对象的生命周期和依赖关系,从而实现了控制反转。

总结

特性 控制反转 (IoC) 依赖注入 (DI)
本质 一种设计原则、思想 一种设计模式、实现技术
关注点 谁拥有控制权(容器 vs. 程序自身) 如何提供依赖(注入 vs. 主动创建)
关系 目标 实现该目标的手段

简单来说:为了实现程序的松耦合(IoC),我们采用了依赖注入(DI)这种方式。 这就是它们之间的关系。

3. Spring中依赖注入有哪几种主要方式?

  • 回答要点:

    1. 构造器注入:通过类的构造函数注入依赖。推荐使用,因为它能保证依赖在对象创建时就可用,且对象是不可变的(immutable)。

    2. Setter注入:通过类的Setter方法注入依赖。更灵活,适合可选依赖。

    3. 字段注入 :使用@Autowired注解直接标注在字段上。不推荐,因为它绕过了封装性,使代码更难测试和耦合于Spring框架。

针对依赖注入可能出现的问题以及解决方案请看我另一篇文章:

https://blog.csdn.net/weixin_46322148/article/details/152219016?sharetype=blogdetail&sharerId=152219016&sharerefer=PC&sharesource=weixin_46322148&spm=1011.2480.3001.8118

4. Spring IoC容器中Bean的作用域(Scope)有哪些?

  • 回答要点:

    1. singleton(默认):每个Spring容器中,一个Bean定义只对应一个实例。

    2. prototype :每次请求(通过getBean()或注入)都会创建一个新的Bean实例。

    3. request:每个HTTP请求创建一个实例(仅适用于Web应用)。

    4. session:每个HTTP会话创建一个实例(仅适用于Web应用)。

    5. application:每个ServletContext生命周期内一个实例(仅适用于Web应用)。

    6. websocket:每个WebSocket会话内一个实例。

5. 解释一下Spring中的面向切面编程(AOP)及其核心概念。

  • 回答要点: AOP允许将横切关注点(如日志、事务、安全)从业务逻辑中分离出来,提高模块化。

    • Aspect(切面):横切关注点的模块化,是一个类,包含通知和切点。

    • Join Point(连接点):程序执行过程中的一个点,如方法调用或异常抛出。在Spring AOP中,它总是代表方法的执行。

    • Pointcut(切点):一个表达式,用于匹配哪些连接点需要被切入。它定义了"在哪里"执行通知。

    • Advice(通知) :在特定连接点上执行的动作。类型包括:@Before, @After, @AfterReturning, @AfterThrowing, @Around

    • Weaving(织入):将切面应用到目标对象以创建代理对象的过程。Spring在运行时完成织入。

一、什么是AOP?为什么要用它?

核心思想

AOP(Aspect-Oriented Programming) 是一种编程范式,它允许将横切关注点从业务逻辑中分离出来,实现更好的模块化。

解决的问题

在传统编程中,像日志、事务、安全等代码会分散在各个业务方法中:

java 复制代码
// 业务方法中混杂着横切关注点
public void createOrder(Order order) {
    // 1. 日志记录 - 横切关注点
    log.info("开始创建订单: " + order.getId());
    
    try {
        // 2. 事务开始 - 横切关注点  
        TransactionManager.begin();
        
        // 3. 核心业务逻辑 - 这才是我们真正关心的
        orderDao.save(order);
        inventoryService.deduct(order.getItems());
        
        // 4. 事务提交 - 横切关注点
        TransactionManager.commit();
        
    } catch (Exception e) {
        // 5. 事务回滚 - 横切关注点
        TransactionManager.rollback();
        // 6. 异常日志 - 横切关注点
        log.error("创建订单失败", e);
        throw e;
    } finally {
        // 7. 资源清理 - 横切关注点
        ResourceCleaner.clean();
    }
}

AOP的解决方案:将这些横切关注点提取到独立的模块(切面)中:

java 复制代码
// 业务方法变得干净纯粹
public void createOrder(Order order) {
    // 只关注核心业务逻辑
    orderDao.save(order);
    inventoryService.deduct(order.getItems());
}

二、AOP核心概念详解

1. Aspect(切面)
  • 定义:横切关注点的模块化,是一个类

  • 类比:就像OOP中的类,但关注的是横切功能

  • 示例:日志切面、事务切面、安全切面

java 复制代码
@Component
@Aspect  // 声明这是一个切面
public class LoggingAspect {
    // 包含各种通知和切点定义
}
2. Join Point(连接点)
  • 定义:程序执行过程中的一个点

  • 在Spring AOP中总是代表方法的执行

  • 示例UserService.createUser()方法的调用、OrderDao.save()方法的执行

3. Pointcut(切点)
  • 定义 :匹配连接点的表达式,定义了"在哪些地方"执行通知

  • 作用:筛选出需要被增强的连接点

java 复制代码
// 切点表达式示例
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {}  // 匹配service包下所有类的所有方法

@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}  // 匹配所有带有@Transactional注解的方法
4. Advice(通知)
  • 定义 :在特定连接点上执行的动作

  • 类型

通知类型 执行时机 应用场景
@Before 方法执行 权限检查、参数验证
@AfterReturning 方法成功返回 记录返回结果、缓存结果
@AfterThrowing 方法抛出异常 异常处理、错误日志
@After 方法结束后(无论成功或异常) 资源清理
@Around 环绕方法执行(最强大) 事务管理、性能监控
5. Weaving(织入)
  • 定义:将切面应用到目标对象创建代理对象的过程

  • Spring的实现方式运行时织入(通过动态代理)


三、完整代码示例

定义一个切面
java 复制代码
@Component
@Aspect
public class LoggingAspect {
    
    // 定义切点:匹配Service层所有方法
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    // 前置通知
    @Before("serviceLayer()")
    public void logMethodStart(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        log.info("开始执行 {}.{}()", className, methodName);
    }
    
    // 返回通知
    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void logMethodReturn(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("方法 {} 执行成功,返回值: {}", methodName, result);
    }
    
    // 异常通知
    @AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
    public void logMethodException(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        log.error("方法 {} 执行异常: {}", methodName, ex.getMessage());
    }
    
    // 环绕通知(最强大)
    @Around("serviceLayer()")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long executionTime = System.currentTimeMillis() - startTime;
            String methodName = joinPoint.getSignature().getName();
            log.info("方法 {} 执行耗时: {} ms", methodName, executionTime);
        }
    }
}
业务类(完全不知道AOP的存在)
java 复制代码
@Service
public class UserService {
    
    public User createUser(String username, String email) {
        // 纯粹的业务逻辑,没有任何横切关注点
        User user = new User(username, email);
        userRepository.save(user);
        return user;
    }
    
    public User findUserById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException("用户不存在"));
    }
}

四、Spring AOP的实现机制

代理模式

Spring AOP通过动态代理实现:

  • JDK动态代理:针对实现了接口的类

  • CGLIB代理:针对没有实现接口的类(通过继承)

启用AOP支持
java 复制代码
@Configuration
@EnableAspectJAutoProxy  // 启用AOP自动代理
@ComponentScan("com.example")
public class AppConfig {
}

在我的其他文章中详细介绍了Spring AOP的源码级实现原理Spring AOP 实现机制源码分析


五、Spring AOP vs AspectJ

特性 Spring AOP AspectJ
织入时机 运行时 编译时、类加载时
能力范围 仅方法级别 字段、构造器、方法等
性能 较好 更好(编译时优化)
复杂度 简单易用 更强大但复杂
依赖 仅Spring容器 需要特殊编译器

总结

AOP的核心价值在于:

  1. 关注点分离:业务逻辑与横切关注点彻底解耦

  2. 代码复用:通用功能在一个地方实现,多处使用

  3. 可维护性:修改横切逻辑只需修改切面,不影响业务代码

  4. 代码简洁:业务方法保持纯粹,只关注核心逻辑

Spring AOP通过代理模式 + 注解配置,让开发者能够以声明式的方式实现横切关注点的模块化,是现代Java企业级开发中不可或缺的重要技术。

6. Spring AOP默认使用哪种代理机制?它和AspectJ有什么区别?

  • 回答要点:

    • 默认机制 :Spring AOP默认使用基于JDK动态代理 (如果目标对象实现了接口)或基于CGLIB的字节码增强(如果目标对象未实现接口)。

    • 与AspectJ的区别

      • 能力:Spring AOP只支持方法级别的连接点。AspectJ更强大,支持字段、构造器等的织入。

      • 织入时机:Spring AOP是运行时织入(通过代理),AspectJ主要是编译时或类加载时织入。

      • 性能:AspectJ通常性能更好,但Spring AOP更简单,足以解决大部分企业应用问题。

      • 依赖:Spring AOP无需特殊编译流程,与Spring容器紧密集成。AspectJ需要特殊的编译器(ajc)或织入器。

一、Spring AOP的默认代理机制

代理机制选择规则

Spring AOP默认使用动态代理,具体选择哪种代理机制遵循以下规则:

java 复制代码
if (目标对象实现了至少一个接口) {
    // 使用 JDK动态代理
    return JDK动态代理;
} else {
    // 使用 CGLIB代理  
    return CGLIB代理;
}
1. JDK动态代理

工作原理:基于接口实现,在运行时动态创建接口的代理类。

java 复制代码
// 示例:目标类实现了接口
public interface UserService {
    void createUser(String name);
}

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void createUser(String name) {
        System.out.println("创建用户: " + name);
    }
}

// Spring会创建类似这样的代理类(概念性代码)
public class $Proxy implements UserService {
    private UserService target;
    
    public $Proxy(UserService target) {
        this.target = target;
    }
    
    @Override
    public void createUser(String name) {
        // 前置增强
        System.out.println("方法开始执行...");
        
        // 调用目标方法
        target.createUser(name);
        
        // 后置增强
        System.out.println("方法执行结束...");
    }
}

特点

  • 要求目标类必须实现至少一个接口

  • 基于Java反射机制

  • 代理对象与目标对象是兄弟关系(都实现相同接口)

2. CGLIB代理

工作原理:通过字节码技术,生成目标类的子类作为代理类。

java 复制代码
// 示例:目标类没有实现接口
@Service
public class ProductService {  // 没有实现接口
    public void saveProduct(Product product) {
        System.out.println("保存产品: " + product.getName());
    }
}

// CGLIB会创建类似这样的代理类(概念性代码)
public class ProductService$$EnhancerByCGLIB extends ProductService {
    private ProductService target;
    
    @Override
    public void saveProduct(Product product) {
        // 前置增强
        System.out.println("方法开始执行...");
        
        // 调用父类方法(目标方法)
        super.saveProduct(product);
        
        // 后置增强
        System.out.println("方法执行结束...");
    }
}

特点

  • 不要求目标类实现接口

  • 基于字节码操作(使用ASM库)

  • 代理对象与目标对象是父子关系(继承关系)

  • 不能代理final类和final方法


二、强制使用CGLIB代理

你可以强制Spring始终使用CGLIB代理:

java 复制代码
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)  // 强制使用CGLIB
public class AppConfig {
}

或者在XML配置中:

XML 复制代码
<aop:aspectj-autoproxy proxy-target-class="true"/>

三、Spring AOP 与 AspectJ 的详细对比

特性 Spring AOP AspectJ
织入时机 运行时织入 编译时/类加载时织入
代理机制 动态代理(JDK/CGLIB) 无代理,直接修改字节码
连接点支持 仅方法执行 全面的连接点支持 : • 方法执行 • 构造器执行 • 字段访问 • 静态初始化 • 对象初始化等
性能 较好(有代理开销) 优秀(编译时优化,无运行时开销)
配置方式 注解或XML配置 注解、XML或AspectJ特有语法
依赖 仅Spring容器 需要AspectJ编译器(ajc)或织入器
应用场景 Spring容器管理的Bean 任何Java对象,包括非Spring管理的对象
复杂度 简单易用 功能强大但相对复杂

四、技术细节对比

1. 织入时机对比

Spring AOP(运行时织入)

java 复制代码
// 应用启动时创建代理
UserService proxy = (UserService) Proxy.newProxyInstance(...);
// 或通过CGLIB创建子类
UserService proxy = enhancer.create();

// 方法调用时通过代理间接执行
proxy.createUser("John"); // 经过代理增强

AspectJ(编译时织入)

java 复制代码
// 编译后的字节码已经包含切面逻辑
// 直接调用,无代理开销
userService.createUser("John"); // 字节码中已织入增强逻辑
2. 连接点支持能力对比

Spring AOP只能拦截方法执行

java 复制代码
// 可以拦截
userService.createUser("John");

// 无法拦截
user.name = "John";          // 字段赋值
new UserService();           // 构造器调用
UserService.staticMethod();  // 静态方法

AspectJ可以拦截各种连接点

java 复制代码
// 都可以拦截
pointcut methodCall(): execution(* *(..));           // 方法执行
pointcut constructorCall(): execution(new(..));      // 构造器执行  
pointcut fieldAccess(): get(* *) || set(* *);        // 字段访问
pointcut staticInitialization(): staticinitialization(*); // 静态初始化
3. 性能对比示例

假设有一个性能监控切面:

Spring AOP实现

java 复制代码
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    try {
        return joinPoint.proceed();
    } finally {
        long duration = System.currentTimeMillis() - start;
        // 记录执行时间...
    }
}
// 每次调用都有代理开销 + 反射开销

AspectJ实现

java 复制代码
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
    long start = System.currentTimeMillis();
    try {
        return joinPoint.proceed();
    } finally {
        long duration = System.currentTimeMillis() - start;
        // 记录执行时间...
    }
}
// 编译后直接嵌入字节码,无额外开销

五、如何选择?

选择 Spring AOP 的情况:
  • 只需要方法级别的拦截

  • 所有需要增强的对象都是Spring Bean

  • 项目已经使用Spring框架

  • 希望配置简单,学习曲线平缓

选择 AspectJ 的情况:
  • 需要字段访问、构造器调用等非方法级别的拦截

  • 需要拦截非Spring管理的对象

  • 对性能有极致要求

  • 项目架构复杂,需要更强大的AOP能力

在Spring中结合使用:
java 复制代码
@Configuration
@EnableAspectJAutoProxy
@EnableLoadTimeWeaving  // 启用加载时织入,使用AspectJ增强Spring AOP
public class AppConfig {
}

总结

Spring AOP的默认代理机制

  • 优先使用JDK动态代理(当目标类实现接口时)

  • 回退到CGLIB代理(当目标类没有实现接口时)

Spring AOP vs AspectJ的核心区别

  • Spring AOP是代理模式AspectJ是字节码织入

  • Spring AOP简单实用AspectJ功能强大

  • Spring AOP适用于大多数企业应用AspectJ适用于特殊高级场景

理解这个区别有助于你在实际项目中做出正确的技术选型,避免在不合适的场景下使用不合适的AOP实现。

7. @Autowired@Resource 注解有什么区别?

  • 回答要点:

    • 来源@Autowired是Spring框架的注解;@Resource是JSR-250标准注解,属于Java规范。

    • 默认注入方式

      • @Autowired默认按类型(byType) 进行装配。如果找到多个相同类型的Bean,再按名称(byName) 匹配(需要配合@Qualifier)。

      • @Resource默认按名称(byName) 进行装配。如果指定了name属性,则按该名称查找;如果未指定,则将字段名或Setter方法名作为Bean名称。如果按名称找不到,才会回退到按类型装配。

一、核心区别总览

特性 @Autowired @Resource
来源 Spring框架定义 JSR-250 (Java标准注解)
默认注入方式 按类型(byType) 按名称(byName)
是否支持按名称 需要配合@Qualifier 内置name属性
必需性 可通过required=false设置可选 默认必须存在
适用范围 更广泛(构造器、方法、参数等) 字段、Setter方法

二、详细对比分析

1. 来源不同

@Autowired

  • 属于Spring框架的专有注解

  • 包名:org.springframework.beans.factory.annotation.Autowired

@Resource

  • 属于Java标准注解(JSR-250)

  • 包名:javax.annotation.Resource

  • 需要JDK 1.6+,在Java 9+中需要单独引入javax.annotation-api

java 复制代码
// 需要添加依赖(如果使用Java 9+)
<dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
</dependency>
2. 默认注入方式(最核心的区别)
@Autowired - 默认按类型匹配
java 复制代码
@Component
public class OrderService {
    
    @Autowired  // 默认按类型查找 PaymentProcessor
    private PaymentProcessor paymentProcessor;
}

匹配过程

  1. 在容器中查找PaymentProcessor类型的Bean

  2. 如果找到唯一一个,直接注入

  3. 如果找到多个 ,需要进一步按名称匹配或使用@Qualifier

@Resource - 默认按名称匹配
java 复制代码
@Component
public class OrderService {
    
    @Resource  // 默认按字段名"paymentProcessor"查找Bean
    private PaymentProcessor paymentProcessor;
}

匹配过程

  1. 首先按名称(字段名paymentProcessor)查找Bean

  2. 如果按名称找不到,再回退到按类型查找


三、多Bean冲突的解决方案

场景:有两个同类型的Bean
java 复制代码
@Configuration
public class AppConfig {
    
    @Bean
    public PaymentProcessor alipayProcessor() {
        return new AlipayProcessor();
    }
    
    @Bean
    public PaymentProcessor wechatProcessor() {
        return new WechatProcessor();
    }
}
解决方案对比
1. 使用 @Autowired + @Qualifier
复制代码
@Component
public class OrderService {
    
    @Autowired
    @Qualifier("alipayProcessor")  // 明确指定Bean名称
    private PaymentProcessor paymentProcessor;
}
2. 使用 @Resource 的 name 属性
复制代码
@Component
public class OrderService {
    
    @Resource(name = "alipayProcessor")  // 直接指定Bean名称
    private PaymentProcessor paymentProcessor;
}
3. 或者依赖字段名匹配
复制代码
@Component
public class OrderService {
    
    // 自动匹配名为 alipayProcessor 的Bean
    @Resource
    private PaymentProcessor alipayProcessor;
}

四、完整代码示例

定义多个同类型Bean
java 复制代码
public interface PaymentProcessor {
    void processPayment(double amount);
}

@Component("creditCardProcessor")
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("信用卡支付: " + amount);
    }
}

@Component("paypalProcessor") 
public class PaypalProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("PayPal支付: " + amount);
    }
}
使用 @Autowired 的几种方式
java 复制代码
@Service
public class PaymentService {
    
    // 方式1: 按类型 + @Qualifier指定名称
    @Autowired
    @Qualifier("creditCardProcessor")
    private PaymentProcessor primaryProcessor;
    
    // 方式2: 构造器注入 + @Qualifier
    private final PaymentProcessor secondaryProcessor;
    
    @Autowired
    public PaymentService(@Qualifier("paypalProcessor") PaymentProcessor processor) {
        this.secondaryProcessor = processor;
    }
    
    // 方式3: 如果只有一个Bean,直接按类型注入
    @Autowired
    private SomeUniqueService uniqueService;
}
使用 @Resource 的几种方式
java 复制代码
@Service  
public class PaymentService {
    
    // 方式1: 按名称匹配(推荐)
    @Resource(name = "creditCardProcessor")
    private PaymentProcessor primaryProcessor;
    
    // 方式2: 依赖字段名自动匹配
    @Resource
    private PaymentProcessor paypalProcessor;  // 自动匹配名为paypalProcessor的Bean
    
    // 方式3: Setter方法注入
    private PaymentProcessor secondaryProcessor;
    
    @Resource
    public void setSecondaryProcessor(@Qualifier("creditCardProcessor") PaymentProcessor processor) {
        this.secondaryProcessor = processor;
    }
}

五、特殊场景处理

1. 可选依赖
复制代码
// @Autowired 的可选依赖
@Autowired(required = false)  // 如果找不到Bean,注入null
private OptionalService optionalService;

// @Resource 默认是必须的,找不到会抛异常
// 要实现可选,需要配合其他方式
2. 构造器注入
复制代码
@Component
public class OrderService {
    private final PaymentProcessor paymentProcessor;
    
    // @Autowired 可以用于构造器(Spring 4.3+ 可省略)
    @Autowired
    public OrderService(PaymentProcessor paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }
    
    // @Resource 不能用于构造器!
}
3. 集合注入
复制代码
@Component
public class PaymentRouter {
    
    // 注入所有 PaymentProcessor 类型的Bean
    @Autowired
    private List<PaymentProcessor> processors;
    
    // @Resource 也可以,但语义上 @Autowired 更合适
    @Resource
    private List<PaymentProcessor> allProcessors;
}

六、最佳实践建议

推荐使用 @Autowired 的情况:
  1. 构造器注入(特别是强制依赖)

  2. 明确按类型匹配的场景

  3. 需要注入集合所有实现

  4. 已经在大量使用Spring生态

推荐使用 @Resource 的情况:
  1. 明确按名称匹配的场景

  2. 希望代码不依赖Spring特定注解

  3. 与其他JSR-250标准注解(如@PostConstruct)配合使用

现代Spring开发趋势:
java 复制代码
@Component
public class OrderService {
    
    // 推荐:使用构造器注入(不可变、易于测试)
    private final PaymentProcessor paymentProcessor;
    private final UserService userService;
    
    // Spring 4.3+ 可以省略 @Autowired
    public OrderService(PaymentProcessor paymentProcessor, UserService userService) {
        this.paymentProcessor = paymentProcessor;
        this.userService = userService;
    }
}

总结

核心区别记忆点

  • @Autowired = Spring专属 + 先类型后名称

  • @Resource = Java标准 + 先名称后类型

选择建议

  • 新项目建议统一使用 @Autowired + 构造器注入

  • 需要避免Spring依赖或明确按名称注入时使用 @Resource

  • 保持团队内部的一致性比选择哪个注解更重要

理解这些区别有助于你在实际开发中根据具体场景选择合适的注入方式,避免因注解使用不当导致的注入失败问题。

8. 如何将一个类声明为Spring Bean?有哪些方式?

  • 回答要点:

    1. 注解方式(最常用)

      • @Component:通用注解。

      • @Service:用于服务层。

      • @Repository:用于数据访问层(DAO),具有异常转换等额外好处。

      • @Controller / @RestController:用于Web控制层。

    2. Java配置类 :在配置类中使用@Bean注解的方法。

    3. XML配置 :在XML文件中使用<bean>标签(现在已不常用)。

9. Spring Bean的生命周期是怎样的?

  • 回答要点: 关键步骤包括:

    1. 实例化(Instantiation)

    2. 属性填充/依赖注入(Populate properties)

    3. BeanNameAware's setBeanName

    4. BeanFactoryAware's setBeanFactory

    5. ApplicationContextAware's setApplicationContext

    6. BeanPostProcessor's postProcessBeforeInitialization

    7. @PostConstruct 注解的方法

    8. InitializingBean's afterPropertiesSet

    9. 自定义的 init-method

    10. BeanPostProcessor's postProcessAfterInitializationAOP代理通常在此处生成

    11. Bean已就绪,可使用

    12. @PreDestroy 注解的方法

    13. DisposableBean's destroy

    14. 自定义的 destroy-method

一、Bean生命周期核心阶段总览

Spring Bean的生命周期可以分为三大阶段:

  1. 实例化与依赖注入

  2. 初始化

  3. 销毁

下面是详细的流程图和解释:


二、详细阶段解析

阶段一:Bean实例化与依赖注入
1. 实例化(Instantiation)
  • 作用:创建Bean的实例对象

  • 方式:通过构造函数反射创建

  • 时机:容器启动时或第一次获取Bean时

java 复制代码
// Spring通过反射调用构造函数
Constructor<?> constructor = beanClass.getDeclaredConstructor();
Object beanInstance = constructor.newInstance();
2. 属性填充/依赖注入(Populate Properties)
  • 作用:设置Bean的属性值,注入依赖

  • 方式:通过Setter方法、字段注入、构造器注入

java 复制代码
@Component
public class UserService {
    // 依赖注入发生在这个阶段
    @Autowired
    private UserRepository userRepository;
    
    private String appName;
    
    // Setter注入
    @Autowired
    public void setAppName(String appName) {
        this.appName = appName;
    }
}
阶段二:Bean初始化(Initialization)
3. Aware接口回调

Bean如果实现了特定的Aware接口,会在此阶段收到回调:

java 复制代码
@Component
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
    
    @Override
    public void setBeanName(String name) {
        // 获取Bean在容器中的名称
        System.out.println("Bean名称: " + name);
    }
    
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // 获取BeanFactory引用
        System.out.println("BeanFactory已设置");
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 获取ApplicationContext引用
        System.out.println("ApplicationContext已设置");
    }
}
4. BeanPostProcessor前置处理
  • 作用:在初始化前后进行自定义处理

  • 重要:Spring AOP的代理就是通过BeanPostProcessor实现的

java 复制代码
@Component
public class CustomBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before初始化: " + beanName);
        return bean; // 可以返回包装后的Bean
    }
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After初始化: " + beanName);
        return bean;
    }
}
5. 初始化方法执行(按顺序)
5.1 @PostConstruct 注解方法
java 复制代码
@Component
public class UserService {
    
    @PostConstruct
    public void init() {
        System.out.println("@PostConstruct方法执行");
        // 初始化资源、启动线程等
    }
}
5.2 InitializingBean 接口
java 复制代码
@Component
public class UserService implements InitializingBean {
    
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean.afterPropertiesSet()执行");
        // 属性设置完成后执行
    }
}
5.3 自定义 init-method
java 复制代码
@Component
public class UserService {
    
    public void customInit() {
        System.out.println("自定义init-method执行");
    }
}

// 配置类中指定
@Configuration
public class AppConfig {
    @Bean(initMethod = "customInit")
    public UserService userService() {
        return new UserService();
    }
}
6. BeanPostProcessor后置处理
  • 作用:在初始化完成后进行最终处理

  • 关键AOP代理对象通常在此阶段生成

java 复制代码
// Spring的AOP就是通过BeanPostProcessor实现的
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
    // 检查是否需要为这个Bean创建代理
    if (needProxy(bean)) {
        return createProxy(bean); // 创建代理对象
    }
    return bean;
}
阶段三:Bean就绪与销毁
7. Bean就绪可用
  • 此时Bean已经完全初始化,可以被应用程序使用

  • 存储在Spring容器的单例缓存中

8. 容器关闭与Bean销毁

当Spring容器关闭时(调用close()方法),执行销毁流程:

8.1 @PreDestroy 注解方法
java 复制代码
@Component
public class UserService {
    
    @PreDestroy
    public void preDestroy() {
        System.out.println("@PreDestroy方法执行");
        // 释放资源、关闭连接等
    }
}
8.2 DisposableBean 接口
java 复制代码
@Component
public class UserService implements DisposableBean {
    
    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean.destroy()执行");
        // 清理工作
    }
}
8.3 自定义 destroy-method
java 复制代码
@Component
public class UserService {
    
    public void customDestroy() {
        System.out.println("自定义destroy-method执行");
    }
}

// 配置类中指定
@Configuration
public class AppConfig {
    @Bean(destroyMethod = "customDestroy")
    public UserService userService() {
        return new UserService();
    }
}

三、完整代码示例

java

复制代码
@Component
public class LifecycleDemoBean implements 
        BeanNameAware, BeanFactoryAware, 
        ApplicationContextAware, InitializingBean, DisposableBean {
    
    private String data;

    // 1. 构造方法(实例化)
    public LifecycleDemoBean() {
        System.out.println("1. 构造方法执行 - 实例化");
    }

    // 2. 依赖注入
    @Autowired
    public void setData(String data) {
        this.data = data;
        System.out.println("2. 依赖注入 - setData: " + data);
    }

    // 3. BeanNameAware
    @Override
    public void setBeanName(String name) {
        System.out.println("3. BeanNameAware - Bean名称: " + name);
    }

    // 4. BeanFactoryAware
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("4. BeanFactoryAware - BeanFactory设置");
    }

    // 5. ApplicationContextAware
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("5. ApplicationContextAware - ApplicationContext设置");
    }

    // 6. @PostConstruct
    @PostConstruct
    public void postConstruct() {
        System.out.println("6. @PostConstruct方法执行");
    }

    // 7. InitializingBean
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("7. InitializingBean.afterPropertiesSet()执行");
    }

    // 8. 自定义init方法
    public void customInit() {
        System.out.println("8. 自定义init方法执行");
    }

    // 业务方法
    public void doBusiness() {
        System.out.println("9. 执行业务逻辑");
    }

    // 10. @PreDestroy
    @PreDestroy
    public void preDestroy() {
        System.out.println("10. @PreDestroy方法执行");
    }

    // 11. DisposableBean
    @Override
    public void destroy() throws Exception {
        System.out.println("11. DisposableBean.destroy()执行");
    }

    // 12. 自定义destroy方法
    public void customDestroy() {
        System.out.println("12. 自定义destroy方法执行");
    }
}

四、不同作用域Bean的生命周期

Singleton(单例,默认)
  • 实例化:容器启动时(懒加载除外)

  • 销毁:容器关闭时

Prototype(原型)
  • 实例化:每次获取时

  • 销毁:Spring不管理原型Bean的销毁,由Java GC处理

Web相关作用域(Request、Session等)
  • 实例化:每次HTTP请求/会话开始时

  • 销毁:HTTP请求/会话结束时


五、总结

Spring Bean生命周期的核心价值:

  1. 精细控制:Spring提供了完整的生命周期管理

  2. 扩展性强:通过多个扩展点(Aware接口、BeanPostProcessor等)可以深度定制Bean行为

  3. 解耦:业务代码与框架代码分离

  4. 资源管理:规范的初始化和销毁流程,确保资源正确释放

面试回答要点:

  • 明确三个阶段:实例化 → 初始化 → 销毁

  • 记住关键扩展点的执行顺序

  • 理解AOP代理的生成时机(BeanPostProcessor后置处理)

  • 能够解释不同作用域Bean的生命周期差异

掌握Bean生命周期对于理解Spring框架的运作机制和解决复杂业务场景问题至关重要。


第二部分:Spring Data 访问与事务管理

10. Spring的JDBC模块是如何简化数据库操作的?

  • 回答要点: 主要通过JdbcTemplate类。它处理了连接管理、异常处理等繁琐的样板代码,开发者只需关注SQL语句和结果映射。它将检查型异常(如SQLException)转换为Spring的DataAccessException层次结构中的非检查型异常。

11. 介绍一下Spring的事务管理。声明式事务是如何实现的?

  • 回答要点: Spring提供了编程式事务和声明式事务。

    • 声明式事务 :是主流方式,通过AOP实现。使用@Transactional注解在方法或类上,Spring会在方法执行前开启事务,方法执行后根据是否抛出异常提交或回滚事务。

    • 实现原理 :Spring为被@Transactional注解的类创建代理对象。当调用代理对象的方法时,事务拦截器(TransactionInterceptor)会介入,管理事务的边界。

一、Spring事务管理概述

1. 两种事务管理方式
编程式事务(Programmatic)

手动编写事务管理代码,灵活性高但代码侵入性强:

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    public void createOrder(Order order) {
        // 手动定义事务属性
        TransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        
        try {
            // 业务逻辑
            orderDao.save(order);
            inventoryService.deduct(order.getItems());
            
            // 提交事务
            transactionManager.commit(status);
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            throw e;
        }
    }
}
声明式事务(Declarative)★ 推荐

通过注解或配置声明事务需求,代码简洁,关注业务逻辑:

java 复制代码
@Service
public class OrderService {
    
    @Transactional  // 一行注解搞定事务管理
    public void createOrder(Order order) {
        // 纯粹的业务逻辑
        orderDao.save(order);
        inventoryService.deduct(order.getItems());
    }
}

二、声明式事务的实现原理

声明式事务的核心实现基于 AOP(面向切面编程)动态代理

实现流程图
详细实现步骤
1. 代理对象创建

Spring容器在创建Bean时,如果发现Bean的方法上有@Transactional注解,会为其创建代理对象:

java 复制代码
// 原始对象
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void createOrder(Order order) {
        // 业务逻辑
    }
}

// Spring创建的代理对象(概念上的)
public class OrderServiceProxy extends OrderServiceImpl {
    private TransactionInterceptor transactionInterceptor;
    
    @Override
    public void createOrder(Order order) {
        // 代理逻辑:先执行事务拦截器,再调用原始方法
        transactionInterceptor.invoke(this::superCreateOrder, order);
    }
    
    private void superCreateOrder(Order order) {
        super.createOrder(order);
    }
}
2. 事务拦截器(TransactionInterceptor)

这是事务管理的核心组件,它实现了AOP的MethodInterceptor接口:

java 复制代码
public class TransactionInterceptor implements MethodInterceptor {
    
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 1. 获取事务属性
        TransactionAttribute txAttr = getTransactionAttribute(invocation.getMethod());
        
        // 2. 创建或加入事务
        TransactionStatus status = transactionManager.getTransaction(txAttr);
        
        Object result;
        try {
            // 3. 执行原始业务方法
            result = invocation.proceed();
            
            // 4. 提交事务
            transactionManager.commit(status);
            
        } catch (Exception ex) {
            // 5. 异常处理:判断是否需要回滚
            completeTransactionAfterThrowing(status, ex);
            throw ex;
        }
        
        return result;
    }
    
    protected void completeTransactionAfterThrowing(TransactionStatus status, Exception ex) {
        // 根据异常类型决定是否回滚
        if (rollbackOn(ex)) {
            transactionManager.rollback(status);
        } else {
            transactionManager.commit(status);
        }
    }
}
3. 事务管理器(PlatformTransactionManager)

Spring事务抽象的核心接口,不同的数据访问技术有不同的实现:

复制代码
// Spring事务管理器体系
PlatformTransactionManager
    ├── DataSourceTransactionManager      // JDBC、MyBatis
    ├── HibernateTransactionManager       // Hibernate
    ├── JpaTransactionManager            // JPA
    └── JtaTransactionManager            // 分布式事务

三、@Transactional 注解详解

1. 基本使用
java 复制代码
@Service
public class UserService {
    
    // 最简单的使用
    @Transactional
    public void updateUser(User user) {
        userRepository.update(user);
    }
    
    // 完整配置的事务
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.READ_COMMITTED,
        timeout = 30,
        readOnly = false,
        rollbackFor = {SQLException.class, DataAccessException.class},
        noRollbackFor = {BusinessException.class}
    )
    public void complexBusinessOperation() {
        // 复杂业务逻辑
    }
}
2. 事务传播行为(Propagation)
复制代码
public enum Propagation {
    REQUIRED,        // 默认:有事务加入,无事务新建
    REQUIRES_NEW,    // 总是新建事务,挂起当前事务
    SUPPORTS,        // 有事务加入,无事务非事务执行
    NOT_SUPPORTED,   // 非事务执行,挂起当前事务
    MANDATORY,       // 必须有事务,否则抛异常
    NEVER,           // 必须无事务,否则抛异常
    NESTED           // 嵌套事务
}

使用示例:

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private AuditService auditService;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderRepository.save(order);
        
        // 审计日志需要独立事务,即使订单创建失败也要记录
        auditService.logOperation("CREATE_ORDER", order.getId());
    }
}

@Service
class AuditService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void logOperation(String operation, Long targetId) {
        // 这个方法的执行会启动新事务,不受外部事务影响
        auditRepository.save(new AuditLog(operation, targetId));
    }
}
3. 事务隔离级别(Isolation)
复制代码
public enum Isolation {
    DEFAULT,              // 使用数据库默认
    READ_UNCOMMITTED,     // 读未提交
    READ_COMMITTED,       // 读已提交(推荐)
    REPEATABLE_READ,      // 可重复读
    SERIALIZABLE          // 串行化
}

四、配置声明式事务

1. Java配置方式(推荐)
java 复制代码
@Configuration
@EnableTransactionManagement  // 启用声明式事务管理
public class TransactionConfig {
    
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
2. XML配置方式(传统)
XML 复制代码
<!-- 启用注解驱动的事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 配置事务管理器 -->
<bean id="transactionManager" 
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

五、声明式事务的底层源码分析

1. 代理创建过程
java 复制代码
// AbstractAutoProxyCreator.createProxy()
public Object createProxy(Class<?> beanClass, String beanName, 
                         Object[] specificInterceptors, TargetSource targetSource) {
    
    // 检查是否需要为Bean创建事务代理
    if (!isInfrastructureClass(beanClass) && hasTransactionalMethods(beanClass)) {
        
        // 创建代理工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.setTarget(targetSource.getTarget());
        
        // 添加事务拦截器
        proxyFactory.addAdvice(transactionInterceptor);
        
        // 返回代理对象
        return proxyFactory.getProxy(classLoader);
    }
    
    return targetSource.getTarget();
}
2. 事务拦截器执行流程
java 复制代码
// TransactionInterceptor.invoke()
public Object invoke(MethodInvocation invocation) throws Throwable {
    // 获取目标类和方法
    Class<?> targetClass = invocation.getThis().getClass();
    Method method = invocation.getMethod();
    
    // 调用事务模板执行
    return invokeWithinTransaction(method, targetClass, new InvocationCallback() {
        public Object proceedWithInvocation() throws Throwable {
            return invocation.proceed();
        }
    });
}

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, 
                                        final InvocationCallback invocation) throws Throwable {
    
    // 1. 获取事务属性
    final TransactionAttribute txAttr = getTransactionAttributeSource()
                                      .getTransactionAttribute(method, targetClass);
    
    // 2. 获取事务管理器
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    
    // 3. 创建事务
    TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, methodIdentification);
    
    Object retVal;
    try {
        // 4. 执行原始业务方法
        retVal = invocation.proceedWithInvocation();
    } catch (Throwable ex) {
        // 5. 异常回滚处理
        completeTransactionAfterThrowing(txInfo, ex);
        throw ex;
    }
    
    // 6. 提交事务
    commitTransactionAfterReturning(txInfo);
    return retVal;
}

六、常见陷阱与最佳实践

1. 事务失效的常见场景
java 复制代码
@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // 陷阱1:自调用导致事务失效
    public void updateUser(User user) {
        // 这是自调用,不会经过代理,事务不生效!
        internalUpdate(user);
    }
    
    @Transactional
    public void internalUpdate(User user) {
        userRepository.update(user);
    }
    
    // 陷阱2:异常被捕获
    @Transactional
    public void updateWithCatch(User user) {
        try {
            userRepository.update(user);
            // 可能发生异常的操作
            riskyOperation();
        } catch (Exception e) {
            // 异常被捕获,事务不会回滚!
            log.error("操作失败", e);
        }
    }
    
    // 陷阱3:非public方法
    @Transactional
    private void privateMethod() {  // 事务不生效!
        // ...
    }
}
2. 解决方案
java 复制代码
@Service
public class CorrectUserService {
    
    @Autowired
    private CorrectUserService self;  // 注入自身代理
    
    // 正确方式1:避免自调用
    public void updateUser(User user) {
        // 通过代理对象调用,事务生效
        self.internalUpdate(user);
    }
    
    @Transactional
    public void internalUpdate(User user) {
        userRepository.update(user);
    }
    
    // 正确方式2:重新抛出异常或指定回滚异常
    @Transactional(rollbackFor = Exception.class)
    public void updateWithCorrectCatch(User user) {
        try {
            userRepository.update(user);
            riskyOperation();
        } catch (BusinessException e) {
            // 业务异常,可能不需要回滚
            log.warn("业务异常", e);
        } catch (Exception e) {
            // 系统异常,重新抛出以触发回滚
            log.error("系统异常", e);
            throw new RuntimeException(e);
        }
    }
}

七、总结

声明式事务的实现核心:

  1. AOP代理机制:通过动态代理为事务性Bean创建代理对象

  2. 事务拦截器TransactionInterceptor负责统一的事务管理逻辑

  3. 事务管理器PlatformTransactionManager提供具体的事务操作

  4. 元数据解析:通过注解或配置获取事务属性

优势:

  • 非侵入性:业务代码无需关心事务管理

  • 维护性好:事务逻辑集中管理

  • 一致性:统一的事务处理逻辑

  • 灵活性:通过注解灵活配置事务属性

适用场景:

  • 大多数企业级应用的数据操作

  • 需要保证数据一致性的业务方法

  • 复杂的多步骤操作需要原子性执行的场景

声明式事务是Spring框架最成功的设计之一,它极大地简化了企业级应用中的事务管理,让开发者能够更专注于业务逻辑的实现。

12. @Transactional 注解可以作用在哪些地方?有什么区别?

  • 回答要点: 可以作用在方法上。

    • 方法上的注解会覆盖类上的注解。

    • 建议将注解放在具体业务方法上,因为类级别的注解会对所有公共方法生效,可能不够精确。

13. @Transactional 注解的隔离级别(Isolation)和传播行为(Propagation)分别是什么?

  • 回答要点:

    • 隔离级别(Isolation) :定义了事务在并发访问数据库时的数据可见性规则。如:READ_COMMITTED(读已提交)、REPEATABLE_READ(可重复读)等。解决脏读、不可重复读、幻读问题。

    • 传播行为(Propagation):定义了当前事务方法被另一个事务方法调用时,事务应该如何传播。例如:

      • REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

      • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。

      • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式继续运行。

      • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

14. 什么情况下会导致 @Transactional 事务失效?

  • 回答要点(常见陷阱):

    1. 方法非public@Transactional只能用于public方法,非public方法不会被代理,因此事务拦截器无法介入。

    2. 自调用问题:同一个类中,一个非事务方法A调用一个事务方法B,事务不会生效。因为代理对象的方法调用才有效,自调用是目标对象内部的调用,不经过代理。

    3. 异常被捕获 :默认只在抛出运行时异常(RuntimeException)Error 时回滚。如果抛出了检查型异常(Exception)或者异常在方法内部被try-catch处理了,事务不会回滚。

    4. 数据库引擎不支持:如MySQL的MyISAM引擎不支持事务。

    5. 错误的配置 :例如没有在配置类上启用@EnableTransactionManagement

@Transactional事务失效的根本原因可以归结为:事务拦截器没有机会介入方法执行。理解Spring事务基于AOP代理的实现机制,就能更好地避免这些陷阱。

核心记忆点

  • Spring事务基于AOP代理

  • 代理对象的方法调用才经过事务拦截器

  • 异常必须抛出到事务拦截器才能触发回滚

  • 配置必须正确才能使事务机制正常工作


第三部分:Spring MVC

15. 描述一下Spring MVC处理一个HTTP请求的完整流程。

  • 回答要点:

    1. DispatcherServlet:前端控制器,接收所有请求。

    2. HandlerMapping:根据请求URL,找到对应的处理器(Controller)。

    3. HandlerAdapter:执行找到的处理器。

    4. Controller :处理请求,调用服务层,返回一个ModelAndView或视图名。

    5. ViewResolver:根据视图名解析出具体的视图对象(如JSP, Thymeleaf模板)。

    6. View:视图进行渲染,将模型数据填充到视图中。

    7. DispatcherServlet:将渲染结果返回给客户端。

16. @Controller@RestController 有什么区别?

  • 回答要点:

    • @Controller:标识一个类为Spring MVC的控制器。通常与视图技术(JSP, Thymeleaf) 配合使用,方法返回的是一个视图名称(String)或ModelAndView对象。

    • @RestController:是@Controller@ResponseBody的组合注解。用于构建RESTful Web服务 ,它的方法返回值会直接通过HttpMessageConverter写入HTTP响应体,而不是被解析为视图名。返回格式通常是JSON或XML。

选择依据:

  • 使用 @Controller 当:

    • 构建传统Web应用,需要服务器端渲染

    • 返回HTML、JSP、Thymeleaf等视图

    • 项目需要服务端页面跳转和模板渲染

  • 使用 @RestController 当:

    • 构建前后端分离应用

    • 提供RESTful API接口

    • 返回JSON/XML等数据格式

    • 开发微服务架构中的服务

核心记忆点:

  • @RestController = @Controller + @ResponseBody

  • @Controller 返回视图,@RestController 返回数据

  • 现代Web开发中,@RestController 使用越来越广泛,特别是在微服务和前后端分离架构中

17. 常用注解 @RequestMapping, @GetMapping, @PostMapping 等是做什么的?

  • 回答要点: 它们用于将HTTP请求映射到具体的控制器方法。

    • @RequestMapping:通用映射注解,可指定URL、方法(GET/POST等)。

    • @GetMapping@RequestMapping(method = RequestMethod.GET)的简写。

    • @PostMapping, @PutMapping, @DeleteMapping:同理,是其他HTTP方法的简写。

18. @PathVariable@RequestParam 有什么区别?

  • 回答要点:

    • @PathVariable:用于从URI模板中提取变量值。例如:/users/{userId}{userId}就是路径变量。

    • @RequestParam:用于从请求参数(URL查询字符串或POST表单数据)中提取值。例如:/users?name=John


第四部分:Spring Boot & 高级话题

19. Spring Boot 和 Spring Framework 是什么关系?它解决了哪些痛点?

  • 回答要点:

    • 关系:Spring Boot是建立在Spring Framework之上的一个项目,它并不是要取代Spring,而是为了简化Spring应用的初始搭建和开发过程。

    • 解决的痛点

      • 自动配置(Auto-configuration):根据项目依赖(如classpath下的jar包)自动配置Spring应用,免去了大量繁琐的XML或Java配置。

      • 起步依赖(Starter Dependencies) :提供了一组预定义的依赖描述符,简化了Maven/Gradle配置(例如,只需引入spring-boot-starter-web,就包含了Web开发所需的所有常见依赖)。

      • 内嵌Servlet容器:可以将Web应用打包成可执行的JAR文件,无需部署到外部Web服务器。

      • Actuator:提供生产级监控端点,方便检查应用运行状态。

Spring Boot 不是替代 Spring Framework,而是让 Spring 更好用的工具。

解决的四大核心痛点:

  1. 依赖管理 → 起步依赖(Starter Dependencies)

  2. 配置复杂 → 自动配置(Auto-configuration)

  3. 部署繁琐 → 内嵌容器 + 可执行JAR

  4. 监控困难 → Actuator监控端点

核心理念:约定优于配置

  • 提供合理的默认值

  • 需要时可以通过配置覆盖

  • 开发者只需关注业务逻辑

20. Spring Boot的自动配置(Auto-configuration)是如何实现的?

  • 回答要点: 核心在于spring-boot-autoconfigurejar包下的META-INF/spring.factories文件。该文件中定义了大量EnableAutoConfiguration的配置类。Spring Boot启动时,会检查classpath、已存在的Bean等条件(通过@Conditional注解,如@ConditionalOnClass, @ConditionalOnMissingBean),来决定是否启用特定的自动配置类。

21. 什么是Spring的Profile?它有什么用?

  • 回答要点: Profile是Spring用来在不同环境(如开发、测试、生产)下切换配置的机制。你可以为不同的环境定义不同的Bean或配置属性,然后通过激活特定的Profile来让对应的配置生效。例如,开发环境使用内存数据库,生产环境使用MySQL。

22. 解释一下Spring中的设计模式,例如单例模式、工厂模式、模板方法模式。

  • 回答要点:

    • 工厂模式BeanFactoryApplicationContext就是工厂模式的体现,它们负责创建和管理Bean。

    • 单例模式:Spring Bean默认就是单例的。

    • 模板方法模式JdbcTemplate, RestTemplate等是典型应用。它们定义了算法的骨架(如获取连接、执行SQL、关闭连接),而将部分步骤(如SQL语句、结果映射)延迟到子类(或通过回调接口)实现。

    • 代理模式:AOP和事务管理都大量使用了动态代理。

    • 依赖注入:本身就是一种控制反转模式的实现。

23. 你如何理解Spring的"约定优于配置"(Convention Over Configuration)理念?

  • 回答要点: 这意味着框架提供了一套默认的约定和规则,如果你遵循这些约定,就无需进行显式配置。这极大地减少了开发人员需要做的决策和配置量。Spring Boot是这一理念的极致体现,例如:默认的包扫描规则、默认的配置文件application.properties、默认的视图解析器等。

24. 在Spring中如何处理异常?

  • 回答要点:

    1. 局部异常处理 :在@Controller@RestController类中使用@ExceptionHandler注解一个方法,用于处理本控制器内的特定异常。

    2. 全局异常处理 :使用@ControllerAdvice(或@RestControllerAdvice)注解一个类,该类中定义的@ExceptionHandler方法可以处理所有控制器抛出的异常。

    3. 错误页面 :可以配置统一的错误页面(如/error)。

25. 谈谈你对Spring生态的理解,除了核心框架,你还了解或使用过哪些Spring项目?

  • 回答要点: 这是一个开放性问题,旨在考察你对Spring生态广度的了解。可以根据你的实际经验回答。

    • Spring Boot:快速开发。

    • Spring Cloud:用于构建微服务架构(如服务发现Eureka,配置中心Config,网关Gateway等)。

    • Spring Security:安全和权限管理框架。

    • Spring Data:简化数据访问,支持JPA, MongoDB, Redis等。

    • Spring Batch:批处理框架。

    • Spring Integration:企业集成模式实现。

    • Spring Session:用于管理集群环境下的Session。

相关推荐
Fency咖啡3 小时前
Spring Boot 3.x 开发 Starter 快速上手体验,通过实践理解自动装配原理
java·spring boot·后端
悟能不能悟3 小时前
什么是反应式编程
java
南方者3 小时前
【JAVA】【BUG】Java 开发中常见问题的具体示例,结合代码片段说明问题场景及原因
java·后端·debug
没有bug.的程序员3 小时前
MySQL 配置调优参数:从基础到生产级优化指南
java·数据库·mysql·优化·mysql配置调优
画船听雨眠aa3 小时前
Java8新特性——Stream API常见操作
java
crystal_pin3 小时前
indexDB
面试
Java水解3 小时前
100道互联网大厂面试题+答案
java·后端·面试
optimistic_chen3 小时前
【Java EE进阶 --- SpringBoot】Mybatis操作数据库(基础)
数据库·经验分享·spring boot·笔记·spring·java-ee·mybatis
nlog3n4 小时前
分布式计数器系统完整解决方案
java·分布式