全局异常类

文件中使用的注解及其作用:

package com.sky.handler;
 
import com.sky.constant.MessageConstant;
import com.sky.exception.BaseException;
import com.sky.result.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
 
import java.sql.SQLIntegrityConstraintViolationException;
 
/**
 * 全局异常处理器,处理项目中抛出的业务异常
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
 
    /**
     * 捕获业务异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(BaseException ex){
        log.error("异常信息:{}", ex.getMessage());
        return Result.error(ex.getMessage());
    }
 
    /**
     * 捕获SQL异常
     * @param ex
     * @return
     */
    @ExceptionHandler
    public Result exceptionHandler(SQLIntegrityConstraintViolationException ex){
        log.error("异常信息:{}", ex.getMessage());
        String message = ex.getMessage();
        if (message.contains("Duplicate entry")) {
            String[] split = message.split(" ");
            String msg = split[2] + MessageConstant.ALREADY_EXIST_USER;
            return Result.error(msg);
        }else {
            return Result.error(MessageConstant.UNKNOWN_ERROR);
        }
    }
 
}
  1. @RestControllerAdvice

    • 这是Spring框架提供的注解,用于定义全局异常处理类
    • 它是@ControllerAdvice和@ResponseBody的组合注解
    • 用于捕获整个应用程序中控制器层抛出的异常
    • 可以将处理结果自动转换为JSON格式返回给客户端
  2. @Slf4j

    • 这是Lombok提供的注解
    • 用于自动生成日志记录器(Logger)实例
    • 省去了手动创建Logger对象的代码
    • 在类中可以直接使用log变量记录日志信息
  3. @ExceptionHandler

    • 这是Spring框架提供的注解,用于标识异常处理方法
    • 可以指定处理的异常类型,如果不指定则根据方法参数判断
    • 在本例中,有两个同名的exceptionHandler方法,分别处理BaseException和SQLIntegrityConstraintViolationException异常
    • 当系统抛出对应类型的异常时,会自动调用相应的方法进行处理

这些注解共同构成了一个全局异常处理机制,能够统一处理应用程序中的各种异常,避免异常信息直接暴露给用户,提高系统的健壮性和用户体验。

接口注解

@RequestBody 注解的作用

@RequestBody 注解用于将 HTTP 请求的 body 部分数据绑定到方法参数上,它会将请求体中的 JSON 数据反序列化为 Java 对象。

以 EmployeeController 类为例:

@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
    log.info("员工登录:{}", employeeLoginDTO);
    // ... 处理逻辑
}

在这个例子中,@RequestBody 的作用是:

  1. 数据绑定:将客户端发送的 JSON 格式请求体自动转换为 EmployeeLoginDTO 对象
  2. 反序列化:将 HTTP 请求中的 JSON 字符串反序列化为 Java 对象
  3. 参数注入:将转换后的对象作为参数传递给方法

使用场景示例

当客户端发送如下 POST 请求时:

POST /admin/employee/login HTTP/1.1
Content-Type: application/json

{
    "username": "admin",
    "password": "123456"
}

@RequestBody 注解会将这个 JSON 请求体自动转换为 EmployeeLoginDTO 对象,其中 username 和 password 字段会被自动填充。

与不使用 @RequestBody 的区别

  • 使用 @RequestBody:从请求体中获取数据并转换为对象
  • 不使用 @RequestBody:默认从 URL 参数或表单数据中获取参数

例如:

// 从请求体中获取JSON数据
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO)
 
// 从URL参数中获取数据
@GetMapping
public Result<String> getEmployee(@RequestParam String name, @RequestParam Integer age)

@RequestParam 注解的作用

@RequestParam 注解用于将请求参数(URL 参数或表单参数)绑定到方法参数上。它通常用于处理 GET 请求的查询参数或 POST 请求的表单数据。

一般用法:

@GetMapping("/users")
public Result<List<User>> getUsers(@RequestParam String name, 
                                   @RequestParam(required = false, defaultValue = "0") Integer page,
                                   @RequestParam(required = false, defaultValue = "10") Integer size) {
    // 处理逻辑
}

主要属性

  1. value/name:指定请求参数的名称
  2. required:指定参数是否必需,默认为 true
  3. defaultValue:指定参数的默认值

使用示例

假设有一个如下请求:

GET /users?name=张三&page=1&size=10

对应的控制器方法:

@GetMapping("/users")
public Result<List<User>> getUsers(@RequestParam String name,
                                   @RequestParam(defaultValue = "0") Integer page,
                                   @RequestParam(defaultValue = "10") Integer size) {
    // name = "张三"
    // page = 1
    // size = 10
    // 处理分页查询逻辑
}

与 @RequestBody 的区别

注解数据来源使用场景数据格式
@RequestBodyHTTP 请求体接收 JSON、XML 等格式数据复杂对象
@RequestParamURL 参数或表单参数接收简单的键值对参数简单数据类型

示例对比

// 使用 @RequestBody 接收 JSON 数据
@PostMapping("/employee")
public Result save(@RequestBody EmployeeDTO employeeDTO) {
    // 处理 employeeDTO 对象
}
 
