spring-boot项目 核心构成
- 开启服务: tomcat + servlet
- 更方便的开发方式和指令:spring-boot 源码
- 构建和包管理:maven
使用vscode创建一个spring-boot项目
先去插件市场下载:Spring Boot Extension Pack 插件

- 打开命令面板 :按下快捷键
Ctrl + Shift + P(Windows/Linux)或Cmd + Shift + P(macOS)。 - 搜索并运行命令 :在弹出的输入框中,输入
Spring Initializr。 - 选择并创建 :你会看到
Spring Initializr: Create a Maven Project(或Generate a Maven Project)的选项,选中它,然后根据提示一步步选择项目语言、Spring Boot版本、依赖(比如Spring Web)即可创建。
使用IDEA创建一个spring-boot项目
使用IDEA创建java项目更简单,点击左上角的new project按照需要选择你想要的配置即可


js
src/
├── main/
│ ├── java/
│ │ └── com/yourcompany/project/
│ │ ├── ProjectApplication.java # 启动类
│ │ ├── controller/ # 控制器层(对外暴露API)
│ │ │ ├── UserController.java
│ │ │ ├── OrderController.java
│ │ │ └── AuthController.java
│ │ ├── service/ # 业务逻辑层
│ │ │ ├── UserService.java # 接口定义
│ │ │ ├── impl/ # 实现类
│ │ │ │ └── UserServiceImpl.java
│ │ │ └── OrderService.java
│ │ ├── mapper/ # 数据访问层(DAO层)
│ │ │ ├── UserMapper.java
│ │ │ └── OrderMapper.java
│ │ ├── entity/ # 实体类(对应数据库表)
│ │ │ ├── User.java
│ │ │ └── Order.java
│ │ ├── dto/ # 数据传输对象(接口入参/出参)
│ │ │ ├── request/ # 请求DTO
│ │ │ │ ├── LoginDTO.java
│ │ │ │ └── UserCreateDTO.java
│ │ │ └── response/ # 响应DTO
│ │ │ ├── UserInfoVO.java
│ │ │ └── Result.java # 统一响应封装
│ │ ├── config/ # 配置类
│ │ │ ├── WebConfig.java # 拦截器/跨域配置
│ │ │ ├── RedisConfig.java
│ │ │ └── SwaggerConfig.java
│ │ ├── interceptor/ # 拦截器
│ │ │ └── JwtInterceptor.java
│ │ ├── filter/ # 过滤器
│ │ │ └── LogFilter.java
│ │ ├── exception/ # 异常处理
│ │ │ ├── GlobalExceptionHandler.java # 全局异常处理器
│ │ │ └── BusinessException.java # 自定义业务异常
│ │ ├── util/ # 工具类
│ │ │ ├── JwtUtil.java
│ │ │ └── RedisUtil.java
│ │ ├── constant/ # 常量/枚举
│ │ │ ├── RedisKeyConstants.java
│ │ │ └── OrderStatusEnum.java
│ │ └── aspect/ # AOP切面(可选)
│ │ └── LogAspect.java
│ ├── resources/
│ │ ├── application.yml # 主配置文件
│ │ ├── application-dev.yml # 开发环境配置
│ │ ├── application-prod.yml # 生产环境配置
│ │ ├── mapper/ # MyBatis XML映射文件
│ │ │ ├── UserMapper.xml
│ │ │ └── OrderMapper.xml
│ │ ├── db/ # 数据库脚本(可选)
│ │ │ └── schema.sql
│ │ └── static/ # 静态资源(html/css/js)
│ └── webapp/ # 前端页面(传统项目)
└── test/ # 测试代码
└── java/
└── com/yourcompany/project/
├── controller/
│ └── UserControllerTest.java
└── service/
└── UserServiceTest.java
创建第一个spring-boot接口
java
package com.example.cmhdemo;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@RestController // 本质由 Controller + ResponseBody
public class HelloController {
@GetMapping("/getJson")
public Map<String, String> getJson() {
Map<String, String> myMap = new HashMap<>();
myMap.put("name", "cmh");
myMap.put("age", "18");
return myMap;
}
@PostMapping("/postJson")
public Object postJson(@RequestBody Object data) {
// 原样返回前端传递的数据
return data;
}
}
接收并处理前端传递过来的请求参数
get请求,前端请求/api?id=1&name=cmh,两个参数分别获取
java
@RestController
public class ParamsController {
// 这里的id不是完全意义上的形参,这个id和前端传的必须是同一个值,前端传id,这边叫mid,就会报错
@GetMapping("/getSingleParams")
public String getSingleParams(@RequestParam String id, @RequestParam String name) {
System.out.println("RequestParam id:" + id);
System.out.println("RequestParam name:" + name);
return new String("cmh");
}
}
java
@RestController
public class ParamsController {
// 简写形式,可以不写 RequestParam ,这样默认两个参数都是非必传
@GetMapping("/getSingleParams")
public String getSingleParams(String id, String name) {
System.out.println("RequestParam id:" + id);
System.out.println("RequestParam name:" + name);
return new String("cmh");
}
}
java
@RestController
public class ParamsController {
// RequestParam 配置 【 参数别名 】【 是否必传 】【 默认值 】
@GetMapping("/getSingleParams")
public String getSingleParams(
@RequestParam(name = "id", required = false, defaultValue = "999") String mid,
@RequestParam(name = "name", required = false, defaultValue = "cmh") String mName) {
System.out.println("RequestParam id:" + mid);
System.out.println("RequestParam name:" + mName);
return new String("cmh");
}
}
get请求,前端请求/api?id=1&name=cmh,两个参数当做一个map获取
java
public class ModelAttrMap {
private String id;
private String name;
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
java
@RestController
public class ParamsController {
@GetMapping("/getMapParams") // 使用map的形式,必须定义一个类来接收
public String getMapParams(@ModelAttribute ModelAttrMap mapParams) {
System.out.println("mapParams:" + mapParams.getId());
System.out.println("mapParams:" + mapParams.getName());
return new String("getMapParams");
}
}
post请求,前端传递账号密码
java
public class PostBody {
private String id;
private String name;
public String getId() {
return this.id;
}
public String getName() {
return this.name;
}
public void setId(String id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
}
java
@RestController
public class ParamsController {
@PostMapping("/postParams")
public String postParams(@RequestBody PostBody postParams) {
System.out.println("PostBody:" + postParams.getId() + postParams.getName());
return "postParams";
}
}
- 参数附加在请求头上,比如
token
java
@RestController
public class ParamsController {
@PostMapping("/postHeaderParams")
public String postHeaderParams(@RequestHeader("token") String token) {
System.out.println("token:" + token);
return token;
}
}
spring-boot项目分层规范
1. Controller 层(表现层 / Web 层)
- 作用:接收前端请求、参数校验、返回响应(JSON/View)。
- 命名 :
XxxController - 关键注解 :
@RestController、@RequestMapping、@GetMapping等。 - 规范:不应包含业务逻辑,直接调用 Service 层。
2. Service 层(业务层)
-
作用:核心业务逻辑处理、事务管理。
-
命名 :
XxxService(接口) +XxxServiceImpl(实现类) -
关键注解 :
@Service、@Transactional -
规范:
- 接口定义契约,实现类写具体逻辑。
- 组织多个 Repository 操作,保证数据一致性。
- 拒绝在 Controller 与 Repository 间直接传递数据。
3. Repository 层(数据访问层 / DAO 层)
-
作用:数据库操作、SQL 管理。
-
命名 :
XxxRepository或XxxMapper -
关键组件:
- JPA :
@Repository+extends JpaRepository - MyBatis-Plus :
@Mapper+extends BaseMapper
- JPA :
-
规范:一个实体对应一个 Repository,只做原子性数据访问。
4. Model 层(实体 / DTO 层)
- 逻辑上的"Model层" =
entity+dto+vo等多个包的总和。 - 没有叫
model的包,是为了避免混淆,让每个包的职责单一、清晰。
细分为不同对象,避免混用:
| 类型 | 位置 | 作用 |
|---|---|---|
| Entity | entity/domain 包 |
与数据库表一一映射,用 JPA 注解 |
| DTO | dto 包 |
接口数据传输(如 UserLoginDTO) |
| VO | vo 包 |
返回给前端的数据对象 |
| BO | bo 包 |
Service 层内部业务对象(较少用) |
关键原则:禁止 Entity 直接暴露给前端,必须通过 DTO/VO 转换。
标准包结构示例
text
com.example.demo
├── controller # Controller 层
│ └── UserController
├── service # Service 层
│ ├── UserService
│ └── impl
│ └── UserServiceImpl
├── repository # Repository 层
│ └── UserRepository
├── entity # 实体
│ └── User
├── dto # 数据传输对象
│ ├── request
│ │ └── UserLoginDTO
│ └── response
│ └── UserVO
├── common # 公共配置、常量、异常、工具类
└── config # 配置类(如 WebConfig、Swagger)
请求调用链路
text
[前端] → Controller (参数校验) → Service (业务) → Repository (DB)
↑ ↑
DTO/VO Entity

实现一个接口分层

UserController.java
java
package com.example.cmhdemo.controller;
import org.springframework.web.bind.annotation.RestController;
import com.example.cmhdemo.model.entity.User;
import com.example.cmhdemo.service.UserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@RestController
@RequestMapping("/user")
public class UserController {
UserService userService = new UserService();
@GetMapping("/getUserById")
public User getUserById(@RequestParam String id) {
User user = userService.getUserById(id);
return user;
}
}
UserService.java
java
package com.example.cmhdemo.service;
import com.example.cmhdemo.model.entity.User;
import com.example.cmhdemo.repository.UserRepository;
public class UserService {
UserRepository userRepository = new UserRepository();
public User getUserById(String id) {
User user = userRepository.getUserById(id);
return user;
}
}
UserRepository.java
java
package com.example.cmhdemo.repository;
import com.example.cmhdemo.model.entity.User;
public class UserRepository {
public User getUserById(String id) {
User user = new User(1, "cmh", "男", 30, "13918407897");
return user;
}
}
User.java
java
package com.example.cmhdemo.model.entity;
import lombok.Data;
@Data
public class User {
private int id;
private String username;
private String gender;
private int age;
private String phone;
public User(int id, String username, String gender, int age, String phone) {
this.id = id;
this.username = username;
this.gender = gender;
this.age = age;
this.phone = phone;
}
}
BaseVo.java
java
package com.example.cmhdemo.model.vo;
import lombok.Data;
@Data
public class BaseVo<T> {
private String code;
private T data;
private String message;
private BaseVo(String code, T data, String message) {
this.code = code;
this.data = data;
this.message = message;
}
public static <F> BaseVo<F> success(F data) {
BaseVo<F> baseVo = new BaseVo<>("200", data, "成功");
return baseVo;
}
public static <F> BaseVo<F> fail(F data, String message) {
BaseVo<F> baseVo = new BaseVo<>("500", data, message);
return baseVo;
}
}
spring依赖注入
什么是依赖注入
依赖注入解决了什么问题
IOC-控制反转思想
依赖的控制权从对象内部反转到了外部容器手中,这意味着你的代码会更干净、更灵活,也更容易进行单元测试。
说人话就是:我们自己调用类进行new实例化的过程,交给Spring框架来控制对象实例化。
1. 我们定义的控制器层,我们只写了一个类,然后告诉Spring这是一个控制器。并没有手动去实例化new他,他怎么就能用了呢?
答: 因为@RestController或者@Controller,把我们这个类作为控制器层加入到了Spring容器,Spring在启动的时候会自动的把Spring容器里的Controller实例化,并且挂载为接口。
2. 各个层之间调用,比如控制器层,调用service层的类。我们需要在控制层手动实例化service层。service调用repository层,我们也需要自己去new。可不可以简单一点呢?
答: 我们用一些注解,把service层的类加入容器,再把repository层的类加入容器,要实例化的时候通过注解自动实现实例化就好了呀。
加入容器的注解
| 注解 | 核心用途(意思) | 典型作用对象 | 通俗理解 |
|---|---|---|---|
@Controller |
Web层 :将类标记为控制器(Controller) ,用于接收和处理HTTP请求。 | 控制器类(如Spring MVC中的XXXController) |
它是项目的"前台接待",专门负责"接客"(处理用户请求)和"回话"(返回响应)。 |
@Service |
业务层 :将类标记为业务服务(Service) ,用于封装业务逻辑。 | 业务逻辑实现类(如XXXServiceImpl) |
它是项目的"业务专员",负责处理具体的"业务事务"(如计算、流程编排等)。 |
@Repository |
数据访问层 :将类标记为数据仓库(Repository) ,用于与数据库进行交互(CRUD)。 | 数据访问对象(如XXXDao或XXXRepository) |
它是项目的"库房管理员",专门负责"存取货物"(对数据库进行增删改查)。 |
@Component |
通用组件 :最通用的注解,用于标记一个普通的Spring组件。 | 不好归类的工具类、通用Bean等 | 它是一个"通用员工工牌",当你觉得上面的三种都不太贴切时,就用它来告诉Spring"这个类归你管"。 |
@Configuration |
配置类 :标记一个类为配置类,用来声明Spring的配置信息和定义Bean。 | 带有@Bean方法的配置类(如AppConfig) |
它是项目的"中央厨房/工作台 ",在这里定义创建一些复杂对象的"菜谱"。 |
@Bean |
方法级注解 :用在方法上,将方法的返回值(一个对象)注册为Spring容器的Bean。 | 配置类中返回特定对象的方法 | 它是"菜谱里的一道菜",告诉Spring:"请执行这个方法,把做好的菜(对象)也放到容器里吧。" |
从容器中取出注解
| 注解 | 核心职责 | 注入依据 | 典型场景 |
|---|---|---|---|
@Autowired |
自动装配依赖 | 按类型匹配(byType) | 大部分情况下的依赖注入,如Service注入到Controller |
@Qualifier |
精确指定Bean | 按名称匹配(byName) | 配合@Autowired使用,解决同类型多实例的歧义问题 |
@Value |
注入配置值 | 按配置键名或SpEL表达式 | 读取配置文件中的值,如端口、密钥、路径等 |
操作数据库
相关包:
xml
<!-- MyBatis-Plus(核心) -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.7</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
配置 application.yml
yml
spring:
application:
name: springDemo2
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/java_db
username: root
password: root123456
server:
port: 8888
使用 mybatis-plus 实现快捷查询
配置 entity
- 说明好
entity对应的表名 -@TableName - 说明主键是谁 -
@TableId(如果主键是自动生成) - 如果表里有字段取默认值也要说明 -
@TableField(比如:createTime)
实现一个用户注册功能
UserController.java
java
package org.example.springdemo2.controller;
import org.example.springdemo2.model.VO.BaseVo;
import org.example.springdemo2.model.entity.User;
import org.example.springdemo2.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@GetMapping("/getUserById")
public BaseVo<User> getUserById(int id) {
return BaseVo.success(userService.getUserById(id));
}
@PostMapping("/register")
public BaseVo<User> register(String username, String password) {
try {
User registerUser = userService.register(username, password);
return BaseVo.success(registerUser, "注册成功,这是用户信息!");
} catch (RuntimeException e) {
return BaseVo.fail(e.getMessage());
}
}
}
UserService.java
java
package org.example.springdemo2.service;
import org.example.springdemo2.model.entity.User;
import org.example.springdemo2.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
UserRepository userRepository;
public User getUserById(int id) {
return userRepository.getUserById(id);
}
public User register(String username, String password) {
int userCount = userRepository.countByUsername(username);
// 如果用户数量大于0,说明已经存在用户
if (userCount > 0) {
throw new RuntimeException("用户已存在!");
}
userRepository.insertUserInfo(username, password);
return userRepository.getUserByUserName(username);
}
}
UserRepository.java
java
package org.example.springdemo2.repository;
import org.apache.ibatis.annotations.*;
import org.example.springdemo2.model.entity.User;
@Mapper
public interface UserRepository {
@Select("SELECT * FROM java_user WHERE id = #{id}")
@Results({
@Result(property = "nickname", column = "nick_name")
})
User getUserById(int id);
@Select("SELECT * FROM java_user WHERE username = #{username}")
User getUserByUserName(@Param("username") String username);
@Insert("INSERT INTO java_user(username, password) VALUES(#{username}, #{password})")
int insertUserInfo(@Param("username") String username, @Param("password") String password);
// 判断当前用户名存在的数量,如果数量为 0 ,表示没有注册,否则表示已经存在用户
@Select("select count(*) from java_user where username = #{username}")
int countByUsername(String username);
}
拦截器,过滤器,全局MVC配置
类似vue中的路由导航守卫,请求拦截器和相应拦截器

