黑马苍穹外卖项目收获

1.理论收获

1.1 Day1 开发环境搭建

常见的名称说明,细化POJO说明


Git版本控制:创建本地仓库,gitee创建远地仓库,推送到远程仓库


前后端连通示意图


前端发送到后端的请求地址不同?

做了一个nginx反向代理,也就是将前端的动态请求由nginx转发到后端服务器

**nginx 作用:**负载均衡(基于反向代理实现),保证后端安全(不向外面暴露地址),反向代理(url映射),提高访问速度(缓存加速)


**Swagger作用:**生成接口文档,在线接口测试

Knife4j是为JavaMVC框架集成Swagger生成Api文档的增强解决方案。(简化Swagger)

图中是Swagger帮助生成接口文档的常见注解,生成后的文档添加注解

1.2 Day2 员工的相关管理操作(增删改查分页)

  1. 新增员工
  2. 员工分页查询
  3. 启用禁用员工账号
  4. 编辑员エ
  5. 导入分类模块功能代码

根据接口设计对应的DTO格式,通过DTO的拷贝对实体中通用字段进行赋值,然后再对实体类中独有的字段进行赋值


把时间进行转化为年月日格式

第一种方法:通过注解

第二种方法:扩展Spring MVC的消息转换器

通过自定义Jackson2ObjectMapperBuilderCustomizerMappingJackson2HttpMessageConverter,统一配置时间类型的序列化 / 反序列化规则,全局生效。适合「所有时间字段都要统一格式(如全为 yyyy-MM-dd)」的场景。


路径参数需要加@PathVariable注解

上传的是JSON格式的数据,需要加一个@RequestBody注解进行接收

可以使用DTO格式的数据直接对实体进行赋值,然后再对实体类里面独有的字段进行赋值。

这里的BaseContext是一次登录就是一个单独的线程,在登录后进行用户鉴权的时候就把ID进行了保存。


1.3 Day3 菜品管理

  1. 公共字段自动填充
  2. 新增菜品
  3. 菜品分页查询
  4. 删除菜品
  5. 修改菜品

对于一些公共字段进行自动填充,解决代码冗余,后续代码更改需要大范围修改代码的问题

如下表所示的公共字段在用户表和菜单表都存在

针对该问题的解决思路为:(涉及技术:枚举,注解,AOP,反射)

  1. 自定义注解AutoFill,用于标识需要进行公共字段自动填充的方法
  2. 自定义切面类AutoFillAspect,统一拦截加入了AutoFill注解的方法,通过反射为公共字段赋值
  3. 在Mapper 的方法上加入AutoFill注解

开发文件上传接口: 浏览器 → 后端服务 → 阿里云OSS

一些注解的解释

UUID 的全称是 Universally Unique Identifier(通用唯一识别码),简单说就是:

一种通过特定算法生成的、全球唯一的 32 位十六进制数字字符串(通常会用 - 分成 5 段,格式如 8-4-4-4-12),几乎不可能重复。


@Transactional 这个注解保证操作的原子性

@Autowired 让 Spring 自动帮你创建并注入需要的对象,不用自己手动 new 实例

@RequestParam 获取 HTTP 请求中的参数值,并将其绑定到控制器方法的参数上


SQL的动态语句拼接的写法

实际的拼接效果如下:

同理的SQL语句动态拼接,相较于单条语句删除可以优化性能


1.4 Day5 Redis相关

  1. Redis入门
  2. Redis数据类型
  3. Redis常用命令
  4. 在Java中操作Redis
  5. 店铺营业状态设置

Redis是一个基于内存的key-value结构数据库

  • 基于内存存储,读写性能高
  • 适合存储热点数据(热点商品、资讯、新闻)
  • 企业应用广泛

5种常用的数据类型

各种数据类型的特点

  • 字符串(string):普通字符串,Redis中最简单的数据类型
  • 哈希(hash):也叫散列,类似于Java中的HashMap结构
  • 列表(list):按照插入顺序排序,可以有重复元素,类似于Java中的LinkedList
  • 集合(set):无序集合,没有重复元素,类似于Java中的HashSet
  • 有序集合(sorted set/zset):集合中每个元素关联一个分数(score),根据分数升序排序,没有重复元素

字符串的命令

哈希操作的命令

列表的操作命令

集合操作命令

有序集合操作命令

通用命令符


@Configuration 注解的类可以看作是 Spring 容器的「配置说明书」

  • 它告诉 Spring:这个类里定义了创建 Bean(Spring 管理的对象)的规则和方法。
  • 替代了传统 Spring 配置中的 XML 文件(比如 applicationContext.xml),实现「注解驱动的配置」。
  • Spring 启动时会扫描并解析这个类,执行里面的方法创建 Bean,并把这些 Bean 放入 IoC 容器中供整个应用使用。