// 使用 @RequestParam 接收 URL 参数
@GetMapping("/employee")
public Result query(@RequestParam String name, 
                    @RequestParam Integer status) {
    // 根据 name 和 status 查询员工
}

@RequestParam 注解非常适合处理简单的查询参数和表单提交数据,是 Web 开发中非常常用的注解之一。

不使用注解的参数绑定机制

在Spring MVC中,即使不使用@RequestParam、@RequestBody等注解,参数也可以被正确绑定。

在代码示例中:

@GetMapping("/page")
@ApiOperation(value = "员工分页查询")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO){
    log.info("员工分页查询");
    PageResult pageResult = employeeService.page(page, pageSize, name);
    return Result.success(pageResult);
}

以及另一个版本:

@GetMapping("/page")
@ApiOperation(value = "员工分页查询")
public Result<PageResult> page(Integer page, Integer pageSize, String name){
    log.info("员工分页查询");
    PageResult pageResult = employeeService.page(page, pageSize, name);
    return Result.success(pageResult);
}

这种不使用注解的参数绑定方式有以下特点:

1. 对于简单类型参数

当方法参数是简单类型(如Integer、String等)时,Spring MVC会默认将它们视为请求参数,并尝试从URL查询参数或表单数据中绑定:

// 等价于 @RequestParam Integer page, @RequestParam Integer pageSize, @RequestParam String name
public Result<PageResult> page(Integer page, Integer pageSize, String name)

这相当于:

GET /admin/employee/page?page=1&pageSize=10&name=张三

2. 对于POJO对象参数

当方法参数是复杂对象(如EmployeePageQueryDTO)时,Spring MVC会自动进行数据绑定,将请求参数映射到对象的属性上:

public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO)

这相当于接收以下请求参数并自动填充到对象属性中:

GET /admin/employee/page?page=1&pageSize=10&name=张三

EmployeePageQueryDTO对象的page、pageSize、name属性会被自动赋值。

3. 绑定规则

  • 参数名匹配:HTTP请求参数名需要与方法参数名或对象属性名匹配
  • 类型转换:Spring会自动进行类型转换(如String转Integer)
  • 可选性:未提供的参数默认为null或类型的默认值

4. 与使用注解的区别

方式优点缺点
不使用注解代码简洁,Spring自动处理不够明确,依赖参数名匹配
使用@RequestParam明确指定参数名,可设置默认值和必需性代码稍显冗长

5. 实际应用建议

// 推荐:明确使用注解
@GetMapping("/page")
public Result<PageResult> page(@RequestParam(required = false, defaultValue = "1") Integer page,
                               @RequestParam(required = false, defaultValue = "10") Integer pageSize,
                               @RequestParam(required = false) String name) {
    // 处理逻辑
}
 
// 或者使用DTO对象(适用于参数较多的情况)
@GetMapping("/page")
public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) {
    // 处理逻辑
}

总的来说,虽然不使用注解也能实现参数绑定,但使用注解会让代码更加清晰、明确,并提供更多的控制选项,如设置默认值、指定是否必需等。

@PathVariable

@PathVariable 是 Spring MVC 框架中的一个注解,用于将 URL 中的占位符映射到控制器方法的参数上。

@PathVariable 的作用

当我们在开发 RESTful API 时,经常需要从 URL 路径中提取参数值。例如:

GET /employees/1

在这个例子中,我们想要获取 ID 为 1 的员工信息。URL 中的 “1” 就是我们需要提取的路径变量。

使用示例

@GetMapping("/employees/{id}")
public Employee getEmployee(@PathVariable Long id) {
    // 这里的 id 参数会自动绑定到 URL 中的 {id} 占位符
    return employeeService.findById(id);
}

在上面的例子中:

  • @GetMapping("/employees/{id}") 定义了一个路径模板,其中 {id} 是路径变量占位符
  • @PathVariable Long id 将 URL 中的路径变量绑定到方法参数上

高级用法

  1. 自定义路径变量名称:
@GetMapping("/employees/{empId}")
public Employee getEmployee(@PathVariable("empId") Long id) {
    // 当路径变量名与方法参数名不一致时,可以指定路径变量名
    return employeeService.findById(id);
}
  1. 多个路径变量:
@GetMapping("/employees/{id}/orders/{orderId}")
public Order getOrder(@PathVariable Long id, @PathVariable Long orderId) {
    // 可以同时使用多个 @PathVariable
    return orderService.findById(orderId);
}
  1. 可选路径变量:
@GetMapping({"/employees/{id}", "/employees"})
public Employee getEmployee(@PathVariable(required = false) Long id) {
    // 设置 required = false 使路径变量变为可选
    if (id != null) {
        return employeeService.findById(id);
    } else {
        // 处理没有 ID 的情况
    }
}

与其他注解的区别

  • @RequestParam: 用于获取查询参数 (URL 中 ? 后面的参数)
  • @RequestBody: 用于获取请求体中的数据
  • @RequestHeader: 用于获取请求头信息

@PathVariable 专门用于从 URL 路径中提取参数,是实现 RESTful 风格 API 的重要注解。

