一、面试官到底在考什么
「为什么要用 Spring?它到底解决了什么问题?」这个问题很少只有一个标准答案。面试官的潜台词通常包括四层:
第一层:你知不知道传统 Java EE 开发的问题。如果回答只停留在「大家用我也用」,说明你没有经历过原生开发,也理解不了框架存在的意义。
第二层:你能不能讲清楚 Spring 的两大核心,也就是 IoC 和 AOP。很多候选人张嘴就是「控制反转、依赖注入、面向切面」,但再问一句「没有 Spring 时对象怎么创建」,就答不上来了。
第三层:你能不能把框架能力和真实业务痛点对应起来。比如事务管理、数据库操作、Web 开发、远程调用、配置管理。
第四层:你有没有自己的判断。Spring 不是万能银弹,过度封装、隐式魔法、启动慢、配置复杂等问题同样存在。
所以这道题的正确回答姿势,不是背官网介绍,而是按照「问题场景、原生痛点、Spring 方案、Spring 背后的思想、局限与思考」这条线展开。
二、没有 Spring 之前,Java 企业级开发长什么样
2.1 对象创建和依赖关系完全交给程序员手工管理
java
public class OrderController { private OrderService orderService = new OrderServiceImpl(); public void submitOrder(Order order) { orderService.createOrder(order); } } public class OrderServiceImpl implements OrderService { private OrderDao orderDao = new OrderDaoImpl(); @Override public void createOrder(Order order) { orderDao.insert(order); } }四个严重问题:
强耦合:直接依赖具体实现类。
重复创建:每个使用方都自己 new 对象。
难以替换:单元测试时想用 Mock 对象几乎做不到。
无法统一管理:生命周期、初始化、销毁、配置散落在业务代码里。
2.2 EJB 试图解决,却把简单问题复杂化
必须继承特定接口,写 Home、Remote、Bean 类。
必须部署在昂贵的 EJB 容器里。
配置以 XML 为主,分散而繁琐。
强耦合容器 API。
Rod Johnson 在《Expert One-on-One J2EE Design and Development》里对此有过系统批判,随后创作的 Spring 框架给出了一个更轻量、更朴实的设计:你只需要写普通的 POJO,其余交给框架管理。
2.3 原生事务、数据库、Web 开发各自都有模板化难题
事务:JDBC 事务需要手动设置 autoCommit(false)、提交、回滚、释放连接。
数据库访问:每次查询都要重复写获取连接、创建 Statement、处理 ResultSet、关闭资源。
Web 开发:原生 Servlet 中每个请求都要解析参数、处理类型转换、封装返回。
横切逻辑:日志、权限、缓存、监控散落在每个业务方法里。
三、Spring 的本质:一个容器加两大核心思想
一个负责创建和管理对象及其依赖关系的轻量级容器,同时提供了 IoC 和 AOP 两大核心能力,并围绕它们构建起一整套企业开发基础设施。
四个关键词:
容器:负责对象的创建、装配、生命周期管理。
IoC:控制反转,解决「谁来创建对象、谁来决定依赖关系」。
AOP:面向切面编程,解决「横切逻辑如何统一管理」。
基础设施:事务、数据访问、Web、消息、安全、批处理、缓存、微服务等模块。
四、IoC:控制反转到底反转了什么
4.1 什么是控制反转
传统做法:对象自己 new 出依赖对象。
Spring 做法:对象声明需要什么,由容器统一创建并注入。
本质上是把「如何创建对象」和「如何使用对象」这两个关注点分离了。
4.2 没有 IoC 的伪代码对比
传统写法:
java
public class OrderController { private OrderService orderService = new OrderServiceImpl(); public void submit(Order order) { orderService.create(order); } }使用 Spring 后:
java
@RestController public class OrderController { private final OrderService orderService; public OrderController(OrderService orderService) { this.orderService = orderService; } public void submit(Order order) { orderService.create(order); } }好处:
依赖只面向接口,换实现不修改调用方代码。
集中管理对象,避免到处 new。
天然支持测试,测试时注入 Mock 实现即可。
4.3 IoC 容器如何知道要创建哪些对象
XML 配置:早期主流。
注解配置:
@Component、@Service、@Repository、@Controller。Java 配置:
@Configuration加@Bean。自动装配:Spring Boot 的
@EnableAutoConfiguration。
五、DI:依赖注入是 IoC 最常见的落地方式
5.1 IoC 和 DI 不是同一个概念
IoC 是思想,DI 是实现方式。依赖注入是实现控制反转最常见、最主流的手段。
5.2 依赖注入的三种方式
java
// 构造器注入,推荐 @Service public class OrderServiceImpl implements OrderService { private final OrderDao orderDao; public OrderServiceImpl(OrderDao orderDao) { this.orderDao = orderDao; } } // Setter 注入 @Service public class OrderServiceImpl implements OrderService { private OrderDao orderDao; @Autowired public void setOrderDao(OrderDao orderDao) { this.orderDao = orderDao; } } // 字段注入,不推荐 @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderDao orderDao; }5.3 多个同类型 Bean 时怎么注入
使用
@Primary标记首选 Bean。使用
@Qualifier按名称指定。使用
@Resource(name = "beanName")按名称注入。在需要动态选择的场景使用工厂模式或自定义路由逻辑。
六、Bean 的生命周期和作用域
6.1 Bean 生命周期
Bean 定义被读取。
实例化。
属性填充。
感知容器回调(
BeanNameAware、BeanFactoryAware、ApplicationContextAware)。前置处理(
BeanPostProcessor.postProcessBeforeInitialization)。初始化(
@PostConstruct或InitializingBean.afterPropertiesSet)。后置处理(
BeanPostProcessor.postProcessAfterInitialization)。投入使用。
容器关闭时执行销毁(
@PreDestroy或DisposableBean.destroy)。
6.2 常用作用域
| 作用域 | 生命周期 | 典型场景 |
|---|---|---|
| singleton | 容器级别,单例共享 | Service、DAO、无状态组件 |
| prototype | 每次获取新建 | 有状态的临时对象 |
| request | 一次 HTTP 请求 | 请求上下文数据 |
| session | 一次 HTTP 会话 | 登录用户信息 |
| application | ServletContext 级别 | 全局共享组件 |
高频坑:prototype Bean 如果被 singleton Bean 持有,它往往不会真的每次使用都变新对象,因为注入只发生一次。解决方案通常是使用方法注入、ObjectFactory或作用域代理。
七、AOP:面向切面编程解决横切逻辑散落问题
7.1 横切逻辑的痛
java
public void createOrder(Order order) { log.info("开始创建订单: {}", order.getId()); checkPermission(); try { orderDao.insert(order); log.info("创建订单成功"); } catch (Exception e) { log.error("创建订单失败", e); throw e; } }每个方法都写一遍,不仅重复,还污染了业务逻辑。
7.2 AOP 的思路:把横切逻辑集中到切面
核心概念:
连接点 JoinPoint:可以插入切面的程序执行点。
切入点 Pointcut:用于匹配连接点的表达式。
通知 Advice:在匹配到的连接点上执行的动作,包括前置、后置、环绕、返回后、抛异常后五种类型。
切面 Aspect:切入点加通知的组合。
织入 Weaving:把切面与目标对象结合生成代理对象的过程。
日志切面示例:
java
@Aspect @Component public class LogAspect { @Around("execution(* com.example.service.*.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { String method = joinPoint.getSignature().toShortString(); long start = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); System.out.println(method + " 执行成功,耗时 " + (System.currentTimeMillis() - start) + "ms"); return result; } catch (Throwable e) { System.out.println(method + " 执行异常,耗时 " + (System.currentTimeMillis() - start) + "ms"); throw e; } } }7.3 一段可运行的 AOP 调用链示例
7.4 AOP 到底是怎么实现的:JDK 动态代理和 CGLIB
JDK 动态代理:要求目标类必须实现接口。
CGLIB 代理:通过字节码技术为目标类生成一个子类,重写目标方法并在其中织入切面逻辑。
关键对比:
JDK 动态代理的性能在早期略弱于 CGLIB,但随着 JDK 版本迭代差距已经很小。
CGLIB 不能代理
final类和final方法,因为无法通过继承重写。Spring Boot 2.x 之后,如果目标对象没有实现接口,则使用 CGLIB。
同一个类内部方法自调用,通常不会触发 AOP 增强,因为此时不会经过代理对象。
java
@Service public class OrderCheckService { @LogExecution public void checkStock(Order order) { // 库存校验逻辑 } }八、Spring 如何解决事务管理的痛苦
8.1 原生 JDBC 事务有多容易写错
java
Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); orderDao.insert(order, conn); stockService.decreaseStock(order.getProductId(), conn); conn.commit(); } catch (Exception e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw e; } finally { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }8.2 声明式事务:一个注解解决大部分问题
java
@Service public class OrderServiceImpl implements OrderService { private final OrderDao orderDao; private final StockService stockService; public OrderServiceImpl(OrderDao orderDao, StockService stockService) { this.orderDao = orderDao; this.stockService = stockService; } @Transactional @Override public void createOrder(Order order) { orderDao.insert(order); stockService.decreaseStock(order.getProductId()); } }8.3 @Transactional 的关键属性和常见失效场景
传播行为 propagation:
REQUIRED、REQUIRES_NEW、NESTED、SUPPORTS等。隔离级别 isolation:
READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。回滚规则:默认只对
RuntimeException和Error回滚。只读事务 readOnly:用于只查询场景。
事务失效典型场景:
把
@Transactional加在private方法上。同一个类内部方法自调用。
异常被方法内部 catch 吃掉。
异常类型不是
RuntimeException且没有配置rollbackFor。Bean 没有被 Spring 管理。
九、Spring 在数据访问层的贡献:消灭样板代码
9.1 重复的 JDBC 代码是最大的生产力黑洞
java
String sql = "SELECT id, name FROM user WHERE id = ?"; try (Connection conn = dataSource.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setLong(1, userId); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getLong("id")); user.setName(rs.getString("name")); return user; } } } catch (SQLException e) { throw new RuntimeException(e); } return null;9.2 JdbcTemplate:把资源管理和异常处理交给模板
java
@Repository public class UserDao { private final JdbcTemplate jdbcTemplate; public UserDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public User findById(Long id) { String sql = "SELECT id, name FROM user WHERE id = ?"; return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { User user = new User(); user.setId(rs.getLong("id")); user.setName(rs.getString("name")); return user; }, id); } }9.3 与 MyBatis、JPA 等 ORM 框架的配合
Spring 的价值:
把 ORM 框架的
SessionFactory、EntityManager、SqlSessionTemplate等核心对象交给容器管理。统一事务管理,让
@Transactional可以和 MyBatis、JPA 搭配使用。屏蔽配置差异,通过 Spring Boot 的自动配置快速接入。
十、Spring MVC:让 Web 开发从繁琐走向结构化
10.1 原生 Servlet 的痛点
java
@WebServlet("/orders") public class OrderServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String productId = req.getParameter("productId"); String userId = req.getParameter("userId"); // 参数校验、类型转换、调用服务、返回 JSON 都要自己写 } }10.2 DispatcherServlet 是 Spring MVC 的核心
一次请求的典型流程:
请求到达
DispatcherServlet。DispatcherServlet通过HandlerMapping找到对应的 Controller 方法。调用
HandlerAdapter执行目标控制器方法。控制器方法完成业务处理,返回
ModelAndView或直接被@ResponseBody序列化。视图解析器解析视图,或因前后端分离直接返回 JSON。
DispatcherServlet把结果响应给客户端。
10.3 现代开发中的 Spring MVC 代码形态
java
@RestController @RequestMapping("/orders") public class OrderController { private final OrderService orderService; public OrderController(OrderService orderService) { this.orderService = orderService; } @PostMapping public Result create(@RequestBody @Valid CreateOrderRequest request) { Long orderId = orderService.create(request); return Result.success(orderId); } }十一、Spring Boot:为什么它让 Spring 再次爆发
11.1 约定大于配置
java
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }11.2 自动装配是什么原理
核心入口是@EnableAutoConfiguration,而@SpringBootApplication本身就包含它。自动配置类上通常带有条件注解:
@ConditionalOnClass@ConditionalOnMissingBean@ConditionalOnProperty@ConditionalOnWebApplication
11.3 starter:把依赖管理打包成模块
引入spring-boot-starter-web,就会自动带来 Spring MVC、内嵌 Tomcat、JSON 序列化等一组依赖。
11.4 内嵌容器和可执行 Jar
Spring Boot 默认内嵌 Tomcat、Jetty 或 Undertow,打包后的 Jar 可以直接通过java -jar运行。
十二、Spring 生态:从框架到企业级开发标准
12.1 Spring 已经不只是「一个框架」
Spring Framework:核心容器、IoC、AOP、事务、数据访问、Web。
Spring Boot:快速构建、自动装配、内嵌容器。
Spring Cloud:面向分布式系统和微服务的组件集合。
Spring Security:认证授权与安全防护。
Spring Data:统一数据访问模型。
Spring Batch:批处理。
12.2 Spring Boot 和 Spring Cloud 是什么关系
Spring Boot负责「让单个服务快速跑起来」。
Spring Cloud负责「让一堆服务协作起来」。
12.3 微服务时代的 Spring 角色
服务注册与发现:Spring Cloud Netflix 或 Kubernetes Service。
配置中心:Spring Cloud Config、Nacos、Consul。
远程调用:OpenFeign、RestTemplate、WebClient。
熔断与限流:Resilience4j、Sentinel。
网关:Spring Cloud Gateway。
链路追踪:Micrometer、Zipkin、SkyWalking。
十三、面试高频追问与回答模板
13.1 高频追问
为什么要用 Spring?它解决了什么问题?
IoC 和 DI 是什么关系?
Bean 的生命周期是怎样的?
AOP 的底层是怎么实现的?
JDK 动态代理和 CGLIB 有什么区别?
@Transactional为什么有时候不生效?Spring Boot 的自动配置是怎么实现的?
Spring Boot 和 Spring Cloud 是什么关系?
Spring 有哪些缺点?
如果没有 Spring,你会怎么设计一个类似的框架?
13.2 回答模板
回答「为什么要用 Spring」时,可以按下面的结构组织:
先讲原生痛点:没有 Spring 时,对象管理、事务、数据访问、Web 开发都有大量重复劳动。
再讲核心能力:Spring 用 IoC 解决对象管理,用 AOP 解决横切逻辑,用声明式事务解决事务管理,用模板化封装解决数据访问。
再讲生态价值:Spring Boot 让单个服务快速跑起来,Spring Cloud 让微服务协作起来。
最后讲客观评价:Spring 不是万能银弹,过度封装、隐式魔法、启动慢等问题同样存在。
13.3 一句话总结
Spring 的本质,是用容器和 AOP 这两大核心能力,把 Java 企业级开发中那些重复、易错、横切的关注点统一管理起来,让开发者能专注于业务逻辑本身。
十四、总结
Spring 的价值可以压缩成一条主线:
原生痛点 → 容器与 IoC → DI → AOP → 声明式事务 → 模板化数据访问 → Spring MVC → Spring Boot → 微服务生态。
把这套体系讲清楚,再结合具体代码示例和面试追问,就能在面试中从容应对「为什么要用 Spring」这道题。