IOC(Inversion of Control,控制反转)容器是 Spring 框架的核心

  • 它是一个内存中的对象池 / 仓库,负责管理所有被 Spring 接管的对象(这些对象称为 Bean);
  • 核心职责:
    1. 创建 Bean :根据你写的配置(比如 @Configuration + @Bean@Component 等)创建对象;
    2. 管理依赖:自动把 Bean 之间的依赖关系注入(比如把 UserDao 注入到 UserService 中);
    3. 管理生命周期:控制 Bean 的创建、初始化、使用、销毁全过程;
    4. 提供获取 Bean 的方式:你可以通过名称、类型从容器中拿到需要的对象。

序列化器的作用

  • 数据格式转换:实现 Java 数据类型 ↔ Redis 字节数组的双向转换,是 Java 程序与 Redis 交互的「桥梁」;
  • 保证数据完整性:确保对象存储到 Redis 后,取出来时能完整还原(比如字段不丢失、类型不错乱);
  • 统一编码规则:避免因编码不一致导致的乱码(比如字符串用 UTF-8 编码,而不是 GBK);
  • 适配不同场景:不同序列化器有不同的特性(比如速度、兼容性、可读性),可适配缓存、分布式锁、消息队列等不同场景。

1.5 Day6 微信小程序开发

  1. HttpClient
  2. 微信小程序开发
  3. 微信登录
  4. 导入商品浏览功能代码

实现跟前端差不多,结构差不多


1.6 Day7 菜品缓存

  1. 缓存菜品
  2. 缓存套餐
  3. 添加购物车
  4. 查看购物车
  5. 清空购物车

缓存和数据库内容的一致性:修改数据和删除数据的时候要注意清理缓存数据。

Spring Cache是一个框架,实现了基于注解 的缓存功能,只需要简单地加一个注解,就能实现缓存功能。

Spring Cache提供了一层抽象,底层可以切换不同的缓存实现,例如:EHCache、Caffeine、Redis

Spring Cache的常见注解


1.7 Day8 用户下单

  1. 导入地址簿功能代码
  2. 用户下单
  3. 订单支付

@Transactional 加一个事务注解,确保一致性。

1.8 Day10 Spring Task

  1. Spring Task
  2. 订单状态定时处理
  3. WebSocket
  4. 来单提醒
  5. 客户催单

SpringTask是spring框架提供的任务调度工具,可以按照约定的时间自动执行某个代码逻辑。

应用场景:

  • 信用卡每月还款提醒
  • 银行贷款每月还款提醒
  • 火车票售票系统处理未支付订单
  • 入职纪念日为用户发送通知

cron表达式 其实就是一个字符串,通过cron表达式可以定义任务触发的时间

构成规则:分为6或7个域,由空格分隔开,每个域代表一个含义

每个域的含义分别为:秒、分钟、小时、日、月、周、年(可选)

不需要记忆,可以通过在线生成器进行生成表达式

cron.qqe2.com


Spring Task使用步骤:

  1. 导入maven坐标spring-context(已存在)
  2. 启动类添加注解@EnableScheduling开启任务调度
  3. 自定义定时任务类

简单实例:


关于代码开发过程中对于组件,注解,注入,容器等的认识和疑惑

技术概念 餐馆场景比喻
Spring 容器 餐馆的「后厨」:统一管理所有食材、厨具、厨师(对应程序里的对象)
组件(Component) 后厨里的「标准化工具 / 人员」:比如炒勺、厨师、配菜员(被 Spring 管理的对象)
注解(Annotation) 贴在工具 / 人员身上的「标签」:比如「炒勺」「川菜厨师」「配菜员」标签
注入(Autowired) 「按需分配」:比如给炒菜的厨师自动递上炒勺(给对象自动赋值它依赖的其他对象)

核心概念:

1. 注解(@XXX):给程序贴「标签」

注解是 Java 的一种特殊标记,本质是「元数据」(描述数据的数据),核心作用:

  • 告诉 Spring:这个类 / 方法 / 变量是什么角色、该怎么处理;
  • 替代传统 XML 配置,让代码更简洁。

通俗理解 :就像快递上的「易碎」「生鲜」标签,快递员看到就知道怎么处理;Spring 看到 @Component 就知道「这个类要被我管理」。

2. 组件(@Component 及衍生注解):标记「要被 Spring 管理的对象」

@Component 是最基础的「组件注解」,作用是:告诉 Spring「这个类的对象我要管,你帮我创建并放到容器里」

注解 适用场景 餐馆比喻
@Component 通用组件(无明确业务层) 后厨通用工具(抹布)
@Service 业务逻辑层(Service) 厨师(核心业务处理)
@Controller 控制层(Controller) 前厅服务员(接收请求)
@Repository 数据访问层(Dao/Mapper) 采购员(和数据库交互)

3. 注入(@Autowired):给组件「自动分配依赖」