@RequestMapping 注解概述

@RequestMapping 是 Spring MVC 框架中最核心的注解之一,用于将 HTTP 请求映射到控制器(Controller)中的处理方法上。它实现了 URL 路径与处理方法之间的映射关系,是 Spring MVC 实现请求分发的核心机制。

基本用法

1. 在类级别使用

当 @RequestMapping 应用于控制器类上时,它为该类中的所有处理方法提供基础路径:

@RestController
@RequestMapping("/admin/common")
public class CommonController {
    // 类中的所有方法都会以"/admin/common"作为基础路径
}

2. 在方法级别使用

@RequestMapping 可以直接应用于方法上,定义该方法处理的具体路径:

@RequestMapping("/upload")
public Result<String> upload(MultipartFile file) {
    return null;
}

结合类级别的 @RequestMapping,该方法的完整访问路径为:/admin/common/upload

@RequestMapping属性

1. value/path 属性

指定请求的 URL 路径:

@RequestMapping("/upload")  // 或 @RequestMapping(value="/upload")
public Result<String> upload(MultipartFile file) {
    return null;
}

2. method 属性

指定处理的 HTTP 方法类型:

@RequestMapping(value="/upload", method=RequestMethod.POST)
// 或者使用组合注解
@PostMapping("/upload")

常见的 HTTP 方法:

  • RequestMethod.GET (对应 @GetMapping)
  • RequestMethod.POST (对应 @PostMapping)
  • RequestMethod.PUT (对应 @PutMapping)
  • RequestMethod.DELETE (对应 @DeleteMapping)

3. params 属性

指定请求参数条件:

// 要求请求必须包含名为"version"的参数
@RequestMapping(value="/api", params="version")
 
// 要求请求必须包含名为"version"且值为"1.0"的参数
@RequestMapping(value="/api", params="version=1.0")
 
// 要求请求不能包含名为"version"的参数
@RequestMapping(value="/api", params="!version")

4. headers 属性

指定请求头条件:

// 要求请求头必须包含"Referer"且值为指定URL
@RequestMapping(value="/api", headers="Referer=http://www.example.com")
 
// 要求请求头必须包含"Accept"
@RequestMapping(value="/api", headers="Accept")

5. consumes 属性

指定处理请求的提交内容类型(Content-Type):

// 只处理Content-Type为application/json的请求
@RequestMapping(value="/api", consumes="application/json")
 
// 只处理Content-Type为multipart/form-data的请求(文件上传)
@RequestMapping(value="/upload", consumes="multipart/form-data")

6. produces 属性

指定返回的内容类型:

// 返回JSON格式数据
@RequestMapping(value="/api", produces="application/json")
 
// 返回XML格式数据
@RequestMapping(value="/api", produces="application/xml")

组合注解

Spring 还提供了一些 @RequestMapping 的组合注解,使代码更加简洁:

  • @GetMapping - 处理 GET 请求
  • @PostMapping - 处理 POST 请求
  • @PutMapping - 处理 PUT 请求
  • @DeleteMapping - 处理 DELETE 请求
  • @PatchMapping - 处理 PATCH 请求

路径匹配模式

@RequestMapping 支持多种路径匹配模式:

// 精确匹配
@RequestMapping("/users")
 
// 路径变量匹配
@RequestMapping("/users/{id}")
 
// 通配符匹配
@RequestMapping("/users/*")     // 匹配/users/下的单级路径
@RequestMapping("/users/**")    // 匹配/users/下的多级路径
 
// 正则表达式匹配
@RequestMapping("/users/{id:\\d+}")  // id必须是数字

@Component 注解概述

@Component 是 Spring 框架中的一个通用注解,用于将一个普通的 Java 类标记为 Spring 容器管理的组件(也称为 Bean)。当一个类被 @Component 注解标记后,Spring 容器会在启动时自动扫描到这个类,并将其注册到 IoC(控制反转)容器中进行管理。

主要作用

  1. 组件标识:将一个类标识为 Spring 容器管理的组件
  2. 自动注册:使 Spring 容器能够自动检测并将该类注册为 Bean 实例
  3. 生命周期管理:Spring 容器负责创建、管理和维护被注解组件的生命周期
  4. 简化配置:无需在 XML 配置文件中手动声明 Bean,直接使用注解即可

使用方式

import org.springframework.stereotype.Component;
 
@Component
public class MyComponent {
    public void doSomething() {
        // 组件的具体实现
    }
}

为了让 @Component 注解生效,通常需要在 Spring 配置中启用组件扫描:

@Configuration
@ComponentScan(basePackages = "com.example") // 指定扫描的包路径
public class AppConfig {
    // 配置类
}

或者在 XML 配置中:

<context:component-scan base-package="com.example"/>

相关注解

@Component 是一个通用注解,Spring 还提供了几个特定用途的派生注解:

  1. @Controller:用于标识控制器层组件,处理 Web 请求
  2. @Service:用于标识服务层组件,提供业务逻辑
  3. @Repository:用于标识数据访问层组件,提供数据访问功能

