项目开发笔记-5

SpringBoot笔记

Posted by Sirin on November 11, 2025

什么是SpringBoot?它的作用是什么

SpringBoot是Spring框架下的一个子项目,其作用是旨在简化Spring应用的初始搭建和开发过程,它在Spring框架之上提供了一套开箱即用的解决方法,相当于Spring框架的快速启动的工具包。

其核心特征包括:

  1. 约定优于配置:提供了合理的默认配置,减少决策负担
  2. 自动配置:根据类路径中的依赖关系自动配置应用程序,减少了手动配置的工作量
  3. 起步依赖:提供了预定义的依赖包,简化了依赖配置和版本控制
  4. 内嵌服务器:内置了Jetty、Tomcat等Web服务器,无需外部服务器部署
  5. 生产就绪:提供了健康检查、指标监控等生产环境所必须的功能
SpringBoot 传统Spring framework
零配置启动,约定优于配置 需大量XML配置文件
内置Tomcat、Jetty等Web服务器,直接运行jar即可 需要外部部署
自动依赖管理,保证版本兼容性 需要手动管理依赖版本
本身具备生产监控功能 需要额外配置

Spring IoC容器

也叫做Spring应用上下文,其作用是管理应用中所有的对象(这些对象称为Bean),其核心思想是实现了控制反转依赖注入这两种设计思想。

  1. 控制反转 传统程序设计中,对象自己负责创建和管理它所依赖的其他对象,这就导致了紧耦合。在IoC中,将创建和管理对象的控制权从程序员手中转移给了容器,不需要主动找依赖,容器会主动送来。
  2. 依赖注入 依赖注入是实现控制反转的主要技术手段。 依赖:当我们说对象A依赖于对象B,意思就是对象A的运行需要用到对象B。 注入:容器在创建对象A的时候,会检查它依赖于哪些对象,并将自动创建好的这些对象“注入”到对象A中,包括Constructor注入(推荐)、Setter方法注入、字段注入(不推荐)

代码示例

// 没有Spring IoC容器的情况,紧耦合
public class Car {
    private Engine engine;

    public Car() {
        // Car 自己负责创建 Engine,它们紧密耦合在一起
        this.engine = new Engine();
    }

    public void start() {
        engine.ignite();
        System.out.println("Car started!");
    }
}

这种程序设计方法缺乏防腐设计。

// (1)使用Spring IoC容器,第一步先通过注解定义Bean

@Component // 告诉 Spring,请把此类作为一个 Bean 管理起来
public class Engine {
    public void ignite() {
        System.out.println("Engine ignited.");
    }
}

@Component // 告诉 Spring,请把此类作为一个 Bean 管理起来
public class Car {
    private Engine engine;

    // 构造器注入:Spring 会自动将 Engine 注入进来
    public Car(Engine engine) {
        this.engine = engine;
    }

    public void start() {
        engine.ignite();
        System.out.println("Car started!");
    }
}

//======================
// (2) 使用Spring IoC容器

@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        // 1. 启动 Spring 容器,它会扫描 @Component 等注解,创建并管理所有 Bean
        ApplicationContext context = SpringApplication.run(MyApplication.class, args);

        // 2. 从容器中“获取”Car,而不是自己 new Car()
        Car myCar = context.getBean(Car.class);

        // 3. 使用 Car,此时 Car 内部的 Engine 已经被容器自动装配好了
        myCar.start(); // 输出: Engine ignited. Car started!
    }
}

SpringBoot注解

核心注解

@Configuration

作用是表明当前的类是一个配置类,可以被Spring Ioc容器识别,并可以在这个类内使用@Bean注解来定义和配置Bean。SpringBoot会自动将入口类(包含main方法的类)视作配置类。

@EnableAutoConfiguration

开启SpringBoot的自动配置机制,是SpringBoot的核心特性。

这一注解会让SpringBoot按照用户添加的Jar包依赖,自动配置Spring应用,例如:

  • pom.xml中引入了spring-boot-starter-web时,SpringBoot会自动配置web服务器和Spring MVC。
  • 引入spring-boot-starter-data-jpa和数据库驱动时,SpringBoot会自动配置数据源、JPA的EntityManager等。