@Autowired 是「自动注入注解」,核心作用:告诉 Spring「这个变量需要你从容器里找一个对应的对象赋值」。可以不用 手动 new 一个对象。

注入的核心规则(新手必知)

  1. 按类型注入 :Spring 默认根据变量的「类型」(比如 UserDao)去容器里找对应的 Bean;
  2. 必须有对应的组件 :要注入的 UserDao 必须加了 @Repository/@Component 注解(否则容器里没有这个对象,会报错);
  3. 单例特性:容器里的 Bean 默认是单例,所有注入的都是同一个对象(节省内存)。

Spring启动后的执行逻辑


WebSockt

应用场景:

  • 视频弹幕
  • 网页聊天
  • 体育实况更新
  • 股票基金报价实时更新

1.8 Day11 营业额统计

  • Apache ECharts
  • 营业额统计
  • 用户统计
  • 订单统计
  • 销量排名Top10

关于前端传参方式 + 后端接收方式

传参位置 适用请求类型 前端传参示例 后端接收注解 适用场景
URL 路径 GET/PUT/DELETE /user/1(1 是用户 ID) @PathVariable 传递资源 ID、状态等简单值
URL 参数(Query) GET /user?name=张三&age=20 @RequestParam 列表查询、筛选条件
请求体(Body) POST/PUT/PATCH JSON 格式的复杂数据 @RequestBody 新增 / 更新对象、提交表单
请求头(Header) 任意 Token、Content-Type 等 @RequestHeader 传递令牌、编码格式等元数据
  1. 场景 1:URL 路径传参(@PathVariable)

核心逻辑 :把参数嵌在 URL 路径里(比如 /user/1),后端通过 @PathVariable 提取。

java 复制代码
// 前端(Vue/React 等)
axios.get('/user/1') // 1 是要传递的用户ID
  .then(res => console.log(res))



// 后端 Spring Boot 接口
@RestController
@RequestMapping("/user")
public class UserController {
    // 路径中的 {id} 对应前端传的 1
    @GetMapping("/{id}")
    public Result getUserById(@PathVariable Long id) {
        System.out.println("接收的用户ID:" + id); // 输出:1
        return Result.success();
    }
}
  1. 场景 2:URL 参数(Query)传参(@RequestParam)

核心逻辑 :参数跟在 URL 后,以 ? 分隔,& 连接多个参数(比如 /user?name=张三&age=20),后端用 @RequestParam 接收。

java 复制代码
// 方式1:直接拼在 URL 里
axios.get('/user?name=张三&age=20')

// 方式2:用 params 配置(推荐,自动编码)
axios.get('/user', {
  params: {
    name: '张三',
    age: 20
  }
})



@GetMapping("/user")
public Result getUserByCondition(
    // 接收 name 参数,required = false 表示非必传
    @RequestParam(required = false) String name,
    // 接收 age 参数,指定默认值(不传时用 0)
    @RequestParam(defaultValue = "0") Integer age
) {
    System.out.println("姓名:" + name + ",年龄:" + age); // 输出:张三,20
    return Result.success();
}
  1. 场景 3:请求体(Body)传参(@RequestBody)

核心逻辑 :把复杂数据(对象、数组)放在请求体中,以 JSON 格式传递,后端用 @RequestBody 接收(最常用的传参方式)。

java 复制代码
// 前端传递用户对象(新增用户)
axios.post('/user', {
  name: '李四',
  age: 25,
  address: '北京市'
})

// PUT 请求(更新用户)
axios.put('/user/2', {
  name: '李四',
  age: 26
})


// 实体类(字段名要和前端 JSON 的 key 一致)
public class User {
    private String name;
    private Integer age;
    private String address;

    // 必须有无参构造方法(Spring 反射需要)
    public User() {}

    // getter/setter 省略
}


// 新增用户(POST + @RequestBody)
@PostMapping("/user")
public Result addUser(@RequestBody User user) {
    System.out.println("用户名:" + user.getName() + ",年龄:" + user.getAge());
    return Result.success();
}

// 更新用户(PUT + @RequestBody)
@PutMapping("/user/{id}")
public Result updateUser(
    @PathVariable Long id,       // 路径传 ID
    @RequestBody User user       // 请求体传更新的用户信息
) {
    System.out.println("更新用户ID:" + id + ",新姓名:" + user.getName());
    return Result.success();
}
  1. 场景 4:请求头(Header)传参(@RequestHeader)

核心逻辑 :把元数据(比如 Token、语言类型)放在请求头里,后端用 @RequestHeader 接收。

java 复制代码
// 前端传递 Token 到请求头
axios.get('/user/info', {
  headers: {
    'Authorization': 'Bearer 123456789', // Token
    'Language': 'zh-CN' // 语言类型
  }
})