这些注解本质上与 @Component 相同,但它们提供了更好的语义区分,有助于理解应用程序的架构层次。

项目中的应用

项目中,AutoFillAspect 类使用了 @Component 注解:

@Aspect // 表示当前类是一个切面类
@Component // 表示当前类是一个组件,会被Spring容器扫描到并管理
@Slf4j // 表示自动注入log对象,log对象表示日志记录器,可以记录日志信息
public class AutoFillAspect {
    // ...
}

这表示 AutoFillAspect 类会被 Spring 容器自动检测并注册为一个 Bean,这样它就可以作为切面类正常工作,实现公共字段自动填充的功能。

工作原理

  1. Spring 容器启动时,会扫描指定包路径下的所有类
  2. 发现带有 @Component 注解的类时,会通过反射机制实例化该类
  3. 将实例化的对象注册到 Spring 容器中,作为 Bean 进行管理
  4. 其他需要依赖该组件的类可以通过 @Autowired 等注解进行依赖注入

通过使用 @Component 注解,Spring 实现了组件的自动发现和管理,大大简化了配置工作,提高了开发效率。

@ConfigurationProperties 基本概念

@ConfigurationProperties 注解允许我们将配置文件(如 application.yml 或 application.properties)中的属性值自动绑定到 Java Bean 的字段中。它提供了一种类型安全的配置属性处理方式。

主要特点

  1. 类型安全:与 @Value 注解相比,@ConfigurationProperties 提供了更强的类型安全,能够自动进行类型转换。

  2. 批量注入:可以一次性将配置文件中具有相同前缀的多个属性值注入到 Bean 中。

  3. 验证支持:可以结合 JSR-303 验证注解(如 @NotNull、@Min 等)对属性值进行验证。

使用方式

1. 添加依赖

确保项目中包含 spring-boot-configuration-processor 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

2. 创建配置属性类

@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Data
public class AliOssProperties {
    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketName;
}

3. 配置文件中定义属性

sky:
  alioss:
    endpoint: oss-cn-beijing.aliyuncs.com
    access-key-id: your-access-key-id
    access-key-secret: your-access-key-secret
    bucket-name: your-bucket-name

常用属性

  • prefix:指定属性前缀,用于匹配配置文件中的属性键
  • ignoreInvalidFields:是否忽略无效字段,默认为 false
  • ignoreUnknownFields:是否忽略未知字段,默认为 true

与其他注解的区别

  1. @Value:适用于单个属性注入,@ConfigurationProperties 适用于批量属性注入
  2. @PropertySource:用于指定配置文件位置,通常与 @ConfigurationProperties 配合使用

验证支持

可以结合验证注解使用:

@Component
@ConfigurationProperties(prefix = "sky.alioss")
@Validated
@Data
public class AliOssProperties {
    @NotBlank
    private String endpoint;
    
    @NotBlank
    private String accessKeyId;
    
    @NotBlank
    private String accessKeySecret;
    
    @NotBlank
    private String bucketName;
}

这种方式使得配置管理更加规范、安全和易于维护,特别适合处理外部化配置。

新增菜品中使用到了该注解

@Autowired

@Autowired 是 Spring 框架中最重要的注解之一,它的核心作用是自动装配(Automatic Wiring),也就是我们常说的依赖注入(Dependency Injection, DI)。通过使用它,你不再需要手动创建和管理对象之间的依赖关系,Spring 容器会帮你自动完成这一切。


1. 核心作用:依赖注入 (DI)

在理解 @Autowired 之前,需要先明白什么是“依赖注入”。

假设你有一个 OrderService (订单服务),它需要使用 UserRepository (用户仓库) 来查询用户信息。

没有依赖注入的写法:

public class OrderService {
    // OrderService 依赖 UserRepository
    // 我们需要自己手动创建 UserRepository 实例
    private UserRepository userRepository = new UserRepository();
 
    public void createOrder(Long userId) {
        User user = userRepository.findById(userId);
        // ... 创建订单的逻辑
    }
}

这种写法的缺点是 OrderServiceUserRepository 紧密耦合在一起了。如果 UserRepository 的创建方式很复杂(比如需要连接池、配置文件等),OrderService 的代码也会变得复杂,并且不利于单元测试。

使用依赖注入的写法:

@Service // 告诉 Spring 这是一个服务类 (Bean)
public class OrderService {
    // 声明一个依赖,但不需要自己创建它
    @Autowired
    private UserRepository userRepository;
 
    public void createOrder(Long userId) {
        // 直接使用,Spring 已经为我们注入了实例
        User user = userRepository.findById(userId);
        // ... 创建订单的逻辑
    }
}
 
@Repository // 告诉 Spring 这是一个仓库类 (Bean)
public class UserRepository {
    // ... 数据库操作 ...
}

@Autowired 告诉 Spring 容器:“请在这里帮我注入一个 UserRepository 类型的实例(Bean)”。Spring 容器会在启动时自动扫描,找到一个符合条件的 UserRepository Bean,然后把它赋值给 userRepository 字段。


2. @Autowired 的三种使用方式

@Autowired 可以用在三个地方:字段、构造器和 Setter 方法上。

