本文纲要
一、SSM整合概述
二、项目结构与依赖管理
2.1 项目目录结构
2.2 Maven依赖清单
三、SSM核心配置类详解
3.1 Spring主配置类 SpringConfig
3.2 数据源与事务配置 JdbcConfig
3.3 MyBatis整合配置 MyBatisConfig
3.4 SpringMVC主配置 SpringMvcConfig
3.5 Web容器初始化 ServletConfig
3.6 父子容器关系图解
四、业务层开发
4.1 实体类 Book
4.2 数据访问层 BookDao
4.3 业务接口与实现
4.4 事务管理配置
五、控制器层开发(原始返回)
六、接口测试
6.1 Service层测试
6.2 Controller接口测试
七、表现层数据封装协议
7.1 前后端数据交互的痛点
7.2 协议演进过程
7.3 统一返回格式设计
八、统一返回格式落地实现
8.1 状态码类 Code
8.2 返回结果类 Result
8.3 改造后的 BookController
8.4 Postman测试验证
九、总结
一、SSM整合概述
SSM 是 Spring + SpringMVC + MyBatis 三个经典框架的组合,广泛应用于 Java Web 开发。本章我们将从零搭建一个 SSM 整合工程,完成配置、功能模块开发、接口测试,并引入表现层数据封装协议,使后端返回的数据格式统一、清晰,便于前后端协作。
整个整合过程分为以下几个阶段:
- 基础环境搭建:创建 Maven Web 项目,导入依赖。
- 配置类编写:用纯 Java 类替代 XML,完成 Spring、MyBatis、SpringMVC 的整合配置。
- 业务模块开发:从数据库表 → 实体类 → DAO → Service → Controller,实现 CRUD 功能。
- 事务管理:在 Spring 配置中开启注解事务,为 Service 层添加事务控制。
- 接口测试:使用 JUnit 测试 Service,使用 Postman 测试 Controller。
- 表现层数据封装:设计统一的返回格式 Result,定义状态码 Code,解决前端解析混乱的问题。
下面我们按照上述步骤,逐一展开。
二、项目结构与依赖管理
1 ) 项目目录结构
最终项目的完整目录结构如下(以 springmvc_09_result 为准):
springmvc_09_result
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── wb
│ │ ├── config
│ │ │ ├── JdbcConfig.java
│ │ │ ├── MyBatisConfig.java
│ │ │ ├── ServletConfig.java
│ │ │ ├── SpringConfig.java
│ │ │ └── SpringMvcConfig.java
│ │ ├── controller
│ │ │ ├── BookController.java
│ │ │ ├── Code.java
│ │ │ └── Result.java
│ │ ├── dao
│ │ │ └── BookDao.java
│ │ ├── domain
│ │ │ └── Book.java
│ │ └── service
│ │ ├── BookService.java
│ │ └── impl
│ │ └── BookServiceImpl.java
│ ├── resources
│ │ └── jdbc.properties
│ └── webapp
│ ├── index.jsp
│ └── WEB-INF
│ └── web.xml
└── test
└── java
└── com
└── wb
└── service
└── BookServiceTest.java
2 ) Maven依赖清单
pom.xml 中引入的依赖及作用如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wb</groupId>
<artifactId>springmvc09result</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<!-- Spring WebMVC (包含spring-context等核心模块) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- Spring JDBC (事务管理依赖) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- Spring Test (整合JUnit) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- MyBatis与Spring整合桥接包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!-- Druid连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<!-- JUnit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- Servlet API (provided,由容器提供) -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Jackson: Java对象与JSON互转 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Tomcat7 Maven插件,方便运行 -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<configuration>
<port>80</port>
<path>/</path>
</configuration>
</plugin>
</plugins>
</build>
</project>
要点:spring-webmvc 会自动传递 spring-context、spring-web 等,因此无需重复声明。
三、SSM核心配置类详解
SSM 整合的核心在于使用配置类替代传统的 XML 配置文件。下面逐一解析每个配置类的作用和编写方式。
1 ) Spring主配置类 SpringConfig
SpringConfig 是 Spring 的根配置类,负责管理 Service 层、事务、属性文件加载以及导入其他配置类。
package com.wb.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan({"com.wb.service"})
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
配置说明:
@Configuration:标识为配置类。@ComponentScan("com.wb.service"):只扫描 Service 包,避免与 SpringMVC 扫描冲突。@PropertySource("classpath:jdbc.properties"):加载数据库连接属性文件。@Import({JdbcConfig.class, MyBatisConfig.class}):导入数据源和 MyBatis 的配置类。@EnableTransactionManagement:开启注解式事务驱动(配合@Transactional使用)。
2 ) 数据源与事务配置 JdbcConfig
JdbcConfig 负责创建 Druid 数据源,并配置事务管理器。
package com.wb.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager ds = new DataSourceTransactionManager();
ds.setDataSource(dataSource);
return ds;
}
}
@Value("${...}")从 jdbc.properties 中注入属性值。dataSource()方法返回一个 Druid 数据源,并注册为 Bean。transactionManager方法创建事务管理器,注入数据源。Spring 在调用时会自动从容器中按类型注入DataSource。
jdbc.properties 内容:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=root
3 ) MyBatis整合配置 MyBatisConfig
MyBatisConfig 负责创建 SqlSessionFactory 和映射扫描器。
package com.wb.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setTypeAliasesPackage("com.wb.domain");
return factoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.wb.dao");
return msc;
}
}
SqlSessionFactoryBean:整合包提供的工厂 Bean,需要注入数据源,并设置实体类别名包(自动扫描com.wb.domain下的类,首字母小写作为别名)。MapperScannerConfigurer:自动扫描 com.wb.dao 包下的接口,为它们创建代理实现并注册为 Bean,无需再写@Mapper注解(当然也可以加)。
4 ) SpringMVC主配置 SpringMvcConfig
package com.wb.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
@Configuration
@ComponentScan("com.wb.controller")
@EnableWebMvc
public class SpringMvcConfig {
}
@ComponentScan("com.wb.controller"):只扫描 Controller 层。@EnableWebMvc:开启 SpringMVC 的默认配置,包括 JSON 转换、路径映射等。
5 ) Web容器初始化 ServletConfig
在 Servlet 3.0+ 环境中,可以通过实现 AbstractAnnotationConfigDispatcherServletInitializer 来替代 web.xml。
package com.wb.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getRootConfigClasses() {
return new Class[]{SpringConfig.class};
}
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringMvcConfig.class};
}
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
getRootConfigClasses():返回 Spring 根容器配置类(父容器)。getServletConfigClasses():返回 SpringMVC 子容器配置类。getServletMappings():指定 DispatcherServlet 的拦截路径,/ 表示拦截所有请求(不包含.jsp)。
6 ) 父子容器关系图解
- 父容器(Root ApplicationContext):由 SpringConfig 加载,管理 Service、DAO、事务等基础组件。
- 子容器(Servlet ApplicationContext):由 SpringMvcConfig 加载,管理 Controller、视图解析器等 Web 组件。
子容器可以访问父容器中的 Bean,反之则不行。为了避免混乱,通常将两个配置分离,扫描不同的包。
四、业务层开发
1 ) 实体类 Book
对应数据库表 tbl_book(字段:id, type, name, description)。
package com.wb.domain;
public class Book {
private Integer id;
private String type;
private String name;
private String description;
@Override
public String toString() {
return "Book{" +
"id=" + id +
", type='" + type + '\'' +
", name='" + name + '\'' +
", description='" + description + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
2 ) 数据访问层 BookDao
使用 MyBatis 注解直接绑定 SQL。注意注释掉的 @Insert 是一种省略字段列表的写法,两种均可。
package com.wb.dao;
import com.wb.domain.Book;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface BookDao {
// 省略字段列表的写法:insert into tbl_book values(null,#{type},#{name},#{description})
@Insert("insert into tbl_book (type,name,description) values(#{type},#{name},#{description})")
public void save(Book book);
@Update("update tbl_book set type = #{type}, name = #{name}, description = #{description} where id = #{id}")
public void update(Book book);
@Delete("delete from tbl_book where id = #{id}")
public void delete(Integer id);
@Select("select * from tbl_book where id = #{id}")
public Book getById(Integer id);
@Select("select * from tbl_book")
public List<Book> getAll();
}
3 ) 业务接口与实现
BookService 接口:定义业务方法,添加 @Transactional 实现声明式事务。
package com.wb.service;
import com.wb.domain.Book;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
public interface BookService {
/*
保存
@param book
@return
*/
public boolean save(Book book);
/*
修改
@param book
@return
*/
public boolean update(Book book);
/*
按id删除
@param id
@return
*/
public boolean delete(Integer id);
/*
按id查询
@param id
@return
*/
public Book getById(Integer id);
/*
查询全部
@return
*/
public List<Book> getAll();
}
BookServiceImpl:注入 BookDao,实现具体逻辑。
package com.wb.service.impl;
import com.wb.dao.BookDao;
import com.wb.domain.Book;
import com.wb.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;
public boolean save(Book book) {
bookDao.save(book);
return true;
}
public boolean update(Book book) {
bookDao.update(book);
return true;
}
public boolean delete(Integer id) {
bookDao.delete(id);
return true;
}
public Book getById(Integer id) {
return bookDao.getById(id);
}
public List<Book> getAll() {
return bookDao.getAll();
}
}
注:目前增删改操作直接返回 true,真实项目中可通过异常捕获返回 false,或在后续结合 AOP 统一处理。
4 ) 事务管理配置
事务管理已通过以下三步完成:
SpringConfig上添加@EnableTransactionManagement。JdbcConfig中配置PlatformTransactionManager Bean。BookService接口上添加@Transactional。
这样,Service 中的所有方法都将运行在事务中。当方法正常结束时事务提交,抛出 RuntimeException 时事务回滚。
五、控制器层开发(原始返回)
在没有统一返回格式之前,BookController 直接返回实体类、布尔值或 List。这是最初版本(对应 springmvc_08_ssm)。
package com.wb.controller;
import com.wb.domain.Book;
import com.wb.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public boolean save(@RequestBody Book book) {
return bookService.save(book);
}
@PutMapping
public boolean update(@RequestBody Book book) {
return bookService.update(book);
}
@DeleteMapping("/{id}")
public boolean delete(@PathVariable Integer id) {
return bookService.delete(id);
}
@GetMapping("/{id}")
public Book getById(@PathVariable Integer id) {
return bookService.getById(id);
}
@GetMapping
public List<Book> getAll() {
return bookService.getAll();
}
}
这种写法会导致前端收到格式不统一的数据:增删改返回 true/false,查单个返回 JSON 对象,查全部返回 JSON 数组。前端解析时需要判断类型,非常不便。于是我们需要引入表现层数据封装。
六、接口测试
1 ) Service层测试
使用 Spring 整合 JUnit,测试 Service 方法。
package com.wb.service;
import com.wb.config.SpringConfig;
import com.wb.domain.Book;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.List;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class BookServiceTest {
@Autowired
private BookService bookService;
@Test
public void testGetById() {
Book book = bookService.getById(1);
System.out.println(book);
}
@Test
public void testGetAll() {
List<Book> all = bookService.getAll();
System.out.println(all);
}
}
2 ) Controller接口测试
启动 Tomcat 后,使用 Postman 发送请求:
| 操作 | 方法 | URL | 请求体/参数 |
|---|---|---|---|
| 新增 | POST | /books | {"type":"...","name":"...","description":"..."} |
| 修改 | PUT | /books | {"id":1,"type":"...","name":"...","description":"..."} |
| 删除 | DELETE | /books/1 | 无 |
| 查单个 | GET | /books/1 | 无 |
| 查全部 | GET | /books | 无 |
此时返回的数据格式各异,为后续统一返回格式埋下伏笔。
七、表现层数据封装协议
1 ) 前后端数据交互的痛点
在没有统一格式的情况下,前端收到的响应五花八门:
- 增删改:
true或false(布尔值) - 查单个:
{"id":1,"type":"...","name":"...","description":"..."}(对象) - 查全部:
[{"id":1,...},{"id":2,...}](数组)
前端需要针对不同接口编写不同的解析逻辑,维护成本高。而且当操作失败时,前端无法仅通过数据本身判断操作是否成功,也不知道失败的原因。
2 ) 协议演进过程
我们通过逐步分析,设计出一个通用的返回模型:
| 演进阶段 | 问题 | 解决方案 |
|---|---|---|
| 1. 原始返回 | 格式不统一,解析困难 | 将所有数据放入一个统一模型 |
| 2. 增加数据字段 | 数据都在 data 中,但无法区分操作类型 | 增加状态码 code |
| 3. 增加状态码 | 能区分操作,但无法区分成功/失败 | 规定状态码末位:1 成功,0 失败 |
| 4. 增加消息字段 | 失败时需要给用户提示信息 | 增加 msg 字段 |
最终确定的数据模型包含三个字段:
data:实际返回的数据(可为任意类型)。code:状态码,用于标识操作类型和成败。msg:消息,主要用于失败时携带错误提示。
3 ) 统一返回格式设计
状态码设计规则:
- 前三位表示操作类型(200=成功类,也可自定义),第四位表示具体操作(1新增、2删除、3修改、4查询),末位 1 表示成功,0 表示失败。
示例:
- 新增成功:
{ "code": 20011, "data": true } - 查询失败:
{ "code": 20040, "data": null, "msg": "数据查询失败,请重试!" }
八、统一返回格式落地实现
1 ) 状态码类 Code
package com.wb.controller;
//状态码
public class Code {
public static final Integer SAVE_OK = 20011;
public static final Integer DELETE_OK = 20021;
public static final Integer UPDATE_OK = 20031;
public static final Integer GET_OK = 20041;
public static final Integer SAVE_ERR = 20010;
public static final Integer DELETE_ERR = 20020;
public static final Integer UPDATE_ERR = 20030;
public static final Integer GET_ERR = 20040;
}
2 ) 返回结果类 Result
package com.wb.controller;
public class Result {
private Object data;
private Integer code;
private String msg;
public Result() {
}
public Result(Integer code, Object data) {
this.data = data;
this.code = code;
}
public Result(Integer code, Object data, String msg) {
this.data = data;
this.code = code;
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
3 ) 改造后的 BookController
将所有方法返回值改为 Result,内部根据操作结果构造相应的返回对象。
package com.wb.controller;
import com.wb.domain.Book;
import com.wb.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private BookService bookService;
@PostMapping
public Result save(@RequestBody Book book) {
boolean flag = bookService.save(book);
return new Result(flag ? Code.SAVEOK : Code.SAVEERR, flag);
}
@PutMapping
public Result update(@RequestBody Book book) {
boolean flag = bookService.update(book);
return new Result(flag ? Code.UPDATEOK : Code.UPDATEERR, flag);
}
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) {
boolean flag = bookService.delete(id);
return new Result(flag ? Code.DELETEOK : Code.DELETEERR, flag);
}
@GetMapping("/{id}")
public Result getById(@PathVariable Integer id) {
Book book = bookService.getById(id);
Integer code = book != null ? Code.GETOK : Code.GETERR;
String msg = book != null ? "" : "数据查询失败,请重试!";
return new Result(code, book, msg);
}
@GetMapping
public Result getAll() {
List<Book> bookList = bookService.getAll();
Integer code = bookList != null ? Code.GETOK : Code.GETERR;
String msg = bookList != null ? "" : "数据查询失败,请重试!";
return new Result(code, bookList, msg);
}
}
改造过程详解:
| 原方法 | 原返回类型 | 新返回类型 | 核心变化 |
|---|---|---|---|
save | boolean | Result | 根据 flag 选择 SAVEOK 或 SAVEERR |
update | boolean | Result | 根据 flag 选择 UPDATEOK 或 UPDATEERR |
delete | boolean | Result | 根据 flag 选择 DELETEOK 或 DELETEERR |
getById | Book | Result | 判断 book 是否为 null,设置 code 和 msg |
getAll | List<Book> | Result | 判断 bookList 是否为 null,设置 code 和 msg |
注意:查询全部时,返回空集合([])仍视为成功,只有返回 null 才视为失败。
4 ) Postman测试验证
启动项目后,使用 Postman 测试各接口:
新增成功:
{
"code": 20011,
"data": true,
"msg": null
}
查询单个成功:
{
"code": 20041,
"data": {
"id": 1,
"type": "技术",
"name": "Spring实战",
"description": "一本好书"
},
"msg": ""
}
查询不存在的数据:
{
"code": 20040,
"data": null,
"msg": "数据查询失败,请重试!"
}
查询全部成功:
{
"code": 20041,
"data": [
{ "id": 1, "type": "...", "name": "...", "description": "..." },
{ "id": 2, "type": "...", "name": "...", "description": "..." }
],
"msg": ""
}
至此,所有接口返回格式完全统一,前端只需判断 code 即可知道操作结果,并从 data 中获取数据,必要时展示 msg 信息。
总结
本文从零开始,完整演示了 SSM 整合的全过程,并在此基础上引入了表现层数据封装,使后端接口返回的数据格式统一、语义清晰。核心要点回顾:
- 配置类整合:通过
SpringConfig、JdbcConfig、MyBatisConfig、SpringMvcConfig、ServletConfig五个 Java 配置类完成 Spring + SpringMVC + MyBatis 的整合,完全摒弃 XML。 - 分层开发:Domain → DAO(注解 SQL) → Service(事务) → Controller(RESTful API),结构清晰。
- 事务管理:通过
@EnableTransactionManagement、事务管理器 Bean 和@Transactional实现声明式事务。 - 统一返回格式:设计
Result和Code类,将增删改查的返回值统一封装,包含状态码、数据和消息,极大方便了前后端协作。 - 测试驱动:分别使用 JUnit 测试 Service 层,Postman 测试 Controller 层,确保功能正确。
掌握这些内容后,你将能够快速搭建一个标准化的 SSM 后端服务,并为后续的微服务或前后端分离项目打下坚实基础。
1253



被折叠的 条评论
为什么被折叠?



