【Rust 半小时速成(2024 Edition 更新版)】

文章目录

    • [变量绑定(Variable bindings)](#变量绑定(Variable bindings))
      • [`let` 关键字](#let 关键字)
      • [类型标注(Type annotation)](#类型标注(Type annotation))
      • 未初始化变量
      • [丢弃值(Throwing values away)](#丢弃值(Throwing values away))
      • 变量遮蔽(Shadowing)
    • 元组(Tuples)
    • 语句(Statements)
    • 函数(Functions)
    • 代码块(Blocks)
      • [块也是表达式(Blocks are expressions)](#块也是表达式(Blocks are expressions))
      • 隐式返回
      • [一切皆表达式(Everything is an expression)](#一切皆表达式(Everything is an expression))
    • [Field access and method calling(字段访问与方法调用)](#Field access and method calling(字段访问与方法调用))
    • [Modules, `use` syntax(模块与 `use` 语法)](#Modules, use syntax(模块与 use 语法))
      • [Types are namespaces too(类型也是命名空间)](#Types are namespaces too(类型也是命名空间))
      • [The libstd prelude(标准库 prelude)](#The libstd prelude(标准库 prelude))
    • Ownership(所有权)
    • Borrowing(借用)
      • [Borrowing rules(借用规则)](#Borrowing rules(借用规则))
    • [String vs &str(String 与 &str)](#String vs &str(String 与 &str))
    • Structs(结构体)
      • [具名字段结构体(Named fields)](#具名字段结构体(Named fields))
      • [元组结构体(Tuple structs)](#元组结构体(Tuple structs))
      • [类单元结构体(Unit structs)](#类单元结构体(Unit structs))
      • [更新语法(Struct update syntax)](#更新语法(Struct update syntax))
    • Enums(枚举)
      • 更复杂的枚举
      • [枚举与模式匹配(Enums + match)](#枚举与模式匹配(Enums + match))
    • [if let 与 while let](#if let 与 while let)
    • [Error handling(错误处理)](#Error handling(错误处理))
      • [`?` 运算符(The question mark operator)](#? 运算符(The question mark operator))
    • [The `Option` type](#The Option type)
    • Traits(特征)
      • [Orphan rules(孤儿规则)](#Orphan rules(孤儿规则))
      • [`Self` 类型](#Self 类型)
      • [Marker traits(标记特征)](#Marker traits(标记特征))
      • [Trait 方法接收者](#Trait 方法接收者)
      • [Deriving traits(派生特征)](#Deriving traits(派生特征))
    • Generics(泛型)
      • 泛型函数
      • [类型参数约束(Trait bounds)](#类型参数约束(Trait bounds))
    • Lifetimes(生命周期)
      • [省略规则(Lifetime elision)](#省略规则(Lifetime elision))
    • 结尾:继续前进吧!

要熟练掌握一门编程语言,最好的方法就是大量阅读它的代码。

但如果你连代码里的关键字和符号是什么意思都不知道,又怎么读得下去呢?

这篇文章不会只深挖一两个概念,而是尽可能展示大量 Rust 代码片段,并解释其中出现的关键词和符号。

准备好了吗?开始!

变量绑定(Variable bindings)

let 关键字

let 用于引入一个变量绑定(binding):

rust 复制代码
let x;      // 声明 "x"
x = 42;     // 把 42 赋值给 "x"

也可以一行完成:

rust 复制代码
let x = 42;

类型标注(Type annotation)

可以用 : 显式指定类型,这就是类型标注

rust 复制代码
let x: i32; // i32 是有符号 32 位整数
x = 42;

一行写法:

rust 复制代码
let x: i32 = 42;

Rust 支持多种整数类型:i8i16i32i64i128 以及对应的无符号版本 u8u16u32u64u128

2024 提示:类型推断在 2024 Edition 下依然强大,多数情况下不需要显式标注。

未初始化变量

如果你先声明变量,后面再赋值,编译器会严格检查:在使用前必须完成初始化。

rust 复制代码
let x;
foobar(x); // 错误:使用可能未初始化的变量
x = 42;

但下面这样完全合法:

rust 复制代码
let x;
x = 42;
foobar(x); // 类型会从此处推断

丢弃值(Throwing values away)

下划线 _ 是一个特殊的"无名"标识符,用于丢弃某个值:

rust 复制代码
// 这什么都不做,因为 42 是常量
let _ = 42;

// 调用函数但丢弃返回值
let _ = get_thing();

_ 开头的名称仍是正常变量,只是编译器不会警告"未使用":

rust 复制代码
// 我们以后可能会用到,先消除未使用警告
let _x = 42;

变量遮蔽(Shadowing)

允许用同一个名称引入新的绑定,这就是 shadowing

rust 复制代码
let x = 13;
let x = x + 3;   // 现在 x 是 16

// 此后的 x 指代第二个绑定,第一个 x 仍然存在(离开作用域时会被 drop),但无法再访问

元组(Tuples)

Rust 的元组是"固定长度、元素类型可以不同的集合"。

rust 复制代码
let pair = ('a', 17);
println!("{:?}", pair.0); // 'a'
println!("{:?}", pair.1); // 17

显式标注类型:

rust 复制代码
let pair: (char, i32) = ('a', 17);

解构元组(Destructuring)

赋值时可以直接解构:

rust 复制代码
let (some_char, some_int) = ('a', 17);
// 现在 some_char 是 'a',some_int 是 17

函数返回元组时特别实用:

rust 复制代码
let (left, right) = slice.split_at(middle);

可以用 _ 丢弃部分值:

rust 复制代码
let (_, right) = slice.split_at(middle);

语句(Statements)

分号 ; 表示语句结束:

rust 复制代码
let x = 3;
let y = 5;
let z = y + x;

语句可以跨多行:

rust 复制代码
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
    .iter()
    .map(|x| x + 3)
    .fold(0, |x, y| x + y);

(后面会解释这些方法的具体含义)

函数(Functions)

fn 关键字声明函数。

无返回值(void)函数:

rust 复制代码
fn greet() {
    println!("Hi there!");
}

带返回值的函数,返回类型用 -> 表示:

rust 复制代码
fn fair_dice_roll() -> i32 {
    4
}

代码块(Blocks)

一对大括号 {} 形成一个块,有自己的作用域:

rust 复制代码
fn main() {
    let x = "out";
    {
        let x = "in";   // 这是另一个 x
        println!("{}", x); // 打印 "in"
    }
    println!("{}", x); // 打印 "out"
}

块也是表达式(Blocks are expressions)

块可以求值产生一个值:

rust 复制代码
// 这两种写法等价
let x = 42;
let x = { 42 };

块内可以有多个语句,最后一个表达式(不带分号)是块的返回值(尾表达式):

rust 复制代码
let x = {
    let y = 1;
    let z = 2;
    y + z   // 没有分号 → 这是整个块的值
};

隐式返回

正因为块是表达式,函数末尾省略分号就相当于返回该值(推荐写法):

rust 复制代码
fn fair_dice_roll() -> i32 {
    4          // 隐式返回
}

// 等价于显式 return(较少使用)
fn fair_dice_roll() -> i32 {
    return 4;
}

一切皆表达式(Everything is an expression)

if 是表达式:

rust 复制代码
fn fair_dice_roll() -> i32 {
    if feeling_lucky {
        6
    } else {
        4
    }
}

match 也是表达式:

rust 复制代码
fn fair_dice_roll() -> i32 {
    match feeling_lucky {
        true => 6,
        false => 4,
    }
}

2024 Edition 补充if let 的临时值作用域在 2024 中有所收窄(临时值更早 drop),这让某些代码更安全,但对基础使用几乎无影响。


Field access and method calling(字段访问与方法调用)

点号 . 通常用于访问某个值的字段:

rust 复制代码
let a = (10, 20);
a.0; // 这就是 10

let amos = get_some_struct();
amos.nickname; // 这就是 "fasterthanlime"

或者在某个值上调用方法:

rust 复制代码
let nick = "fasterthanlime";
nick.len(); // 这就是 14

Modules, use syntax(模块与 use 语法)

双冒号 :: 用于操作命名空间(namespaces)。

示例中,std 是一个 crate (≈ 库),cmp 是一个 module (≈ 源文件),min 是一个函数:

rust 复制代码
let least = std::cmp::min(3, 8); // 这就是 3

use 指令可以把其他命名空间中的名称"引入当前作用域":

rust 复制代码
use std::cmp::min;
let least = min(7, 1); // 这就是 1

use 指令中,花括号有特殊含义(称为 globs):

rust 复制代码
// 以下几种写法都有效
use std::cmp::min;
use std::cmp::max;

use std::cmp::{min, max};

use std::{cmp::min, cmp::max};

通配符 * 可以引入某个命名空间中的所有符号(谨慎使用):

rust 复制代码
// 这会引入 min、max 以及很多其他东西
use std::cmp::*;

Types are namespaces too(类型也是命名空间)

类型本身也是命名空间,方法可以当作普通函数调用:

rust 复制代码
let x = "amos".len();        // 4
let x = str::len("amos");    // 同样是 4

The libstd prelude(标准库 prelude)

str 是基本类型,但许多非基本类型默认也在作用域中。

rust 复制代码
// Vec 是一个普通结构体,不是基本类型
let v = Vec::new();

// 这段代码完全等价,只是用了完整路径
let v = std::vec::Vec::new();

这是因为 Rust 在每个模块的开头自动插入了:

rust 复制代码
use std::prelude::v1::*;

(它重新导出了大量常用符号,例如 VecStringOptionResult 等)。

2024 Edition 提示:prelude 内容略有扩展(包含更多异步相关 trait),但对本文的基础教程没有影响。


Ownership(所有权)

Rust 的核心理念之一是所有权(ownership)。

每个值都有一个所有者 (owner)。当所有者离开作用域时,该值就会被自动 drop(销毁)。

rust 复制代码
fn main() {
    let s = String::from("hello"); // s 拥有这个 String
    // 在这里使用 s ...
} // s 离开作用域,String 的内存被自动释放

当你把值移动(move)给另一个变量时,原来的所有者就不再有效:

rust 复制代码
let s1 = String::from("hello");
let s2 = s1;        // s1 的所有权移动给了 s2

// println!("{}", s1); // 错误!s1 已经失效
println!("{}", s2); // 正确

2024 提示:移动语义和 drop 顺序在 2024 Edition 中保持稳定,临时值 drop 时机有时更早(更安全)。

Borrowing(借用)

有时你只想临时访问 一个值,而不想拿走它的所有权,这时就需要借用(borrowing)。

借用使用引用(reference)语法:

  • &T ------ 不可变借用(immutable borrow)
  • &mut T ------ 可变借用(mutable borrow)
rust 复制代码
fn calculate_length(s: &String) -> usize {
    s.len()
}

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1); // 借用 s1,不转移所有权
    println!("The length of '{}' is {}.", s1, len);
}

Borrowing rules(借用规则)

Rust 有严格的借用规则(借用检查器 borrow checker 负责检查):

  1. 在任意时刻,你要么拥有一个可变引用 (&mut T),要么拥有任意多个不可变引用 (&T),但不能同时拥有两者。
  2. 引用必须始终有效(不能悬垂指针)。
rust 复制代码
let mut s = String::from("hello");

let r1 = &s; // 不可变借用
let r2 = &s; // 另一个不可变借用,没问题

// let r3 = &mut s; // 错误!已有不可变借用时不能创建可变借用

println!("{}, {}", r1, r2);

可变借用示例:

rust 复制代码
fn change(s: &mut String) {
    s.push_str(", world");
}

fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{}", s); // "hello, world"
}

String vs &str(String 与 &str)

  • String ------ 堆上分配的可增长字符串,拥有所有权。
  • &str ------ 字符串切片(string slice),只是对 UTF-8 数据的引用,通常没有所有权。
rust 复制代码
let s = String::from("hello");     // String,拥有数据
let slice: &str = &s[0..2];        // &str,借用 s 的部分数据

let literal = "world";             // &str,指向静态字符串数据

函数参数中通常优先使用 &str(更灵活):

rust 复制代码
fn print_str(s: &str) {
    println!("{}", s);
}

// 可以传入 &String 或 &str
print_str(&s);
print_str(literal);

2024 提示 :字符串处理在 2024 中依然推荐使用 &str 作为参数类型,结合改进的 lifetime elision 规则,代码会更简洁。


Structs(结构体)

结构体(struct)是自定义数据类型,用 struct 关键字声明。

具名字段结构体(Named fields)

rust 复制代码
struct User {
    username: String,
    email: String,
    sign_in_count: u64,
    active: bool,
}

创建实例:

rust 复制代码
let user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

字段访问使用点号 .

rust 复制代码
println!("{} <{}>", user1.username, user1.email);

元组结构体(Tuple structs)

没有字段名,只有类型:

rust 复制代码
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

访问时仍用 .0.1 等:

rust 复制代码
println!("Black is ({}, {}, {})", black.0, black.1, black.2);

类单元结构体(Unit structs)

没有任何字段,常用于标记类型:

rust 复制代码
struct AlwaysEqual;

let subject = AlwaysEqual;

更新语法(Struct update syntax)

可以用 .. 从另一个结构体复制剩余字段(Rust 2024 中依然有效):

rust 复制代码
let user2 = User {
    email: String::from("another@example.com"),
    ..user1   // 复制 user1 中剩余字段(username、sign_in_count、active)
};

注意:user1username 等字段会发生移动 (move),因此 user1 之后不能再被使用(除非是 Copy 类型)。

Enums(枚举)

枚举(enum)是 Rust 中非常强大的特性,可以表示一个值有多种可能的"变体"(variants)。

rust 复制代码
enum IpAddr {
    V4(String),
    V6(String),
}

使用:

rust 复制代码
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));

更复杂的枚举

枚举的每个变体可以携带不同类型的数据,甚至是结构体:

rust 复制代码
enum Message {
    Quit,                          // 没有关联数据
    Move { x: i32, y: i32 },       // 具名字段
    Write(String),                 // 单个字符串
    ChangeColor(i32, i32, i32),    // 元组
}

枚举与模式匹配(Enums + match)

match 是 Rust 中处理枚举的最常用方式,它强制你处理所有可能的情况(穷尽性检查):

rust 复制代码
fn main() {
    let msg = Message::ChangeColor(0, 160, 255);

    match msg {
        Message::Quit => println!("The Quit variant has no data!"),
        Message::Move { x, y } => println!("Move to x={}, y={}", x, y),
        Message::Write(text) => println!("Text message: {}", text),
        Message::ChangeColor(r, g, b) => println!("Change color to R={}, G={}, B={}", r, g, b),
    }
}

2024 提示 :Rust 2024 的模式匹配更加灵活,支持 if let 链和更智能的临时值处理,但 match 的穷尽性检查仍然是核心安全特性。

if let 与 while let

有时你只关心枚举的某一个变体,这时 if let 非常方便:

rust 复制代码
if let Message::Write(text) = msg {
    println!("Text message: {}", text);
}

while let 用于循环处理:

rust 复制代码
let mut stack = vec![1, 2, 3];

while let Some(top) = stack.pop() {
    println!("{}", top);
}

Error handling(错误处理)

Rust 没有异常(exceptions)。错误用 Result<T, E> 类型处理。

rust 复制代码
enum Result<T, E> {
    Ok(T),
    Err(E),
}

示例函数:

rust 复制代码
fn get_user(id: u32) -> Result<User, String> {
    if id == 1 {
        Ok(User { /* ... */ })
    } else {
        Err(String::from("User not found"))
    }
}

? 运算符(The question mark operator)

? 是早期返回错误的语法糖。在返回 Result 的函数中使用:

rust 复制代码
fn get_user_name(id: u32) -> Result<String, String> {
    let user = get_user(id)?;
    Ok(user.username)
}

如果 get_user 返回 Err,则整个函数立即返回该错误;否则继续执行。

2024 提示? 运算符在 2024 Edition 中对 OptionResult 的支持更加一致,结合改进的 From trait 转换,错误处理代码更简洁。

The Option type

Option<T> 用于表示"可能有值,也可能没有值":

rust 复制代码
enum Option<T> {
    Some(T),
    None,
}

示例:

rust 复制代码
let some_number: Option<i32> = Some(42);
let absent_number: Option<i32> = None;

使用 if let 处理 Option

rust 复制代码
if let Some(number) = some_number {
    println!("We have a number: {}", number);
}

? 也可以用于返回 Option 的函数中。


Traits(特征)

Traits 描述多个类型可以共有的行为(类似其他语言的接口):

rust 复制代码
trait Signed {
    fn is_strictly_negative(self) -> bool;
}

Orphan rules(孤儿规则)

Rust 允许你实现:

  • 你的 trait 在任意类型上
  • 任意 trait 在你的类型上

不允许实现外部 trait 在外部类型上(防止冲突)。

以下是在我们自己的类型上实现 trait:

rust 复制代码
struct Number {
    odd: bool,
    value: i32,
}

impl Signed for Number {
    fn is_strictly_negative(self) -> bool {
        self.value < 0
    }
}

fn main() {
    let n = Number { odd: false, value: -44 };
    println!("{}", n.is_strictly_negative()); // 打印 "true"
}

在外部类型(例如原生 i32)上实现我们的 trait:

rust 复制代码
impl Signed for i32 {
    fn is_strictly_negative(self) -> bool {
        self < 0
    }
}

fn main() {
    let n: i32 = -44;
    println!("{}", n.is_strictly_negative()); // 打印 "true"
}

实现外部 trait 在我们自己的类型上(例如重载运算符):

rust 复制代码
impl std::ops::Neg for Number {
    type Output = Number;
    fn neg(self) -> Number {
        Number {
            value: -self.value,
            odd: self.odd,
        }
    }
}

fn main() {
    let n = Number { odd: true, value: 987 };
    let m = -n; // 因为实现了 Neg,所以可以这样写
    println!("{}", m.value); // 打印 "-987"
}

Self 类型

impl 块中,Self 指代当前正在实现的类型:

rust 复制代码
impl std::ops::Neg for Number {
    type Output = Self;
    fn neg(self) -> Self {
        Self {
            value: -self.value,
            odd: self.odd,
        }
    }
}

Marker traits(标记特征)

有些 trait 不提供方法,只作为"标记"------表示该类型支持某种行为。例如 Copy

rust 复制代码
fn main() {
    let a: i32 = 15;
    let b = a; // i32 是 Copy,所以这里是复制
    let c = a; // 再次复制
}

Number 默认不是 Copy,所以移动后不能再使用:

rust 复制代码
fn main() {
    let n = Number { odd: true, value: 51 };
    let m = n; // 移动
    // let o = n; // 错误:use of moved value
}

传递不可变引用则没有问题:

rust 复制代码
fn print_number(n: &Number) {
    println!(
        "{} number {}",
        if n.odd { "odd" } else { "even" },
        n.value
    );
}

fn main() {
    let n = Number { odd: true, value: 51 };
    print_number(&n);
    print_number(&n); // 可以多次借用
}

可变引用需要 mut

rust 复制代码
fn invert(n: &mut Number) {
    n.value = -n.value;
}

fn main() {
    let mut n = Number { odd: true, value: 51 };
    print_number(&n);
    invert(&mut n);
    print_number(&n);
}

Trait 方法接收者

trait 方法可以接收 self&self&mut self

rust 复制代码
impl std::clone::Clone for Number {
    fn clone(&self) -> Self {
        Self { ..*self }
    }
}

调用时会隐式借用:

rust 复制代码
let m = n.clone();                    // 等价于下面这行
let m = std::clone::Clone::clone(&n);

Copy 是 marker trait(无方法),但要求先实现 Clone

rust 复制代码
#[derive(Clone)] // 推荐使用 derive
struct Number { /* ... */ }

impl std::marker::Copy for Number {}

现在 Number 可以复制而非移动:

rust 复制代码
let m = n; // 复制
let o = n; // 再次复制

Deriving traits(派生特征)

常用 trait 可以使用 #[derive] 自动实现:

rust 复制代码
#[derive(Clone, Copy)]
struct Number {
    odd: bool,
    value: i32,
}

2024 提示derive 在 2024 Edition 中依然是推荐写法,许多标准 trait(如 DebugPartialEqEq)都支持自动派生。

Generics(泛型)

泛型函数

函数可以是泛型的:

rust 复制代码
fn foobar<T>(arg: T) {
    // 对 arg 做一些操作
}

可以有多个类型参数:

rust 复制代码
fn foobar<L, R>(left: L, right: R) {
    // 使用 left 和 right
}

类型参数约束(Trait bounds)

通常需要为泛型参数添加约束:

rust 复制代码
use std::fmt::Display;

fn print<T: Display>(value: T) {
    println!("value = {}", value);
}

或者使用 where 子句(更清晰,尤其约束复杂时):

rust 复制代码
fn print<T>(value: T) where T: Display {
    println!("value = {}", value);
}

多个约束:

rust 复制代码
use std::fmt::Debug;

fn compare<T>(left: T, right: T) where T: Debug + PartialEq {
    println!(
        "{:?} {} {:?}",
        left,
        if left == right { "==" } else { "!=" },
        right
    );
}

Lifetimes(生命周期)

生命周期是 Rust 借用检查器的核心概念,用于确保引用不会超过它指向的数据的存活时间。

最常见的生命周期注解是 'a

rust 复制代码
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

省略规则(Lifetime elision)

在很多简单情况下,Rust 会自动推断生命周期(elision):

rust 复制代码
// 这两个函数签名在大多数情况下等价
fn first_word(s: &str) -> &str;
fn first_word<'a>(s: &'a str) -> &'a str;

Rust 2024 Edition 重要更新 :Return Position impl Trait(RPIT)现在默认捕获所有 in-scope 的 lifetime 参数(与类型参数一致)。这让返回 impl Trait 的函数在涉及引用的场景下更安全、更直观。

示例(2024 风格):

rust 复制代码
// 在 Rust 2024 中,这通常能自动捕获需要的 lifetime
fn make_ref<'a>(x: &'a i32) -> impl std::fmt::Display + 'a {
    x
}

如果需要精确控制捕获哪些参数,可以使用 use<..> 语法(所有 Edition 均支持):

rust 复制代码
fn foo<'a, T>(x: &'a T) -> impl Trait + use<'a, T> { ... }

这让代码在 2024 Edition 下更简洁,同时避免了旧版中常见的 "Captures trick" 或 '_ hack。

结尾:继续前进吧!

恭喜你!通过这篇文章,你已经了解了 Rust 中最核心的概念:变量绑定、所有权、借用、结构体、枚举、模式匹配、错误处理、trait、泛型和生命周期。

这些知识足以让你开始阅读真实的 Rust 代码,并逐步编写自己的程序。

2024 Edition 进阶建议

  • 使用 cargo new 创建项目,并设置 edition = "2024"
  • 多阅读标准库文档和高质量 crate(如 tokioserde)。
  • 练习编写带 trait bounds 的泛型函数和返回 impl Trait 的 API。
  • 当遇到 borrow checker 错误时,不要害怕------这是 Rust 在保护你。

Rust 是一门注重安全和性能的语言,学习曲线虽然陡峭,但回报巨大。

继续写代码、多读源码,你会越来越喜欢它。

相关推荐
REDcker2 小时前
ARMv8、AArch64 与 arm64:命名与体系结构要点
开发语言·c++·arm
Source.Liu3 小时前
【office2pdf】office2pdf 纯 Rust 实现的 Office 转 PDF 库
rust·pdf·office2pdf
中国胖子风清扬3 小时前
实战:基于 Camunda 8 的复杂审批流程实战指南
java·spring boot·后端·spring·spring cloud·ai·maven
LXXgalaxy3 小时前
Uni-app 小程序页面跳转带参实战笔记(含对象传参与防坑)
开发语言·前端·javascript
oi..3 小时前
Flag和JavaScript document有关
开发语言·前端·javascript·经验分享·笔记·安全·网络安全
ZTLJQ3 小时前
数据的另一面:Python中NoSQL数据库完全解析
开发语言·python·nosql
烧饼Fighting3 小时前
java+vue推rtsp流实现视频播放(由javacv+ffmpg转为vlcj)
java·开发语言·音视频
紫丁香3 小时前
03-Flask请求上下文响应与错误处理机制深度解析
后端·python·flask
zb200641203 小时前
Spring Boot spring-boot-maven-plugin 参数配置详解
spring boot·后端·maven