a. 字段注入 (Field Injection)

这是最常见、最简洁的用法。

@Service
public class MyService {
    @Autowired
    private MyRepository myRepository; // 直接在字段上使用
}
  • 优点:代码非常简洁。
  • 缺点
    • 不利于单元测试:无法在不启动 Spring 容器的情况下,方便地为 myRepository 传入一个 mock 对象。
    • 可能导致循环依赖:如果两个类通过字段注入相互依赖,可能会引发问题。
    • 依赖关系不明确:无法通过构造器一眼看出这个类有哪些是必须的依赖。

b. 构造器注入 (Constructor Injection)

这是 Spring 官方最为推荐的使用方式。

@Service
public class MyService {
 
    private final MyRepository myRepository; // 推荐使用 final 关键字
 
    // 构造器
    @Autowired // 当类只有一个构造器时,此注解可以省略
    public MyService(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}
  • 优点
    • 依赖清晰:构造器参数明确地声明了类的必要依赖。
    • 保证对象不可变性:可以将依赖字段声明为 final,确保在对象创建后不会被修改,增加了程序的健壮性。
    • 非常便于单元测试:你可以轻易地通过 new MyService(mockRepository) 来实例化这个类,并传入 mock 对象进行测试,完全不需要 Spring 容器。
    • 避免循环依赖:如果存在循环依赖,Spring 容器在启动时会立即抛出异常,可以及早发现问题。

c. Setter 方法注入 (Setter Method Injection)

@Service
public class MyService {
 
    private MyRepository myRepository;
 
    @Autowired
    public void setMyRepository(MyRepository myRepository) {
        this.myRepository = myRepository;
    }
}
  • 优点
    • 适用于可选依赖:这种方式允许你在对象创建后再注入依赖,适用于那些非必需的、可以动态改变的依赖。
  • 缺点
    • 无法使用 final 关键字。
    • 依赖关系不如构造器注入清晰。

3. @Autowired 的工作原理

Spring 容器在进行自动装配时,默认遵循**按类型匹配(byType)**的原则。

  1. 按类型查找:Spring 容器会扫描所有由它管理的 Bean,查找与需要注入的字段/参数类型相匹配的 Bean。
  2. 如果只找到一个:直接注入。
  3. 如果找到多个:当接口有多个实现类时,Spring 会犯难,不知道该注入哪一个,此时会抛出 NoUniqueBeanDefinitionException 异常。

例如,有一个 PaymentService 接口和两个实现类:

public interface PaymentService {
    void pay();
}
 
@Service("alipay")
public class AlipayService implements PaymentService { ... }
 
@Service("wechatPay")
public class WechatPayService implements PaymentService { ... }

如果你这样写,就会报错:

@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService; // 错误!Spring 不知道该注入 AlipayService 还是 WechatPayService
}

如何解决多个匹配项的问题?

有两种主要方式:

  • 使用 @Qualifier 注解:通过指定 Bean 的名字来明确要注入哪一个。

    @Service
    public class OrderService {
        @Autowired
        @Qualifier("wechatPay") // 明确指定注入名为 "wechatPay" 的 Bean
        private PaymentService paymentService;
    }
  • 使用 @Primary 注解:在多个实现类中,指定一个首选的(主要的)Bean。

    @Service("alipay")
    public class AlipayService implements PaymentService { ... }
     
    @Primary // 标记为首选
    @Service("wechatPay")
    public class WechatPayService implements PaymentService { ... }

    这样,在自动装配时,Spring 会优先注入 WechatPayService


4. @Autowired(required = false)

默认情况下,@Autowired 要求依赖必须存在,否则 Spring 容器在启动时会报错。如果你希望某个依赖是可选的,可以使用 required = false

@Service
public class MyService {
    @Autowired(required = false)
    private OptionalService optionalService; // 如果容器中没有 OptionalService 类型的 Bean,也不会报错,此时 optionalService 的值为 null
}

5. @Autowired vs @Resource

@Resource 是 Java EE (JSR-250) 规范提供的注解,Spring 也支持它。它和 @Autowired 的主要区别在于装配顺序:

  • @Autowired:Spring 特有。默认**按类型(byType)装配。如果发现多个同类型的 Bean,再按名称(byName)**匹配。
  • @Resource:Java 规范。默认**按名称(byName)装配。如果找不到指定名称的 Bean,再按类型(byType)**匹配。

总结

特性描述
核心功能实现依赖注入,由 Spring 容器自动将所需的 Bean 注入到类中。
工作方式默认按类型查找 Bean 进行注入。
使用位置字段、构造器、Setter 方法。
最佳实践推荐使用构造器注入,因为它能保证依赖的不可变性、明确性和易测试性。
常见问题当同一类型存在多个 Bean 时,需要配合 @Qualifier@Primary 来解决歧义。
可选依赖使用 @Autowired(required = false) 来表示某个依赖不是必需的。

@Autowired 是 Spring IoC (控制反转) 容器的核心体现,它极大地简化了 Java 应用的开发,让开发者可以更专注于业务逻辑,而不是对象的手动创建和管理。

