创建第一个spring-boot项目

spring-boot项目 核心构成

  1. 开启服务: tomcat + servlet
  2. 更方便的开发方式和指令:spring-boot 源码
  3. 构建和包管理:maven

使用vscode创建一个spring-boot项目

先去插件市场下载:Spring Boot Extension Pack 插件

  1. 打开命令面板 :按下快捷键 Ctrl + Shift + P(Windows/Linux)或 Cmd + Shift + P(macOS)。
  2. 搜索并运行命令 :在弹出的输入框中,输入 Spring Initializr
  3. 选择并创建 :你会看到 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;
  }

}

接收并处理前端传递过来的请求参数

  1. 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");
  }

}
  1. 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");
  }

}
  1. 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";
  }

}
  1. 参数附加在请求头上,比如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 管理。

  • 命名XxxRepositoryXxxMapper

  • 关键组件

    • JPA@Repository + extends JpaRepository
    • MyBatis-Plus@Mapper + extends BaseMapper
  • 规范:一个实体对应一个 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)。 数据访问对象(如XXXDaoXXXRepository 它是项目的"库房管理员",专门负责"存取货物"(对数据库进行增删改查)。
@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

  1. 说明好entity对应的表名 - @TableName
  2. 说明主键是谁 - @TableId(如果主键是自动生成)
  3. 如果表里有字段取默认值也要说明 - @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());
    }
}
相关推荐
像我这样帅的人丶你还1 小时前
Java 后端详解(三):全局异常处理与 JPA 数据库映射
java·后端
前端Hardy1 小时前
又一个 AI 神器火了!
前端·javascript·后端
神奇小汤圆2 小时前
面试被问烂的Java虚拟机调优,我用一个实战案例给你讲得明明白白
后端
明月_清风3 小时前
开发者网络概念全扫盲:一篇搞定
后端·网络协议
明月_清风3 小时前
零信任入门:从"城堡护城河"到"每次进门都要刷卡"
后端
站大爷IP4 小时前
Python循环中修改字典键导致遍历异常深度解析实战案例
后端
掘金者阿豪7 小时前
高可用读写分离实战(二):我把数据库主库停了,结果整个集群的反应和我想象的不一样
后端
掘金者阿豪7 小时前
《高可用读写分离集群实战》系列(一)
后端