☰
面试官:为什么要用 Spring?它到底解决了什么问题?
2026/10/7 21:30:08 网站建设 项目流程

一、面试官到底在考什么

「为什么要用 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 两大核心能力,并围绕它们构建起一整套企业开发基础设施。

四个关键词:

  1. 容器:负责对象的创建、装配、生命周期管理。

  2. IoC:控制反转,解决「谁来创建对象、谁来决定依赖关系」。

  3. AOP:面向切面编程,解决「横切逻辑如何统一管理」。

  4. 基础设施:事务、数据访问、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 生命周期

  1. Bean 定义被读取。

  2. 实例化。

  3. 属性填充。

  4. 感知容器回调(BeanNameAware、BeanFactoryAware、ApplicationContextAware)。

  5. 前置处理(BeanPostProcessor.postProcessBeforeInitialization)。

  6. 初始化(@PostConstruct或InitializingBean.afterPropertiesSet)。

  7. 后置处理(BeanPostProcessor.postProcessAfterInitialization)。

  8. 投入使用。

  9. 容器关闭时执行销毁(@PreDestroy或DisposableBean.destroy)。

6.2 常用作用域

作用域生命周期典型场景
singleton容器级别,单例共享Service、DAO、无状态组件
prototype每次获取新建有状态的临时对象
request一次 HTTP 请求请求上下文数据
session一次 HTTP 会话登录用户信息
applicationServletContext 级别全局共享组件

高频坑: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 的核心

一次请求的典型流程:

  1. 请求到达DispatcherServlet。

  2. DispatcherServlet通过HandlerMapping找到对应的 Controller 方法。

  3. 调用HandlerAdapter执行目标控制器方法。

  4. 控制器方法完成业务处理,返回ModelAndView或直接被@ResponseBody序列化。

  5. 视图解析器解析视图,或因前后端分离直接返回 JSON。

  6. 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 高频追问

  1. 为什么要用 Spring?它解决了什么问题?

  2. IoC 和 DI 是什么关系?

  3. Bean 的生命周期是怎样的?

  4. AOP 的底层是怎么实现的?

  5. JDK 动态代理和 CGLIB 有什么区别?

  6. @Transactional为什么有时候不生效?

  7. Spring Boot 的自动配置是怎么实现的?

  8. Spring Boot 和 Spring Cloud 是什么关系?

  9. Spring 有哪些缺点?

  10. 如果没有 Spring,你会怎么设计一个类似的框架?

13.2 回答模板

回答「为什么要用 Spring」时,可以按下面的结构组织:

  1. 先讲原生痛点:没有 Spring 时,对象管理、事务、数据访问、Web 开发都有大量重复劳动。

  2. 再讲核心能力:Spring 用 IoC 解决对象管理,用 AOP 解决横切逻辑,用声明式事务解决事务管理,用模板化封装解决数据访问。

  3. 再讲生态价值:Spring Boot 让单个服务快速跑起来,Spring Cloud 让微服务协作起来。

  4. 最后讲客观评价:Spring 不是万能银弹,过度封装、隐式魔法、启动慢等问题同样存在。

13.3 一句话总结

Spring 的本质,是用容器和 AOP 这两大核心能力,把 Java 企业级开发中那些重复、易错、横切的关注点统一管理起来,让开发者能专注于业务逻辑本身。


十四、总结

Spring 的价值可以压缩成一条主线:

原生痛点 → 容器与 IoC → DI → AOP → 声明式事务 → 模板化数据访问 → Spring MVC → Spring Boot → 微服务生态。

把这套体系讲清楚,再结合具体代码示例和面试追问,就能在面试中从容应对「为什么要用 Spring」这道题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询