@ComponentScan

这个注解会让Spring自动扫描当前包及其所有子包中具有@Component @Configuration @Bean注解的类,并将其注册为Spring容器中的Bean。正因为有这个注解,通常将主应用类放到项目的根包,使之能扫描到所有的组件。

@SpringBootApplication

组合注解,即@Configuration+@EnableAutoConfiguration+ComponentScan

// 完整的主启动类示例
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

Web开发注解

@Controller

使用@Controller标注的类,它的方法通常返回一个视图名称(String),由视图解析器来定位并渲染一个页面(如 return "index"; 会跳转到 index.jsp

@ResponseBody

该标注表示将方法的返回值直接写入HTTP响应体,表明返回的是数据,而不是视图名。

@Controller // 传统的控制器注解
public class MyOldRestApiController {

    @ResponseBody // 需要这个注解来表明返回的是数据,不是视图名
    @GetMapping("/api/user")
    public User getUser() {
        return new User("John", 30); // 希望返回 JSON: {"name": "John", "age": 30}
    }

    @ResponseBody // 每个方法都需要加,很麻烦
    @PostMapping("/api/user")
    public User createUser(...) { ... }
}

@RestController

相当于@Controller@RespondBody的组合,继承了@Controller所以能被Spring组件扫描到并表示为一个Web控制器;内置了@RespondBody的语义,它的所有方法都默认具有@RespondBody的效果。

@RestController 通常与以下注解配合使用,来映射不同的 HTTP 请求:

  • @GetMapping - 处理 HTTP GET 请求,用于获取资源。
  • @PostMapping - 处理 HTTP POST 请求,用于创建资源。
  • @PutMapping - 处理 HTTP PUT 请求,用于更新/替换资源。
  • @DeleteMapping - 处理 HTTP DELETE 请求,用于删除资源。
  • @PatchMapping - 处理 HTTP PATCH 请求,用于部分更新资源。

@RequestBody

作用是将HTTP请求体中的数据反序列化为Java对象,接收前端发送的JSON/XML数据

@RestController
public class UserController {

    @PostMapping("/users")
    // 关键在这里:@RequestBody 将请求体中的 JSON 自动转换为 User 对象
    public ResponseEntity<String> createUser(@RequestBody User user) {
        System.out.println("收到的用户姓名:" + user.getName());
        System.out.println("收到的用户年龄:" + user.getAge());
        // ... 这里可以执行保存到数据库等操作
        return ResponseEntity.ok("用户创建成功");
    }
}

@RequestMapping

主要作用是为Spring MVC框架建立请求URL处理该请求的Java方法之间的映射关系。

@Controller
public class MyController {
    
    // 将这个方法映射到 /home 路径的 GET 请求
    @RequestMapping(value = "/home", method = RequestMethod.GET)
    public String home() {
        return "home-page"; // 返回视图名
    }
    
    // 映射到 /submit 路径的 POST 请求
    @RequestMapping(value = "/submit", method = RequestMethod.POST)
    public String submit() {
        return "result-page";
    }
}

@RequestParam

将 URL 中的查询字符串参数或表单数据绑定到控制器方法的参数

// 1. 方法参数名与URL参数名不同
// GET /api/users?userName=张三
@GetMapping("/api/users")
public User findUser(@RequestParam("userName") String name) {
    // URL 参数是 userName,但方法参数是 name
    return userService.findByName(name);
}

// 2. 指定参数是否必须
// 可以接收:GET /api/users?type=VIP 或 GET /api/users
@GetMapping("/api/users")
public List<User> getUsers(@RequestParam(required = false) String type) {
    // 如果请求中没有 type 参数,type 的值会是 null
    return userService.getUsersByType(type);
}


// 3. 分页查询带有默认值
// GET /api/products 或 GET /api/products?page=3&size=50
@GetMapping("/api/products")
public List<Product> getProducts(
        @RequestParam(defaultValue = "1") int page,
        @RequestParam(defaultValue = "20") int size) {
    // 如果没有提供 page 参数,默认为 1
    // 如果没有提供 size 参数,默认为 20
    return productService.getProducts(page, size);
}

@PathVariable

将 URL 路径中的占位符变量绑定到控制器方法的参数上。

// GET /api/users/123/orders/456
@GetMapping("/api/users/{userId}/orders/{orderId}")
public Order getOrder(@PathVariable Long userId, 
                     @PathVariable Long orderId) {
    // userId = 123, orderId = 456
    return orderService.findUserOrder(userId, orderId);
}

// URL路径变量名与方法参数变量名不匹配时可以明确指定
// GET /api/users/123
@GetMapping("/api/users/{id}")
public User getUser(@PathVariable("id") Long userId) {
    // URL 模板中是 {id},但方法参数名是 userId
    // 需要使用 @PathVariable("id") 明确指定
    return userService.findById(userId);
}

1️⃣@PathVariable与2️⃣@RequestParam的区别:

  • 1️⃣的语法是/api/{variableName};2️⃣的语法是/api?paramName=value
  • 1️⃣要求相应的参数总是必需的;2️⃣则是可选的

依赖注入注解

@Component

通用组件注解,让Spring将这个类实例化并管理到IoC容器中,使其成为一个Bean。简单来说,这个注解让Spring能够自动发现并创建这个类的对象,并在需要时将其注入

@Service

@Component的特化,标识业务逻辑层的组件。

@Repository

@Component的特化,标识数据访问层的组件。

@Autowired

用于自动装配依赖关系,让Spring自动将其他组件(Bean)注入到当前组件。比较常用的是方式是构造器注入,在Spring 4.3+中,如果类只有一个构造器,则可以省略@Autowired注解。

@Autowired支持三种注入方式:构造器注入,Setter方法注入,字段注入。

// 字段注入
@Service
public class UserService {
    
    @Autowired // 直接注入到字段
    private UserRepository userRepository;
    
    @Autowired
    private EmailService emailService;
    
    public User findUser(Long id) {
        return userRepository.findById(id);
    }
}

// Setter方法注入
@Service
public class UserService {
    
    private UserRepository userRepository;
    private EmailService emailService;
    
    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    @Autowired
    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }
}