Filter vs Interceptor 核心区别
| 对比 | Filter | Interceptor |
|---|---|---|
| 所属 | Servlet 规范 | Spring MVC |
| 层级 | 更底层 | 更上层 |
| 能否访问 Controller | ❌ 不能 | ✅ 可以 |
| 拦截范围 | 所有请求 | 仅 Controller |
| 使用场景 | 通用处理 | 业务逻辑处理 |
整体流程图(非常重要)
js
浏览器
↓
Filter(进入,chain.doFilter之前的代码)
↓
DispatcherServlet
↓
Interceptor preHandle
↓
Controller
↓
Interceptor postHandle
↓
响应返回
↓
Interceptor afterCompletion
↓
Filter(返回,chain.doFilter之后的代码)
↓
浏览器
Filter:
- CORS
- 字符编码
- 请求日志
Interceptor:
- JWT 登录校验
- 权限控制
- 接口限流
定义一个过滤器
Filter 生命周期
js
项目启动
↓
init() (只执行一次)
↓
doFilter() (每个请求都执行)
↓
destroy() (项目关闭才执行)
| 方法 | 触发时机 | 作用 |
|---|---|---|
| init | 项目启动 | 初始化资源 |
| doFilter | 每个请求 | 核心逻辑(拦截/放行) |
| destroy | 项目关闭 | 释放资源 |
在真实项目中:
init❌ 很少用(Spring 已经帮你管理大部分初始化)doFilter✅ 最常用(鉴权 / 日志 / CORS)destroy⚠️ 少用(除非你手动管理资源)
实现一个简单的filter
java
package org.example.springdemo2.config;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.io.IOException;
// 实现一个 Filter 接口
//@Component // Component通过注解,注册过滤器,告诉spring-boot,这是一个过滤器
//@Order(1) // 定义过滤器的执行顺序,数值越小优先级越高
@WebFilter(urlPatterns = "/*") // WebFilter通过注解,注册过滤器(且需要在Appliction中定义 @ServletComponentScan)
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器初始化了!");
}
// 一定要重写一个 doFilter 方法,否则会报错
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter -> 请求进入:" + request.getRemoteAddr());
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("过滤器销毁了!");
}
}
1. 新建文件
2. 实现Filter接口
3. 重写doFilter方法编写过滤逻辑
4. 通过则调用chain.doFilter
5. 加上@Component注解使代码生效
6. 可以加上@Order注解或者@WebFilter注解说明顺序和限制生效地址
定义一个拦截器 Interceptor(更常用)
MyInterceptor
java
package org.example.springdemo2.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
// 要使用拦截器,需要:定义spring mvc全局配置(项目配置是application.yml),添加拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("请求即将到达控制器!发生在Controller 执行前");
// true表示通过,false表示会被拦截
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("请求刚刚离开控制器!但响应还没有发送给浏览器,发生在Controller 执行后!");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
@Nullable Exception ex) throws Exception {
System.out.println("整个请求彻底结束,响应也发送浏览器完毕!");
}
}
1. 定义拦截器
2. 实现 HandlerInterceptor 类
3. 重写 preHandle(最常用) postHandle afterCompletion 类
4. 新建全局配置,实现 WebMvcConfigurer 类,重写 addInterceptors 方法,并使用 addInterceptor 添加拦截器
InterceptorConfig
java
package org.example.springdemo2.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
// 这里只是一个配置文件,要将这个配置文件加入到spring容器中
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor());
}
}