@GetMapping("/user/info")
public Result getUserInfo(
    // 接收 Authorization 头
    @RequestHeader("Authorization") String token,
    // 接收 Language 头,非必传
    @RequestHeader(value = "Language", required = false) String language
) {
    System.out.println("Token:" + token); // 输出:Bearer 123456789
    System.out.println("语言:" + language); // 输出:zh-CN
    return Result.success();
}

注意事项

  • @RequestBody 只能用一次 :一个请求只能有一个请求体,所以一个接口只能有一个 @RequestBody 注解;
  • 参数名要一致 :前端传的 name 要和后端实体类 / 参数名一致(大小写敏感),不一致可加 @JsonProperty 映射
  • GET 请求不能传 Body:HTTP 规范中 GET 请求没有请求体,前端用 GET 传 Body 后端收不到,需改用 POST/PUT;
  • 非必传参数要配置 :用 required = falsedefaultValue,否则前端不传会报错。

销量排名Top10设计:使用名称查询,借助 group by 语句以及 sum 求和语句

1.9 Day 12 运营数据报表

  • 工作台
  • Apache POI
  • 导出运营数据Excel报表

2.通过观看课件和文档的进一步收获

2.1 扫盲类

2.1.1 常见的项目网站中提供的各种资料分别是什么,如脚手架,E-R图等

  1. 项目脚手架 (框架).zip:这是项目的基础框架模板,包含了已经配置好的工程结构、依赖、通用工具类(比如统一响应封装、异常处理、日志配置、Redis / 数据库连接等)。

  2. 项目文档.zip:这是项目的整体说明文档。

  3. ER 图.zip:这是数据库实体关系图(Entity Relationship Diagram),用可视化方式展示数据库表结构和表之间的关联关系(比如用户表和订单表的一对多关系)。

  4. 数据库设计文档.zip:这是详细的数据库设计说明,比 ER 图更具体

  5. ⭐⭐⭐项目源码.zip(核心资料):这是完整的项目源代码,包含所有业务代码、配置文件、静态资源等。

2.1.2 网上常讲的中间件,技术栈这些都是些什么啊

**技术栈:**做项目从到到尾涉及到的所有技术,合在一起就叫做技术栈

例如:后端技术栈

  • 语言:Java
  • 框架:SpringBoot、SpringMVC、MyBatis/MyBatis-Plus
  • 数据库:MySQL
  • 缓存:Redis
  • 工具:Maven、Git、Swagger
  • 部署:Linux、Docker

常见的JAVA后端技术栈

  1. 核心框架(必学)
  • Java 8 / 11开发语言
  • Spring Boot项目开发骨架,一切的基础
  • Spring MVC写接口、接收前端请求
  • Spring Cloud / Alibaba微服务一套(大项目用)
  • MyBatis / MyBatis-Plus操作数据库
  1. 工具类
  • Maven / Gradle管理依赖、打包项目
  • Git代码版本管理
  • Swagger / Knife4j自动生成接口文档
  • Lombok简化代码(@Data、@Slf4j)
  1. 数据库
  • MySQL最主流数据库
  • PostgreSQL国企、金融常用
  1. 缓存
  • Redis缓存、分布式锁、限流

中间件: 项目里,不是你写的业务代码,但又必须用的工具软件,统称中间件。作用是帮助解决通用问题,不用你重复造轮子。

例如:

  • Redis ------ 缓存中间件:加速查询、减轻数据库压力
  • MySQL ------ 数据库(也算广义中间件):存数据
  • MQ(消息队列)RabbitMQ / RocketMQ / Kafka:异步、削峰、解耦
  • Nginx ------ 反向代理、负载均衡:分发请求、扛高并发
  • Elasticsearch ------ 搜索中间件:快速搜索大量数据( like 百度搜索)
  • MinIO / OSS ------ 文件存储:存图片、视频、文件
  • Gateway / Spring Cloud Gateway ------ 网关:统一入口、鉴权、限流

企业中最常见的中间件

  1. 缓存中间件
  • Redis(99% 项目都用)作用:加速、存会话、分布式锁
  1. 消息队列 MQ(异步、削峰)
  • RabbitMQ中小型公司最常用
  • RocketMQ阿里、电商、高并发常用
  • Kafka大数据、日志、高吞吐
  1. 搜索引擎
  • **Elasticsearch(ES)**做搜索、日志分析
  1. 网关 / 负载均衡
  • Nginx部署、反向代理、负载均衡
  • Spring Cloud Gateway微服务网关
  1. 服务治理(微服务)
  • Nacos服务注册、配置中心
  • Sentinel限流、降级、熔断
  1. 分布式任务调度
  • XXL-Job定时任务、分布式调度
  1. 分布式事务
  • Seata保证多服务数据一致
  1. 文件存储
  • MinIO自己搭建文件服务器
  • 阿里云 OSS / 腾讯云 COS云存储
  1. 监控
  • Prometheus + Grafana监控服务器、接口、性能

