Day5学习--SpringBoot详解
SpringBoot的核心作用
Spring Boot 是基于 Spring 框架的"快速开发脚手架",它的核心设计理念是**"约定优于配置"**。它并不是用来替代 Spring 的,而是为了解决传统 Spring 开发中存在的痛点:
- 告别繁琐配置:摒弃了传统 Spring 大量的 XML 配置,通过自动化配置和默认约定,实现零配置或极简配置即可启动项目。
- 开箱即用:内置了 Tomcat、Jetty 等 Web 服务器,项目可以直接打包成 Jar 包运行,无需再单独部署到外部服务器。
- 简化依赖管理:提供了一系列"起步依赖(Starter)",将常用技术栈的依赖整合在一起,自动解决版本冲突问题。
自动配置原理
Spring Boot 能够实现"开箱即用"的核心在于自动配置(Auto-Configuration) 。其核心入口是项目启动类上的 @SpringBootApplication 注解。
@SpringBootApplication 是一个组合注解,主要包含以下三个核心部分:
@SpringBootConfiguration:标识当前类是一个配置类。@ComponentScan:自动扫描并注册当前启动类所在包及其子包下的 Bean。@EnableAutoConfiguration:开启自动配置的灵魂。
底层加载逻辑:
@EnableAutoConfiguration 底层通过 @Import 导入了 AutoConfigurationImportSelector 类。
- 在 Spring Boot 2.x 中,它会读取
META-INF/spring.factories文件。 - 在 Spring Boot 3.x 中,改为读取
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件。
加载到全量自动配置类后,Spring Boot 会通过 @Conditional 系列条件注解(如 @ConditionalOnClass、@ConditionalOnMissingBean)进行过滤。只有当类路径下存在特定的类(例如引入了 Web 依赖)且容器中不存在该 Bean 时,对应的默认配置才会生效,最终将组件实例注册到 Spring 容器中。
快速创建项目与依赖引入
目前创建 Spring Boot 项目最主流的方式是使用 Spring Initializr (可以通过 IDEA 内置或官网 https://start.spring.io/ 访问)。它能自动生成符合规范的项目结构和 pom.xml 文件。
Spring Boot 通过 Starter(起步依赖) 把常用依赖封装为一个坐标,避免了手动管理版本冲突。例如,引入 spring-boot-starter-web 就能自动集成 Tomcat、Spring MVC 和 JSON 转换器。
pom.xml文件示例
xml
<!-- 继承 Spring Boot 父工程,利用其版本仲裁机制 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.5</version>
</parent>
<dependencies>
<!-- 引入 Web 起步依赖,开箱即用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
YML配置文件与端口配置
企业级开发中,更推荐使用 .yml 格式代替传统的 .properties 文件。YML 采用树形层级结构,可读性更强,且支持复杂的数据结构。
修改默认端口:
Spring Boot 内置的 Tomcat 默认端口是 8080。如果发生端口占用,可以直接在配置文件中修改。
application.yml示例
yml
server:
port: 8081 # 修改默认启动端口为 8081
servlet:
context-path: /api # 配置项目的访问路径前缀
日志配置(Logging)
Spring Boot 默认使用 SLF4J 作为日志门面,Logback 作为底层实现。默认的日志级别是 INFO,这意味着只会打印 INFO、WARN 和 ERROR 级别的日志。
日志级别从低到高依次为:
TRACE < DEBUG < INFO < WARN < ERROR
在开发阶段,为了排查问题,我们通常需要开启 DEBUG 级别的日志。
yml
logging:
level:
root: INFO # 全局日志级别
com.arguan: DEBUG # 将我们自己的项目包路径设置为 DEBUG 级别
自定义参数配置与读取
除了框架自带的配置,我们经常需要在配置文件中定义一些业务参数(如第三方接口的密钥、系统名称等)。
yml
# 自定义业务参数
my-app:
name: "SpringBoot-Testing"
version: "1.0.0"
max-retry: 3
在 Java 代码中读取自定义参数:
可以使用 @Value 注解将配置文件中的值注入到属性中。
java
@RestController
@RequestMapping("/test")
public class ConfigController {
// 读取自定义配置
@Value("${my-app.name}")
private String appName;
@Value("${my-app.max-retry:5}") // 如果配置文件中没有,默认值为 5
private Integer maxRetry;
@GetMapping("/info")
public String getConfig() {
return "应用名称:" + appName + ",最大重试次数:" + maxRetry;
}
}
项目分层架构(MVC思想)
在企业级开发中,为了保证代码的高内聚、低耦合以及良好的可维护性,通常采用经典的四层架构。每一层都有其明确的职责,严禁跨层调用。
- Entity(实体层) :
也称为 POJO 或 Domain 层。它的作用是映射数据库中的表结构,一个实体类对应一张数据表。通常只包含属性、Getter/Setter 方法,不包含任何业务逻辑。 - Mapper(持久层) :
也称为 DAO(Data Access Object)层。它的作用是直接与数据库交互,负责数据的增删改查(CRUD)。在 Spring Boot 中,通常使用 MyBatis 或 JPA 来实现。 - Service(业务层) :
系统的"大脑",负责处理核心业务逻辑(如参数校验、事务控制、复杂计算等)。Controller 层接收请求后,会调用 Service 层来处理具体业务,Service 层再调用 Mapper 层操作数据库。 - Controller(控制层) :
系统的"门面",负责接收前端发来的 HTTP 请求,解析参数,调用 Service 层获取结果,并最终将响应数据返回给前端。
请求接收与参数绑定
Spring Boot 提供了非常便捷的注解来接收前端传递的不同类型的参数。以下是开发中最常用的三种参数绑定方式:
-
@PathVariable(路径参数):
用于接收 URL 路径中的参数,常用于 RESTful 风格的接口,比如根据 ID 查询或删除某个资源。
- 示例 :
GET /users/101,接收 ID101。
- 示例 :
-
@RequestParam(查询参数) :
用于接收 URL 问号
?后面的键值对参数(如?name=张三&age=18)。常用于分页查询、条件搜索等简单参数的传递。 -
@RequestBody(JSON 对象参数) :
用于接收前端请求体(Request Body)中的 JSON 格式数据。常用于新增(POST)或修改(PUT)操作,可以将复杂的 JSON 对象直接映射为后端的 Java 实体对象。
本人在自学网课过程中,开发了一个智能学习辅助系统,所以我将使用系统中实现过的代码来演示三种请求接收参数
删除部门--使用@RequestParam注解
- 在系统中,比如要对一个部门进行部门管理,其中有一个功能是删除部门,那么访问数据库时的语句就需要根据部门的ID进行删除,这时候我们就需要接收ID这个参数。
java
/**
* 删除部门 使用@RequestParam注解
* 注意事项:一旦声明了@RequestParam注解,该参数在请求时必须传递,如果不传递将会报错
* @param id
* @return
*/
@DeleteMapping("/depts")
public Result delete(@RequestParam("id") Integer id) {
System.out.println("根据ID删除部门:" + id);
return Result.success();
}
获取部门信息--使用@PathVariable注解
- 如果想在一个部门列表中针对某一个部门去获取他的信息时,我们也需要去获取他的ID从而到数据库中进行查询返回所需的部门信息,我们可以使用路径参数来获取。
java
/**
* 获取部门信息
* @param id
* @return
*/
@GetMapping("/{id}")
public Result get(@PathVariable Integer id) {
System.out.println("查询部门ID:" + id);
Dept dept = deptService.findById(id);
return Result.success(dept);
}
添加部门--使用@RequestBody注解
- 需要添加部门时,系统需要接收一个部门的所有信息,这时候我们可以接受一个部门类的请求体,使用JSON格式封装数据,它会自动映射成后端在实体类中所定义的部门类的对象,实现参数接收。
java
/**
* 添加部门
* @param dept
* @return
*/
@PostMapping
public Result add(@RequestBody Dept dept) {
System.out.println("添加部门:" + dept.getName());
deptService.addDept(dept);
return Result.success();
}
响应返回与统一结果封装
- 在前后端分离的开发模式下,为了降低前端的解析成本,后端所有接口必须返回统一的数据格式。通常我们会封装一个泛型类
Result<T>,包含状态码(code)、提示信息(msg)和业务数据(data)。
java
public class Result<T> {
private Integer code; // 状态码(200表示成功,500表示失败)
private String msg; // 提示信息
private T data; // 业务数据(泛型,可适应任意类型)
// 省略构造方法、Getter 和 Setter
// 成功响应
public static <T> Result<T> success(T data) {
return new Result<>(200, "操作成功", data);
}
// 失败响应
public static <T> Result<T> error(String msg) {
return new Result<>(500, msg, null);
}
}
简单接口开发实战(新增与查询)
- 结合上述的分层架构,参数绑定和统一相应格式,我实现了一个简单的接口开发代码
Entity层(用户实体类)
java
public class User {
private Long id;
private String username;
private Integer age;
// 省略 Getter 和 Setter
}
Mapper 层(模拟数据库操作)
java
@Mapper
public interface UserMapper {
// 模拟插入用户
int insert(User user);
// 模拟根据ID查询用户
User selectById(Long id);
}
Service 层(处理业务逻辑)
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User saveUser(User user) {
// todo... 业务逻辑,比如校验用户名是否重复
userMapper.insert(user);
return user;
}
public User getUserById(Long id) {
return userMapper.selectById(id);
}
}
Controller 层(对外暴露接口)
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// 接口1:新增用户(使用 @RequestBody 接收 JSON 参数)
@PostMapping("/add")
public Result<User> addUser(@RequestBody User user) {
User savedUser = userService.saveUser(user);
return Result.success(savedUser);
}
// 接口2:查询用户(使用 @PathVariable 接收路径参数)
@GetMapping("/{id}")
public Result<User> getUser(@PathVariable Long id) {
User user = userService.getUserById(id);
if (user != null) {
return Result.success(user);
}
return Result.error("用户不存在");
}
// 接口3:返回统一结果测试(无参接口)
@GetMapping("/hello")
public Result<String> sayHello() {
return Result.success("你好,高冠林!");
}
}
学习中的问题
- 依赖冲突 :在引入第三方依赖时,如果遇到版本冲突,可以利用 Maven 的依赖树(
mvn dependency:tree)查看冲突情况,并使用<exclusion>标签排除不需要的依赖版本。Spring Boot 的父工程(Parent)已经帮我们管理了绝大多数常用依赖的版本,尽量优先使用 Starter。 - 端口占用 :启动项目时如果报错
Port 8080 was already in use,说明 8080 端口被其他程序(或上一次未正常关闭的项目)占用。最快的解决方法是在application.yml中修改server.port: 8081,或者在命令行中查出占用端口的进程 PID 并将其强制结束。