// 构造器注入
@Service
public class UserService {
    
    private final UserRepository userRepository;
    private final EmailService emailService;
    
    // Spring 会自动寻找合适的 Bean 注入到构造器参数中
    @Autowired
    public UserService(UserRepository userRepository, EmailService emailService) {
        this.userRepository = userRepository;
        this.emailService = emailService;
    }
}

这几种注入方式的对比如下

注入方式 优点 缺点 推荐
构造器注入 不可变(final);完全初始化的对象;易于测试;避免循环依赖问题; 参数多时代码较长 推荐
Setter方法注入 可选依赖;灵活性好; 对象状态可能不完整;不能使用final 一般
字段注入 简洁 隐藏依赖关系;难以测试;不能使用final 不推荐

关于上述注入方式的Q&A:

Q: 为什么Setter方法注入和字段注入不能添加final关键字? A: 在Java中,final关键字表示”不可变的”,被final修饰的字段必须在对象构造完成之前被初始化,且只能被赋值一次。Setter方法可能在构造完成后很久才调用,对于字段注入,Spring通过反射在对象构造完成后设置字段值,此时构造已经完成,无法对final字段赋值。

**Q: **为什么Setter方法注入时对象状态可能不完整?

A: 因为Setter方法的调用时机、调用顺序、隐藏的依赖不确定,对象可能在部分初始化的状态下被使用,导致某些注入的Bean为null。

Q: 为什么字段注入会隐藏依赖?

A: 因为相应的依赖被作为private的字段隐藏,从公共API中根本看不出这个类的依赖关系。

@Qualifier

用于指定要注入的Bean名称,避免歧义。

// 自定义 Bean 名称
@Component("creditCardService")
public class CreditCardPaymentService implements PaymentService {
    // ...
}