@Bean

在 Spring Boot 中注册一个 Bean(即让 Spring IoC 容器来管理一个对象)非常简单,主要有以下几种常用方式。


方式一:使用组件扫描注解(最常用)

这是最自动化、最方便的方式。Spring Boot 的启动类上通常都有 @SpringBootApplication 注解,而这个注解内部包含了 @ComponentScan@ComponentScan 会自动扫描启动类所在包及其子包下的所有组件,并将它们注册为 Bean。

你只需要在你的类上添加特定的“构造型(Stereotype)”注解即可。

常见的组件扫描注解:

  • @Component:通用的组件注解,表示这是一个可以被 Spring 管理的 Bean。

  • @Service:通常用于标注业务逻辑层的组件(Service)。

  • @Repository:通常用于标注数据访问层的组件(DAO)。它除了能被扫描外,还能将特定的数据访问异常转为 Spring 的统一异常体系。

  • @Controller / @RestController:用于标注 Web 控制器层的组件。

示例:

假设你的项目结构如下:

com.example.myapp
├── MyappApplication.java   // 启动类
├── controller
│   └── MyController.java
├── service
│   └── MyService.java
└── repository
    └── MyRepository.java

你只需要在相应的类上添加注解:

1. Service 层

package com.example.myapp.service;
 
import org.springframework.stereotype.Service;
 
@Service // 告诉 Spring:请创建并管理这个类的实例
public class MyService {
    public String getMessage() {
        return "Hello from MyService!";
    }
}

2. Repository 层

package com.example.myapp.repository;
 
import org.springframework.stereotype.Repository;
 
@Repository // 告诉 Spring:这是数据访问组件
public class MyRepository {
    // ...
}
  1. 在其他组件中使用

Spring 容器会自动创建 MyService 的实例,然后你就可以在其他组件(如 Controller)中通过 @Autowired 注入并使用它。

package com.example.myapp.controller;
 
import com.example.myapp.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class MyController {
 
    private final MyService myService;
 
    @Autowired // 注入 MyService Bean
    public MyController(MyService myService) {
        this.myService = myService;
    }
 
    @GetMapping("/")
    public String home() {
        return myService.getMessage();
    }
}

Note

对于自己编写的业务类(Controller, Service, Repository 等),使用组件扫描注解是最佳且最常用的方式。


方式二:使用 @Configuration@Bean(非常常用)

当你想注册一个来自第三方库的类的实例(比如 RestTemplate, ObjectMapper),或者当一个 Bean 的创建过程比较复杂,需要一些自定义逻辑时,这种方式是最好的选择。

它的步骤是:

  1. 创建一个配置类,并用 @Configuration 注解标记它。

  2. 在配置类中创建一个方法,这个方法返回你想要注册为 Bean 的对象实例。

  3. 在该方法上使用 @Bean 注解。

示例:注册一个第三方库的 RestTemplate Bean

RestTemplate 是 Spring 提供的一个用于发起 HTTP 请求的工具类,但它不会被自动注册,需要我们手动配置。

1. 创建配置类

package com.example.myapp.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
@Configuration // 声明这是一个配置类
public class AppConfig {
 
    @Bean // 声明这是一个 Bean
    // 方法名 "restTemplate" 将作为默认的 Bean 名称
    public RestTemplate restTemplate() {
        // 创建并返回一个 RestTemplate 实例
        // 这里可以进行复杂的初始化操作,比如设置超时、拦截器等
        return new RestTemplate();
    }
}

2. 在需要的地方注入使用

@Service
public class SomeApiService {
 
    @Autowired
    private RestTemplate restTemplate; // Spring 会注入上面方法创建的那个 RestTemplate 实例
 
    public String fetchData() {
        // 使用 restTemplate 发起请求
        return restTemplate.getForObject("https://api.example.com/data", String.class);
    }
}

你也可以自定义 Bean 的名称:

@Bean("myCustomRestTemplate")
public RestTemplate anotherRestTemplate() {
    return new RestTemplate();
}

Note

当你无法修改一个类的源码(如第三方库),或者创建 Bean 的逻辑很复杂时,请使用 @Configuration + @Bean


总结与最佳实践

方式适用场景优点
组件扫描注解 (@Service, @Component 等)项目内部自己编写的类,如 Controller、Service、Repository 等。简单、自动化,符合“约定优于配置”的原则。
@Configuration + @Bean1. 注册第三方库中的对象
2. 创建过程复杂的对象
3. 需要解耦,将对象的创建逻辑集中管理。
灵活、功能强大,可以完全控制 Bean 的创建和配置过程。

在日常的 Spring Boot 开发中,这两种方式几乎涵盖了 99% 的 Bean 注册场景。通常的实践是:

  • 用组件扫描注解来管理你自己的业务组件。
  • @Configuration@Bean 来管理基础设施组件和第三方组件。

@EnableTransactionManagement

什么是事务?