总结:

  • 技术栈 = 做项目用到的所有技术
  • 中间件 = 项目里的工具软件(Redis、MySQL、MQ、Nginx 等)
  • 你写的代码 = 业务逻辑
  • 中间件 = 帮你支撑业务、提高性能、解决难题
  • 技术栈是大集合 ,中间件是里面的一小类

2.1.3 持久层的定义

持久层(Persistence Layer)是后端项目中专门负责「把数据存到数据库 / 从数据库取数据」的一层代码,核心作用是隔离业务逻辑和数据存储细节

  • 持久:指数据「永久保存」(比如存到 MySQL、Redis,而不是内存里 ------ 内存里的数据重启就没了);
  • :指项目代码的「分工模块」(就像公司的部门:销售部管卖货,技术部管开发,持久层只管数据存取)。

核心作用:

  • 降低耦合
  • 统一管理数据存取逻辑,方便维护
  • 封装通用操作,避免重复代码

持久层和其他层的关系

2.1.4 常见注解

@Data 注解:@DataLombok 框架 提供的注解,自动为 Java 类生成 getter/settertoStringequalshashCode无参构造方法 等通用方法,不用你手动写这些重复的模板代码。

@PostMapping 是 Spring MVC 提供的注解,专门标记这个接口只接收 HTTP POST 类型的请求,同时可以指定接口的访问路径。

@RequestBody 是 Spring MVC 提供的参数注解,作用是:把前端 POST 请求体中 JSON 格式的数据,自动转换成 Java 对象(实体类),绑定到方法的参数上。

@RestController 是 Spring MVC 提供的组合注解,本质 = @Controller + @ResponseBody。标记这个类是 Spring MVC 的控制器 (Controller);让类中所有方法的返回值自动序列化为 JSON 格式,直接作为 HTTP 响应体返回给前端。

@RequestMapping 是 Spring MVC 提供的注解,作用是为整个控制器类下的所有接口 设置统一的路径前缀

@AllArgsConstructor :生成「全参构造方法」(PageResult(long total, List records));

@NoArgsConstructor :生成「无参构造方法」(PageResult());

@PutMapping 是 Spring MVC 提供的注解,核心作用是:标记接口只接收 HTTP PUT 类型的请求,且语义上对应「全量更新 / 替换资源」(比如修改用户的所有信息、更新订单的全部状态)。

@ConfigurationProperties,绑定配置文件中指定前缀的配置项到当前类的字段

@Configuration:告诉 Spring 这是一个「配置类」,类中的 @Bean 方法会被 Spring 扫描,方法返回的对象会被注册到 Spring 容器中

@ConditionalOnMissingBean,「条件注解」:只有 Spring 容器中没有 AliOssUtil 类型的 Bean 时,才执行这个方法;如果已有,则跳过,避免重复创建

@Transactional // 声明事务:方法内的数据库操作要么全成,要么全败

@TestJUnit 框架(Java 单元测试标准框架)提供的注解 ,核心作用是:标记一个普通方法为「测试方法」,无需 main 方法,直接运行这个方法,验证代码逻辑是否符合预期(比如测试 Service 层的 saveWithFlavor 方法是否能正确新增菜品)。

注解 HTTP 方法 核心语义 典型场景
@GetMapping GET 查询资源 根据 ID 查用户、分页查订单
@PostMapping POST 创建资源 新增用户、提交订单
@PutMapping PUT 全量更新 / 替换资源 修改用户所有信息、更新订单
@DeleteMapping DELETE 删除资源 删除用户、删除订单

service层的接口不加注解吗?只有service层的实现类加@Service注解?

Service 层的接口通常不加任何注解,只有实现类需要加 @Service 注解

Service 接口的作用是「定义规范」(声明要实现的方法),它本身不包含任何业务逻辑,也不需要被 Spring 容器管理 ------Spring 管理的是「能干活的对象」(即接口的实现类),而不是「规范」。

给接口加注解导致两个问题:

  • Spring 无法实例化接口(接口不能 new),启动时会报 NoSuchBeanDefinitionException
  • 违背「接口只定义规范」的设计原则,接口应该和容器解耦,专注于方法声明。

2.1.5 Tomcat

Tomcat 是一款开源的、轻量级的 Java Web 服务器 / 容器,核心作用是:运行基于 Java 开发的 Web 应用(比如你写的 Spring Boot 项目),接收前端浏览器 / 客户端的 HTTP 请求,处理后返回响应结果。

简单说:Tomcat 是 Java Web 应用的「运行容器」------ 你的 Spring Boot 代码(Controller/Service/Mapper)本身不能直接接收 HTTP 请求,必须把代码 "放进" Tomcat 里运行,Tomcat 负责和前端交互,调用你的代码逻辑。