@Component("payPalService")  
public class PayPalPaymentService implements PaymentService {
    // ...
}

@Service
public class OrderService {
    
    @Autowired
    @Qualifier("creditCardService") // 使用自定义名称
    private PaymentService primaryPaymentService;
    
    @Autowired
    @Qualifier("payPalService")
    private PaymentService secondaryPaymentService;
}

@Primary

用于标记多个相同类型的Bean中的默认选择,Spring在没有特别指定的情况下,会优先选择这个Bean。

// MySQL 数据源作为默认选择
@Component
@Primary // 标记为首选 Bean
public class MySQLDataSource implements DataSource {
    public Connection getConnection() {
        return new MySQLConnection();
    }
}

// H2 数据源作为备选
@Component
public class H2DataSource implements DataSource {
    public Connection getConnection() {
        return new H2Connection();
    }
}

@Repository
public class UserRepository {
    
    @Autowired
    private DataSource dataSource; // 会自动注入 MySQLDataSource
    
    public void saveUser(User user) {
        // 使用默认的 MySQL 数据源
    }
}

数据访问注解

@Entity

标记一个Java类是一个实体,表示这个类对应数据库中的一张表,类的实例对应表中的一行记录。它建立了Java对象和数据库表之间的映射关系,使得用户能够以面向对象的方式操作数据库。

其基本要求包括

  • 类必须有无参构造方法
  • 类不能被声明为final

一个编码示例如下,展示了@Entity配套的核心注解@Id, @GeneratedValue等。

import javax.persistence.*;

@Entity // 标记这个类是一个 JPA 实体
@Table(name = "users") // 可选:指定表名,默认使用类名
public class User {
    
    @Id // 标记为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 主键生成策略
    private Long id;
    
    @Column(name = "username") // 可选:指定列名,默认使用字段名
    private String username;
    
    private String email; // 默认映射到 email 列
    private Integer age;  // 默认映射到 age 列
    
    // 必须有无参构造函数
    public User() {
    }
    
    // 有参构造函数
    public User(String username, String email, Integer age) {
        this.username = username;
        this.email = email;
        this.age = age;
    }
    
    // Getter 和 Setter 方法
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    
    public String getEmail() { return email; }
    public void setEmail(String email) { this.email = email; }
    
    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }
}

@Transactional

该注解用于声明一个方法或类需要事务管理,保证数据库操作的ACID特性,即原子性Atomicity,一致性Consistency,隔离性Isolation,持久性Durability。该注解确保了一组数据库操作要么全部成功,要么全部失败回滚,不会部分成功,适合于转账业务这样的操作。

@Query

该注解用于在Repository接口中定义自定义的SQL或者Java Persistence Query Language查询,例如:

public interface UserRepository extends JpaRepository<User, Long> {
    
    // 使用原生 SQL 查询
    @Query(value = "SELECT * FROM users u WHERE u.email = ?1", nativeQuery = true)
    User findByEmailNative(String email);
    
    // 复杂的原生 SQL 查询
    @Query(value = "SELECT u.* FROM users u " +
                   "WHERE u.age > ?1 AND u.status = ?2 " +
                   "ORDER BY u.created_date DESC " +
                   "LIMIT ?3", nativeQuery = true)
    List<User> findActiveUsersOlderThan(Integer age, String status, Integer limit);
}

@Modifiying

该注解用于标记一个@Query方法会修改数据库数据(INSERT, UPDATE, DELETE),而不是仅仅查询。

SpringBoot Starter

Starter是SpringBoot提供的一套依赖描述符,包含了特定功能所需的所有依赖。

设计理念是:约定优于配置,开箱即用,零配置启动。

组成部分包括:依赖管理+自动配置+默认配置

命名规范上:官方采用spring-boot-starter-*,第三方为*-spring-boot-starter

优势:SpringBoot Starter是SpringBoot框架实现其核心理念——约定优于配置——的核心组件,能够简化Maven/Gradle配置,让依赖管理不再那么复杂,同时具有强大的自动配置能力,无需编写大量的XML配置,做到开箱即用,极大提升开发效率。同时,它支持覆盖默认配置,提供了灵活性。

