什么是SpringBoot?它的作用是什么
SpringBoot是Spring框架下的一个子项目,其作用是旨在简化Spring应用的初始搭建和开发过程,它在Spring框架之上提供了一套开箱即用的解决方法,相当于Spring框架的快速启动的工具包。
其核心特征包括:
- 约定优于配置:提供了合理的默认配置,减少决策负担
- 自动配置:根据类路径中的依赖关系自动配置应用程序,减少了手动配置的工作量
- 起步依赖:提供了预定义的依赖包,简化了依赖配置和版本控制
- 内嵌服务器:内置了Jetty、Tomcat等Web服务器,无需外部服务器部署
- 生产就绪:提供了健康检查、指标监控等生产环境所必须的功能
| SpringBoot | 传统Spring framework |
|---|---|
| 零配置启动,约定优于配置 | 需大量XML配置文件 |
| 内置Tomcat、Jetty等Web服务器,直接运行jar即可 | 需要外部部署 |
| 自动依赖管理,保证版本兼容性 | 需要手动管理依赖版本 |
| 本身具备生产监控功能 | 需要额外配置 |
Spring IoC容器
也叫做Spring应用上下文,其作用是管理应用中所有的对象(这些对象称为Bean),其核心思想是实现了控制反转和依赖注入这两种设计思想。
- 控制反转 传统程序设计中,对象自己负责创建和管理它所依赖的其他对象,这就导致了紧耦合。在IoC中,将创建和管理对象的控制权从程序员手中转移给了容器,不需要主动找依赖,容器会主动送来。
- 依赖注入 依赖注入是实现控制反转的主要技术手段。 依赖:当我们说对象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工作原理
- 依赖引入:通过Maven/Gradle引入Starter依赖
- 自动配置:StringBoot扫描spring.factories文件,加载自动配置类
- 条件判断:根据
@Conditional注解判断是否满足装配条件 - Bean创建:在条件满足的情况下自动创建和配置相关Bean
@ConditionOnClass注解
这一注解是实现条件化配置的核心,只有当 类路径下存在指定的类时,才会创建被注解的配置类或Bean。
其重要性体现在:
- 避免
ClassNotFoundException - 实现按需配置,根据项目引入的Starter来激活相关配置
- 通过条件判断避免强制依赖,只有用户实际引入相应依赖时配置类才会激活
如何自定义Starter
一个自定义Starter项目结构如下
my-spring-boot-starter/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/starter/
│ │ └── resources/
│ │ └── META-INF/
│ └── test/
├── pom.xml
-
创建Starter项目,在Maven的pom.xml文件中声明
parent为spring-boot-starter-parent,dependency包括spring-boot-starter和spring-boot-configuration-processor。 - 定义自动配置类,例如,对于一个向文本添加前后缀的服务,其自动配置类可以是:
@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); } } - 创建配置属性类,用于提供默认配置,进行类型安全的配置绑定和配置验证,核心目的就是实现“约定优于配置”
@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; } } - 编写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 # 其他可能的注册项还有 应用上下文初始化器、应用监听器、自动配置导入选择器等 -
添加配置meta-data
- 编写使用文档并发布到Maven仓库
Bean生命周期与作用域
- 实例化阶段
- 属性填充阶段
- 初始化阶段
- 销毁阶段
作用域:
- 单例Bean(Singleton Bean):整个Spring容器中只会有一个实例,容器销毁时销毁,线程不安全,需要自己处理并发
- 原型Bean(Prototype Bean):每次获取都会创建新的实例,Spring只负责创建,不负责销毁,整个生命周期需要客户端负责
- 懒加载Bean(Lazy Bean):在第一次使用时才会创建