在介绍注解之前,首先要理解数据库事务(Transaction)。事务是一系列数据库操作(如增、删、改)的集合,它被视为一个不可分割的逻辑工作单元。事务必须遵循 ACID 原则:

  • 原子性 (Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚。

  • 一致性 (Consistency): 事务执行前后,数据库都必须处于一致的状态。

  • 隔离性 (Isolation): 多个并发事务之间相互隔离,一个事务的执行不应被其他事务干扰。

  • 持久性 (Durability): 事务一旦提交,其结果就是永久性的。

在业务开发中,一个业务操作(比如“用户下单”)可能包含多个数据库操作(扣减库存、创建订单、更新用户积分等)。我们必须保证这些操作要么全部完成,要么一个都不做,否则就会出现数据不一致的问题。这时,就需要使用事务管理。

作用

@EnableTransactionManagement 是一个启用注解。它的作用就像一个开关,告诉 Spring 框架:“请开启基于注解的事务管理功能”。

当 Spring 容器启动时,如果扫描到这个注解,它就会创建一个代理(Proxy),去寻找所有被 @Transactional 注解标记的 public 方法。当这些方法被调用时,Spring 的事务管理功能就会被激活,在方法执行前后进行事务的开启、提交或回滚。

在 Spring Boot 中还需要手动添加吗?

通常不需要。

这是 Spring Boot 自动配置(Auto-configuration)的魅力所在。只要你的项目中引入了与数据持久化相关的 starter,例如:spring-boot-starter-data-jpa,spring-boot-starter-jdbc,spring-boot-starter-jooq

Spring Boot 会自动检测到这些依赖,并自动配置好事务管理器(PlatformTransactionManager),同时自动启用基于注解的事务管理功能。这个过程等同于你手动添加了 @EnableTransactionManagement

结论: 在绝大多数标准的 Spring Boot 应用中,你不需要在任何配置类上显式地添加 @EnableTransactionManagement 注解。只有当你完全脱离 Spring Boot 的自动配置,手动配置数据源和事务管理器时,才需要使用它。


@Transactional

@Transactional核心的事务注解,它用来声明一个方法或一个类的所有方法需要运行在数据库事务中。

使用位置

  • 方法上:推荐用法。只对当前 public 方法生效,提供最精细的事务控制。

  • 类上:该类下所有 public 方法都将应用相同的事务配置。如果方法上也有 @Transactional,则方法级别的配置会覆盖类级别的配置。

工作原理(AOP 代理)

Spring 是通过 AOP (面向切面编程) 来实现 @Transactional 的。当一个 Bean 的方法被 @Transactional 标记时,Spring 不会直接将这个 Bean 注入给其他依赖方,而是注入一个该 Bean 的代理对象

调用流程如下:

调用方 代理对象 开启事务 真实对象的方法 提交/回滚事务 返回结果

这个代理机制是理解 @Transactional 许多“失效”场景的关键。

常用属性

@Transactional 注解提供了丰富的属性,让你能精细地控制事务的行为。

a. propagation:事务的传播行为

定义了当一个事务方法被另一个事务方法调用时,事务应该如何创建和管理。

  • Propagation.REQUIRED (默认值): 如果当前存在事务,则加入该事务;如果不存在,则创建一个新的事务。这是最常用的设置。

  • Propagation.REQUIRES_NEW: 无论当前是否存在事务,都创建一个新的独立事务。如果外部存在事务,外部事务会被挂起,直到新事务完成。

  • Propagation.SUPPORTS: 如果当前存在事务,则加入该事务;如果不存在,则以非事务的方式执行。

  • Propagation.NOT_SUPPORTED: 以非事务的方式执行。如果当前存在事务,则将该事务挂起。

  • Propagation.MANDATORY: 必须在一个已存在的事务中执行,否则抛出异常。

  • Propagation.NEVER: 必须以非事务的方式执行,如果存在事务,则抛出异常。

  • Propagation.NESTED: 如果当前存在事务,则创建一个嵌套事务(Savepoint)。如果不存在,则行为同 REQUIRED

b. isolation:事务的隔离级别

定义了事务处理并发问题时的隔离程度。

  • Isolation.DEFAULT (默认值): 使用数据库默认的隔离级别(通常是 READ_COMMITTEDREPEATABLE_READ)。

  • Isolation.READ_UNCOMMITTED: 读未提交。可能导致脏读、不可重复读、幻读。

  • Isolation.READ_COMMITTED: 读已提交。可以防止脏读,但可能导致不可重复读、幻读。

  • Isolation.REPEATABLE_READ: 可重复读。可以防止脏读和不可重复读,但可能导致幻读。(MySQL 默认)

  • Isolation.SERIALIZABLE: 串行化。最高隔离级别,可以防止所有并发问题,但性能最差。

c. rollbackFornoRollbackFor:异常回滚规则

  • 默认情况下,只有当方法抛出 RuntimeExceptionError 时,事务才会回滚。对于检查型异常(Checked Exception,如 IOException),事务不会回滚。

  • rollbackFor: 指定哪些异常需要触发事务回滚。

// 即使是 IOException (检查型异常),也会触发回滚
@Transactional(rollbackFor = IOException.class)
public void saveData() throws IOException { ... }
  • noRollbackFor: 指定哪些异常需要触发事务回滚。

d. readOnly:只读事务

  • @Transactional(readOnly = true)

  • 将事务设置为只读模式。这可以作为一个性能优化的提示,数据库可能会针对只读查询做一些优化。

  • 如果你在一个标记为 readOnly 的事务中尝试进行写操作(INSERT, UPDATE, DELETE),会抛出异常。强烈建议在所有查询方法上都加上此属性

e. timeout:事务超时

  • @Transactional(timeout = 30)

  • 设置事务的超时时间(单位:秒)。如果事务执行时间超过这个值,就会被强制回滚。

示例代码

@Service
public class OrderService {
 
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private StockRepository stockRepository;
 
    // 这是一个完整的业务单元,需要事务保证原子性
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void createOrder(Order order, Product product) {
        // 1. 创建订单
        orderRepository.save(order);
 
        // 2. 扣减库存
        int affectedRows = stockRepository.decreaseStock(product.getId(), order.getQuantity());
        if (affectedRows == 0) {
            // 如果库存不足,手动抛出异常,整个事务会回滚,上面已保存的订单也会被撤销
            throw new RuntimeException("库存不足!");
        }
        
        // 模拟其他异常
        // if (true) { throw new IOException("文件读写错误"); } 
        // 如果没有 rollbackFor = Exception.class,这个 IOException 不会触发回滚
    }
 
    @Transactional(readOnly = true)
    public Order findOrderById(Long id) {
        return orderRepository.findById(id);
    }
}

常见失效场景(重要!)

由于 @Transactional 基于 AOP 代理,以下情况会导致事务失效:

  1. 应用在非 public 方法上protectedprivate 或包可见性的方法上加 @Transactional 会失效,因为代理无法拦截。

  2. 方法内部调用:在一个类中,一个没有事务的方法 A() 调用了同一个类中有事务的方法 B()this.B()),方法 B 的事务会失效。因为 this 指向的是真实对象,而不是代理对象,调用绕过了代理的事务增强逻辑。

  3. 异常被 try-catch 捕获:如果在事务方法内部 catch 住了异常并且没有重新抛出,Spring 的代理就无法感知到异常的发生,事务会正常提交而不是回滚。