Starter工作原理

  1. 依赖引入:通过Maven/Gradle引入Starter依赖
  2. 自动配置:StringBoot扫描spring.factories文件,加载自动配置类
  3. 条件判断:根据@Conditional注解判断是否满足装配条件
  4. Bean创建:在条件满足的情况下自动创建和配置相关Bean

@ConditionOnClass注解

这一注解是实现条件化配置的核心,只有当 类路径下存在指定的类时,才会创建被注解的配置类或Bean。

其重要性体现在:

  • 避免ClassNotFoundException
  • 实现按需配置,根据项目引入的Starter来激活相关配置
  • 通过条件判断避免强制依赖,只有用户实际引入相应依赖时配置类才会激活

如何自定义Starter

一个自定义Starter项目结构如下

my-spring-boot-starter/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/starter/
│   │   └── resources/
│   │       └── META-INF/
│   └── test/
├── pom.xml
  1. 创建Starter项目,在Maven的pom.xml文件中声明parentspring-boot-starter-parent, dependency包括spring-boot-starterspring-boot-configuration-processor

  2. 定义自动配置类,例如,对于一个向文本添加前后缀的服务,其自动配置类可以是:
    @Configuration
    @ConditionalOnClass(MyService.class)
    @EnableConfigurationProperties(MyProperties.class)
    public class MyAutoConfiguration {
           
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnProperty(prefix = "my.starter", value = "enabled", havingValue = "true", matchIfMissing = true)
        public MyService myService(MyProperties properties) {
            return new MyService(properties.getPrefix(), properties.getSuffix());
        }
         
        // 一个自动配置类中可能会配置多个Bean,且这些Bean之间可能存在依赖关系
      	// 这里以MyServiceHelper作为一个例子
        @Bean
        @ConditionalOnMissingBean
        public MyServiceHelper myServiceHelper(MyService myService) {
            return new MyServiceHelper(myService);
        }
    }
    
  3. 创建配置属性类,用于提供默认配置,进行类型安全的配置绑定和配置验证,核心目的就是实现“约定优于配置”
    @ConfigurationProperties(prefix = "my.starter")
    public class MyProperties {
        /**
         * 内容前缀,默认值为 "["
         */
        private String prefix = "[";
           
        /**
         * 内容后缀,默认值为 "]"
         */
        private String suffix = "]";
           
        /**
         * 是否启用starter,默认true
         */
        private boolean enabled = true;
           
        // Getter和Setter方法
        public String getPrefix() {
            return prefix;
        }
           
        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }
           
        public String getSuffix() {
            return suffix;
        }
           
        public void setSuffix(String suffix) {
            this.suffix = suffix;
        }
           
        public boolean isEnabled() {
            return enabled;
        }
           
        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }
    }
    
  4. 编写spring.factories文件,在src/main/resources/META-INF/目录下创建spring.factories文件并添加
    # 自动配置类注册 - SpringBoot启动时会扫描并处理这些类
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    com.example.starter.config.MyAutoConfiguration
       
    # 配置属性类注册 - 通知SpringBoot,这些配置属性类需要处理
    org.springframework.boot.autoconfigure.EnableConfigurationProperties=\
    com.example.starter.config.MyProperties
       
    # 其他可能的注册项还有 应用上下文初始化器、应用监听器、自动配置导入选择器等
    
  5. 添加配置meta-data

  6. 编写使用文档并发布到Maven仓库

Bean生命周期与作用域

  • 实例化阶段
  • 属性填充阶段
  • 初始化阶段
  • 销毁阶段

作用域:

  1. 单例Bean(Singleton Bean):整个Spring容器中只会有一个实例,容器销毁时销毁,线程不安全,需要自己处理并发
  2. 原型Bean(Prototype Bean):每次获取都会创建新的实例,Spring只负责创建,不负责销毁,整个生命周期需要客户端负责
  3. 懒加载Bean(Lazy Bean):在第一次使用时才会创建