Tomcat 的核心特征

  1. 核心定位:Servlet 容器(Java EE 规范实现,处理 HTTP 请求的 Java 类)
  • 你的 Controller 层(比如 @RestController 注解的类),底层本质是 Servlet;
  • Tomcat 负责管理 Servlet 的生命周期(创建、初始化、调用、销毁),处理请求的线程池、连接数等。
  1. 关键特性
特性 说明
轻量级 体积小、启动快,适合开发 / 测试 / 中小型项目(对比 WebLogic、JBoss 更轻便)
开源免费 Apache 基金会维护,无商业授权费用,是 Java Web 开发的标配
支持 HTTP/HTTPS 直接处理 HTTP 请求(默认端口 8080),也可配置 HTTPS(443 端口)
支持热部署 开发时修改代码后,无需重启 Tomcat 即可生效(IDEA 中常用)

3.总结

  • Tomcat 是「Java Web 服务器 / 容器」,核心作用是接收 HTTP 请求、运行 Java Web 应用、返回响应;
  • Spring Boot 默认内置 Tomcat,启动项目时自动启动,无需手动配置;
  • Tomcat 是 Java Web 开发的标配,相当于前端和后端代码之间的「中转站」。

2.1.6 反射机制

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

步骤 作用 关键 API
获取 Class 对象 反射入口,代表类的元数据 obj.getClass() / Class.forName()
获取字段 拿到字段的反射对象 getDeclaredField() / getField()
开启可访问 突破访问权限 setAccessible(true)
读写字段 操作字段值 get(obj) / set(obj, value)

反射的特性

  1. 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
  2. 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newlnstance()方法或Constructor对象的newlnstance()方法实现的。
  3. 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
  4. 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。

反射的核心入口:Class 类

Java 中所有类(比如 StringArrayList、你自己写的 User 类)在运行时都会被 JVM 实例化为一个 Class 对象(注意大写 C),这个对象包含了该类的所有元数据(类名、字段、方法等)。

反射的应用:

  • Spring:通过反射创建 Bean 对象、实现依赖注入;
  • MyBatis:通过反射把数据库结果集映射为 Java 对象;
  • IDE(Eclipse/IDEA):通过反射显示类的结构、自动补全代码;
  • 序列化 / 反序列化(JSON 转对象):通过反射给对象的字段赋值。

反射的常用API

类 / 接口 作用
Class 代表类的元数据(反射的入口)
Field 代表类的字段(成员变量)
Method 代表类的方法
Constructor 代表类的构造器

一些核心方法:

  • getConstructor(参数类型...):只能获取公共(public) 的构造器;
  • getDeclaredConstructor(参数类型...):能获取所有访问权限的构造器(包括私有);
  • setAccessible(true):突破访问权限限制(反射的核心操作)。
  • Field.set(对象, 值):给指定对象的该字段赋值;
  • Field.get(对象):获取指定对象的该字段值(返回 Object,需强转)。
  • Method.invoke(对象, 方法参数...):调用指定对象的该方法,参数按顺序传递;
  • getMethod(方法名, 参数类型...):获取公共方法;
  • getDeclaredMethod(方法名, 参数类型...):获取所有访问权限的方法。

Class<?> c = Class.forName(getName("className"));

这个 Class 对象可以代表任意类的运行时类型,但我们暂时不知道(也不需要知道)具体是哪个类。

写法 含义 使用场景
Class<?> 任意类的 Class 对象 动态加载类、通用类操作(如获取类名)
Class<T> 特定类 T 的 Class 对象 已知具体类型,需强类型约束(如反射创建 User 实例)
Class 裸类型(无泛型) 不推荐使用(放弃类型检查)

2.1.7 JAVA注解

注解(Annotation)是一种特殊的标记 ,可以标注在类、方法、字段、参数、包等代码元素上,格式是 @注解名(比如 @Override)。

注解本质 是一个继承了Annotation的特殊接口 ,其具体实现类是Java运行时生成的动态代理类

我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationlnvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

Java 自带了一批开箱即用的注解,分为两类:编译器注解 (给编译器看)和 元注解(给注解本身打标签)。

编译器注解

注解名 作用 示例
@Override 标记方法重写父类的方法,编译器会检查方法签名是否正确(写错就报错) java<br>@Override<br>public String toString() { return "test"; }<br>
@Deprecated 标记方法 / 类 / 字段已过时,使用时编译器会提示警告 java<br>@Deprecated<br>public void oldMethod() {}<br>
@SuppressWarnings 抑制编译器警告(比如泛型未检查、过时 API 警告) java<br>@SuppressWarnings("unchecked")<br>List list = new ArrayList();<br>
@FunctionalInterface 标记函数式接口(仅含一个抽象方法),编译器检查接口是否符合规范 java<br>@FunctionalInterface<br>interface MyInterface { void doSomething(); }<br>

元注解,元注解用于定义自定义注解的行为(比如注解能标在哪里、能保留多久),核心有 4 个:

元注解名 作用
@Target 指定注解能标注的位置(比如类、方法、字段等),参数是 ElementType 枚举
@Retention 指定注解的保留策略(生命周期),参数是 RetentionPolicy 枚举
@Documented 标记注解会被 javadoc 工具生成到文档中
@Inherited 标记注解能被子类继承(仅对类注解生效)
  • @TargetElementType 常用值:
    • TYPE:可标注类、接口、枚举;
    • METHOD:可标注方法;
    • FIELD:可标注字段;
    • PARAMETER:可标注方法参数;
    • CONSTRUCTOR:可标注构造器;
    • ALL:所有位置。
  • @RetentionRetentionPolicy 常用值:
    • SOURCE:仅保留在源码中,编译后丢弃(比如 @Override);
    • CLASS:保留到编译后的 class 文件,但 JVM 运行时丢弃(默认值);
    • RUNTIME:保留到运行时,可通过反射获取(框架常用,比如 Spring 的 @Autowired)。

2.1.8 HttpClient

HttpClient是ApacheJakartaCommon下的子项目,可以用来提供高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包,并且它支持HTTP协议最新的版本和建议。

HttpClient的核心API:

  • HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。
  • HttpClients:可认为是构建器,可创建HttpClient对象。
  • CloseableHttpClient:实现类,实现了HttpClient接口。
  • HttpGet:Get方式请求类型。
  • HttpPost:Post方式请求类型。

HttpClient发送请求步骤:

  • 创建HttpClient对象
  • 创建Http请求对象
  • 调用HttpClient的execute方法发送请求

2.2 技术类

2.2.1 项目所涉及的技术栈

**SpringBoot:**快速构建Spring项目, 采用 "约定优于配置" 的思想, 简化Spring项目的配置开发。

SpringMVC:Spring MVC 是 Spring 框架中专门处理「前端和后端交互」的模块,核心作用是接收前端请求、处理请求、返回响应结果。SpringMVC是spring框架的一个模块,spring mvc和spring无需通过中间整合层进行整合,可以无缝集成。

Spring Task: 由Spring提供的定时任务框架。

**httpclient:**主要实现了对http请求的发送。

**Spring Cache:**由Spring提供的数据缓存框架

**JWT:**用于对应用程序上的用户进行身份验证的标记。

**阿里云OSS:**对象存储服务,在项目中主要存储文件,如图片等。

Swagger: 可以自动的帮助开发人员生成接口文档,并对接口进行测试。

**POI:**封装了对Excel表格的常用操作。

**WebSocket:**一种通信网络协议,使客户端和服务器之间的数据交换更加简单,用于项目的来单、催单功能实现。

MySQL: 关系型数据库, 本项目的核心业务数据都会采用MySQL进行存储。

Redis: 基于key-value格式存储的内存数据库, 访问速度快, 经常使用它做缓存。

Mybatis: 本项目持久层将会使用Mybatis开发。

**pagehelper:**分页插件。

**spring data redis:**简化java代码操作Redis的API。

**git:**版本控制工具, 在团队协作中, 使用该工具对项目中的代码进行管理。

**maven:**项目构建工具。

**junit:**单元测试工具,开发人员功能实现完毕后,需要通过junit对功能进行单元测试。

**postman:**接口测工具,模拟用户发起的各类HTTP请求,获取对应的响应结果。

2.2.2 Nginx

**反向代理:**隐藏后端服务地址,统一入口,让前端只访问 Nginx,不用关心后端在哪

**负载均衡:**当后端有多个服务器时,Nginx 把请求均匀分发到不同服务器,避免单点压力过大。

**静态资源:**直接处理 HTML/CSS/JS/ 图片等静态文件,不用让后端服务器处理,提升性能。

**动静分离:**把静态资源(前端)和动态请求(后端)分开处理,静态走 Nginx,动态走后端,提升整体性能。

**限流防护:**限制单位时间内的请求数,防止恶意攻击或突发流量打垮后端。

复制代码
前端请求 → Nginx → (负载均衡)→ 后端服务1 / 后端服务2 / ...
                        ↓
                  静态资源直接返回

2.2.3 ThreadLocal

ThreadLocal 并不是一个Thread,而是Thread的局部变量。 ThreadLocal为每个线程提供单独一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。

  • public void set(T value) 设置当前线程的线程局部变量的值

  • public T get() 返回当前线程所对应的线程局部变量的值

  • public void remove() 移除当前线程的线程局部变量

2.2.4 AOP切面编程,实现功能增强,来完成公共字段自动填充功能

1). 自定义注解 AutoFill,用于标识需要进行公共字段自动填充的方法

2). 自定义切面类 AutoFillAspect,统一拦截加入了 AutoFill 注解的方法,通过反射为公共字段赋值

3). 在 Mapper 的方法上加入 AutoFill 注解

1)自定义注解 AutoFil

