引言
Spring Boot 自 2014 年发布以来,已成为 Java 后端开发中最主流的框架之一。一个 @SpringBootApplication 加一行 main 方法就能启动 Web 服务,这种开发体验让许多开发者将其视为理所当然。但 Spring Boot 并非凭空诞生的全新框架,它是在传统的 SSM 框架(Spring + SpringMVC + MyBatis)之上做了一层自动化的封装与抽象。理解这层封装究竟封装了什么、抽象掉的是哪些环节,才能回答一个更根本的问题------Spring Boot 到底做对了哪几件事。
下面四个部分按照一条请求在后端中的处理链路依次展开:请求经 Controller 控制层 进入系统,经 Service 服务层 处理业务逻辑,下沉到 Mapper 数据访问层 操作数据库,而贯穿这三层运转的底层骨架则是 配置体系。每一层都从如今 Spring Boot 的开发体验入手,回看 SSM 中对应的手动环节来揭示底层发生了什么,最后回到 Spring Boot 看它如何将这些环节自动化。
一、Controller 控制层------回看请求是怎么走到的
如今写一个 REST 接口,只需要 @RestController 加 @RequestMapping,返回一个对象,前端收到 JSON。这个过程顺畅到让人意识不到中间发生了什么。
回看 SSM 中的实现方式,同样一个流程需要串联起多个配置节点才能跑通:
Tomcat 收到请求 → web.xml 中声明 DispatcherServlet(url-pattern="/api/*")拦截 → 加载 spring-mvc.xml → spring-mvc.xml 配置 RequestMappingHandlerMapping → 匹配 @RequestMapping 找到 Controller 方法 → 方法返回后由 spring-mvc.xml 中配置的 Jackson 消息转换器序列化为 JSON → 写回响应
涉及的配置节点:
| 配置位置 | 配置内容 |
|---|---|
web.xml |
声明 DispatcherServlet,配置 url-pattern 和初始化参数 |
spring-mvc.xml |
处理器映射器、适配器、Jackson 消息转换器 |
web.xml(DelegatingFilterProxy) |
注册 CorsFilter 等过滤器 |
任何一个节点写错或少写,请求就走不到 Controller。
现在 Spring Boot 在 WebMvcAutoConfiguration 类中通过 @ConditionalOnClass 检测到 classpath 中有 Web 依赖,自动完成上述三个节点的默认装配------自动注册 DispatcherServlet、自动配置 Jackson 转换器、自动嵌入 Tomcat。你只需要写一个 @SpringBootApplication,这些默认行为就被触发了。如果需要替换默认行为,声明自己的 Bean 即可覆盖自动配置------本质上就是把声明 Bean 的过程对齐到了 SSM 中手动声明 Bean 的环节。
二、Service 服务层------回看那些重复的「注入→调用→返回」
如今继承 ServiceImpl<M, T> 就能直接调用 getById、list、page 等方法。
回看 SSM 的 Service 层,每一组业务功能需要完整走完这三步:
java
// 第一步:写接口
public interface UserService {
User getById(Long id);
void updateUserInfo(User user);
}
// 第二步:写实现类,手动注入 Mapper 并调用
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public User getById(Long id) {
return userMapper.selectById(id);
}
@Override
public void updateUserInfo(User user) {
userMapper.updateById(user);
}
}
// 第三步:在 applicationContext.xml 中配置组件扫描
<context:component-scan base-package="com.example.service"/>
10 个业务模块就要写 10 组这样的接口和实现类,且大部分方法的代码模式完全一致:注入 Mapper → 调用 Mapper 方法 → 返回。真正属于"业务逻辑"的代码往往只占一小部分。
MyBatis-Plus 用泛型和模板方法设计模式,把这段重复模式封装进了 ServiceImpl 基类中:
java
public interface UserService extends IService<User> {
User login(String username, String password); // 只写自定义方法
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public User login(String username, String password) {
return lambdaQuery().eq(User::getUsername, username).one();
}
}
ServiceImpl.getById(id) 内部已调用 baseMapper.selectById(id)。lambdaQuery().eq().one() 三行链式调用替代了 XML 中定义方法签名和手写 SELECT WHERE 语句的全过程。Spring Boot 在这里做对的事是:识别并封装了代码中高频重复的结构化模式,让开发者只关注"不同"的部分。
三、Mapper 数据访问层------回看那些手写的 SQL 和 XML
如今 Mapper 接口往往是一个空接口,继承 BaseMapper<T> 即可拥有完整的 CRUD 能力。
回看 SSM 中的 MyBatis,每个 Mapper 接口对应一个 XML 映射文件,每一条 SQL 都需要手动编写:
xml
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectByUsername" resultType="com.example.entity.User">
SELECT * FROM user WHERE username = #{username}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(username, password, nickname, email, phone)
VALUES (#{username}, #{password}, #{nickname}, #{email}, #{phone})
</insert>
<update id="updateById">
UPDATE user
<set>
<if test="nickname != null">nickname = #{nickname},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update>
</mapper>
同时需要在 spring-mybatis.xml 中手动声明 SqlSessionFactoryBean、指定 mapperLocations 路径、配置 MapperScannerConfigurer 的扫描包------任何一个路径写错,MyBatis 就找不到映射文件。
MyBatis-Plus 的处理方式完全不同:以 updateById 为例,调用 BaseMapper.updateById(user) 后,框架通过反射读取 User 实体上的 @TableName 注解获取表名,遍历非 null 字段并解析 @TableId、@TableField 注解,自动拼接出 UPDATE user SET nickname=?, email=? WHERE id=? 并执行。SQL 自动生成加 PaginationInnerInterceptor 分页拦截,两层增强让编写标准 CRUD 的成本趋近于零。
四、配置管理------回看那些散落的 XML 文件
这是最直观的一处对比。如今所有配置收归一个 application.yml:
yaml
server:
port: 8080
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://localhost:3306/mentalhealth?useUnicode=true&characterEncoding=utf8mb4
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
global-config:
db-config:
id-type: auto
logging:
level:
com.example.mapper: debug
回看 SSM,同样这些配置散落在项目各个位置:
bash
src/main/resources/
├── mybatis-config.xml # MyBatis 全局配置
├── spring-mybatis.xml # 数据源、SqlSessionFactory、事务、Mapper 扫描
├── spring-mvc.xml # 处理器映射、消息转换器、视图解析器
├── applicationContext.xml # 全局 Spring 配置,导入其他配置文件
└── db.properties # 数据库连接参数
src/main/webapp/WEB-INF/
└── web.xml # Servlet 容器部署描述符
6 个文件分散在三层目录中,且彼此之间存在引用关系。Spring Boot 在 spring-boot-autoconfigure 包中定义了上百个 *AutoConfiguration 类,通过 @ConditionalOnClass、@ConditionalOnMissingBean 等条件注解,按"如果 classpath 中有 X 依赖且没有用户自定义的 Bean,就注入默认的 X Bean"的规则自动装配。配置从"手动拼装"变成了"声明式依赖加运行时条件装配",你可以随时手动声明自己的 Bean 来覆盖自动配置,这与 SSM 中手动声明 Bean 在本质上是一致的。
总结
回看 SSM 中的每一层,Spring Boot 做对的核心三件事:
- 约定优于配置:对常见场景预设合理默认值,省掉大部分样板配置代码
- 条件自动装配:运行时根据 classpath 依赖按需注入组件
- 内嵌运行时:消除开发环境与生产环境的部署差异
理解 SSM 每一层做了什么,是正确使用 Spring Boot 的前提------当自动配置未按预期生效时,只有知道底层原本是如何手动拼装的,才能有方向地排查而非无从下手。而手动声明自己的 Bean 来覆盖 Spring Boot 的自动配置,这在本质上与 SSM 中手动声明 Bean 是同一回事。例如,SSM 中在 spring-mybatis.xml 手动声明 DataSource Bean,Spring Boot 中则通过 @Configuration 类中的 @Bean 方法声明自定义 DataSource------底层走的是同样的 Bean 注册流程,只是配置载体从 XML 换成了 Java 注解。
本文 B/S 架构分层及术语参考 GB/T 30882.1-2014《信息技术 应用软件系统技术要求 第1部分:基于B/S体系结构的应用软件系统基本要求》、GB/T 11457-2006《信息技术 软件工程术语》及阿里巴巴Java开发手册(黄山版)v1.7.1。
免责声明:本文内容仅供框架技术学习交流使用。文章中涉及框架技术细节等信息均来自公开资料,如有侵权行为,请联系作者删除相关内容。