总结

注解作用在 Spring Boot 中的使用
@EnableTransactionManagement作为一个开关,启用 Spring 基于注解的事务管理功能。通常不需要。Spring Boot 的数据相关 starter 会自动配置并启用。
@Transactional标记在方法或类上,声明一个事务的边界和属性(如传播行为、隔离级别等)。非常常用。应用于 Service 层的 public 方法上,用于保证业务操作的原子性。

@Builder

@BuilderLombok 提供的注解,用于在 Java 中简化 Builder 模式 的实现。Builder 模式常用于创建对象时属性很多、构造函数参数复杂的情况,它可以让对象的构造更清晰、可读性更好。


一、作用

  • 自动为类生成一个 Builder 类

  • 支持 链式调用 来设置属性。

  • 避免冗长的构造函数。

  • 可读性好,适合属性较多的场景。


二、基本用法

import lombok.Builder;
import lombok.ToString;
 
@Builder
@ToString
public class User {
    private String username;
    private String email;
    private int age;
}
 
public class Main {
    public static void main(String[] args) {
        User user = User.builder()
                        .username("小帅")
                        .email("xiaoshuai@example.com")
                        .age(21)
                        .build();
 
        System.out.println(user);
    }
}

输出:

User(username=小帅, email=xiaoshuai@example.com, age=21)

三、特点和细节

  1. 生成的方法

    • ClassName.builder():创建一个 Builder 实例。

    • builder.fieldName(value):设置字段。

    • build():返回构建好的对象。

  2. 可与其他 Lombok 注解配合

    • @AllArgsConstructor / @NoArgsConstructor

    • @Data / @Getter / @Setter

  3. 支持部分参数构建

    • 不需要给所有字段赋值,未赋值的为默认值(对象为 null,基本类型为默认值)。
  4. 对继承的支持

    • 直接 @Builder 不支持继承情况,Lombok 提供了 @SuperBuilder 注解,用于父子类都使用 Builder 模式。

四、进阶用法

1. 自定义 Builder 方法名

@Builder(builderMethodName = "createBuilder")
public class User {
    private String name;
    private int age;
}

使用方式:

User user = User.createBuilder().name("小帅").age(22).build();

2. 与 @Singular 一起使用

用于集合类型字段,可以避免手动构建集合。

import lombok.Builder;
import lombok.Singular;
import java.util.List;
 
@Builder
public class Team {
    private String name;
    @Singular
    private List<String> members;
}

使用方式:

Team team = Team.builder()
                .name("开发组")
                .member("小帅")
                .member("小李")
                .build();

3. 默认值

@Builder.Default 可以为字段指定默认值:

@Builder
public class User {
    private String name;
    @Builder.Default
    private int age = 18;
}

👉 总结:
@Builder 是 Lombok 提供的便捷注解,用来实现 Builder 模式,特别适合字段多、构造复杂的类。如果有继承关系,则推荐用 @SuperBuilder