java 复制代码
package com.sky.annotation;

import com.sky.enumeration.OperationType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //数据库操作类型:UPDATE INSERT
    OperationType value();
}
元注解 取值 含义
@Target ElementType.METHOD 注解只能加在方法上
@Retention RetentionPolicy.RUNTIME 注解保留到运行时,可通过反射获取(插件核心依赖)

2)自定义切面 AutoFillAspect

java 复制代码
package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 * 自定义切面,实现公共字段自动填充处理逻辑
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入点
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFillPointCut(){}

    /**
     * 前置通知,在通知中进行公共字段的赋值
     */
    @Before("autoFillPointCut()")
    public void autoFill(JoinPoint joinPoint){
        log.info("开始进行公共字段自动填充...");

        //获取到当前被拦截的方法上的数据库操作类型
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
        OperationType operationType = autoFill.value();//获得数据库操作类型

        //获取到当前被拦截的方法的参数--实体对象
        Object[] args = joinPoint.getArgs();
        if(args == null || args.length == 0){
            return;
        }

        Object entity = args[0];

        //准备赋值的数据
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根据当前不同的操作类型,为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            //为4个公共字段赋值
            try {
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else if(operationType == OperationType.UPDATE){
            //为2个公共字段赋值
            try {
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通过反射为对象属性赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

AOP的核心概念

术语 含义 对应本类中的代码
切面(Aspect) 封装通用逻辑的类(比如这个 AutoFillAspect) @Aspect 注解标记的类
切入点(Pointcut) 定义要拦截哪些方法 @Pointcut(...) 注解的方法
通知(Advice) 拦截方法后要执行的逻辑(前置 / 后置 / 环绕等) @Before(...) 注解的 autoFill 方法
连接点(JoinPoint) 被拦截的方法(比如 Mapper 的 insert/update 方法) joinPoint 参数

3)在Mapper接口的方法上加入 AutoFill 注解

CategoryMapper 为例,分别在新增和修改方法添加@AutoFill()注解,也需要EmployeeMapper做相同操作

java 复制代码
package com.sky.mapper;

@Mapper
public interface CategoryMapper {
    /**
     * 插入数据
     * @param category
     */
    @Insert("insert into category(type, name, sort, status, create_time, update_time, create_user, update_user)" +
            " VALUES" +
            " (#{type}, #{name}, #{sort}, #{status}, #{createTime}, #{updateTime}, #{createUser}, #{updateUser})")
    @AutoFill(value = OperationType.INSERT)
    void insert(Category category);
    /**
     * 根据id修改分类
     * @param category
     */
    @AutoFill(value = OperationType.UPDATE)
    void update(Category category);

}

2.2.5 JAVA异常

2.2.6 关于DTO,VO和实体之间的关系。

DTO、VO、Entity 是数据载体类的核心概念,它们各司其职,解决 "不同层级之间数据传递、数据展示、数据存储" 的问题。

1. 实体(Entity): 数据库的 "镜像",对应数据库中的一张表,每个字段对应表的列,是数据持久化的核心载体。

Entity 就是 "数据库表在代码里的化身",字段和表列一一对应,只负责和数据库交互,不包含业务逻辑。只关注 "数据存储",不关注 "前端展示" 或 "接口传参"。

2. DTO(Data Transfer Object): 数据传输对象。用于不同层级 / 系统之间传递数据的对象(比如前端→后端、服务 A→服务 B),按需封装字段,避免传输冗余数据。

DTO 是 "数据的快递包裹",你需要传什么数据,就往包裹里装什么,不需要的字段一律不放,减少网络 / 内存开销。

3. VO(View Object): 视图对象。用于向前端展示数据的对象,是 "前端能看懂的格式",可能包含多个 Entity/DTO 的数据,或做格式转换。

VO 是 "给前端的最终展示模板",比如前端需要 "订单时间显示为 yyyy-MM-dd"、"订单状态显示为中文(待付款 / 已完成)",这些都在 VO 里处理。

相关推荐
panzer_maus2 小时前
Java多线程介绍
java·开发语言
AMoon丶2 小时前
Golang--多种控制结构详解
java·linux·c语言·开发语言·后端·青少年编程·golang
indexsunny2 小时前
互联网大厂Java面试实战:微服务与Spring Boot在电商场景下的应用解析
java·spring boot·redis·docker·微服务·kubernetes·oauth2
薛定谔之死猫2 小时前
Ruby简单粗暴把图片合成PDF文档
java·pdf·ruby
moxiaoran57532 小时前
Spring Bean线程安全性分析
java·spring
小鸡脚来咯2 小时前
正则表达式考点
java·开发语言·前端
liuyao_xianhui2 小时前
递归_反转链表_C++
java·开发语言·数据结构·c++·算法·链表·动态规划
星辰_mya2 小时前
线上故障排查实战经验总结一
java·开发语言·jvm·面试