別再自己瞎寫(xiě)工具類(lèi)了,SpringBoot內(nèi)置工具類(lèi)應(yīng)有盡有,建議收藏??!
兄弟們,大家是不是也經(jīng)常半夜三更還在調(diào)試自己寫(xiě)的工具類(lèi)?"為什么這個(gè)字符串判空又出問(wèn)題了?"" 這個(gè)日期轉(zhuǎn)換怎么總是報(bào)格式化異常?""為什么我寫(xiě)的集合工具類(lèi)在高并發(fā)下會(huì)翻車(chē)?" 醒醒兄弟,別再重復(fù)造輪子了!SpringBoot 早就為我們準(zhǔn)備了一整套 "瑞士軍刀" 級(jí)別的工具類(lèi),今天咱們就來(lái)好好盤(pán)點(diǎn)一下這些能讓你少掉頭發(fā)的寶藏工具。
字符串處理:StringUtils 的正確打開(kāi)方式
首先咱們來(lái)聊聊最常用的字符串處理。我敢打賭,每個(gè) Java 開(kāi)發(fā)者都寫(xiě)過(guò)類(lèi)似if (str == null || str.length() == 0)這樣的代碼。但自從有了 Spring 的StringUtils,這些代碼都可以扔進(jìn)垃圾桶了。
Spring 的StringUtils位于org.springframework.util包下,這個(gè)工具類(lèi)簡(jiǎn)直是字符串處理的救星。最常用的就是isEmpty()和isBlank()方法,這倆兄弟的區(qū)別就像區(qū)分咖啡加沒(méi)加糖一樣重要:
// 判空:當(dāng)字符串為null或者長(zhǎng)度為0時(shí)返回true
StringUtils.isEmpty(""); // true
StringUtils.isEmpty(null); // true
StringUtils.isEmpty(" "); // false(注意這里有空格)
// 判空白:比isEmpty更嚴(yán)格,連續(xù)空格也會(huì)被判為空白
StringUtils.isBlank(""); // true
StringUtils.isBlank(null); // true
StringUtils.isBlank(" "); // true(空格也會(huì)被判為空白)
StringUtils.isBlank("  \t\n"); // true(制表符、換行符也不行)
StringUtils.isBlank(" hello "); // false(有實(shí)際內(nèi)容)是不是瞬間明白為什么自己的表單驗(yàn)證總是出問(wèn)題了?用isBlank()處理用戶輸入才是正確選擇,畢竟誰(shuí)也不能保證用戶不會(huì)輸入一串空格來(lái)?yè)v亂。還有個(gè)超實(shí)用的方法trimWhitespace(),能幫你去除字符串前后的空白字符,包括那些看不見(jiàn)的制表符和換行符:
String messyStr = "  \tHello SpringBoot!\n  ";
String cleanStr = StringUtils.trimWhitespace(messyStr);
System.out.println(cleanStr); // 輸出"Hello SpringBoot!"對(duì)于字符串拼接,StringUtils也有妙招。collectionToDelimitedString()方法能把集合轉(zhuǎn)換成用指定分隔符連接的字符串,再也不用手動(dòng)寫(xiě)循環(huán)拼接了:
List<String> fruits = Arrays.asList("apple", "banana", "orange");
String fruitStr = StringUtils.collectionToDelimitedString(fruits, ", ");
System.out.println(fruitStr); // 輸出"apple, banana, orange"這里有個(gè)冷知識(shí):StringUtils還能判斷字符串是否以某個(gè)前綴開(kāi)頭,并且忽略大小寫(xiě)!這在處理 HTTP 請(qǐng)求頭或者文件路徑時(shí)特別有用:
// 第三個(gè)參數(shù)true表示忽略大小寫(xiě)
boolean startsWith = StringUtils.startsWithIgnoreCase("HelloWorld.txt", "hello");
System.out.println(startsWith); // 輸出true集合操作:CollectionUtils 讓你的集合飛起來(lái)
說(shuō)完字符串,咱們來(lái)看看集合操作。Java 的集合框架雖然強(qiáng)大,但用起來(lái)總有些地方讓人不爽。比如判斷集合是否為空,每次都要寫(xiě)if (list == null || list.isEmpty()),煩不煩?好在 Spring 的CollectionUtils能幫我們解決這些痛點(diǎn)。
首先是最常用的isEmpty()方法,它能完美處理 null 值,再也不用擔(dān)心空指針異常:
List<String> nullList = null;
List<String> emptyList = new ArrayList<>();
List<String> hasElements = Arrays.asList("a", "b");
CollectionUtils.isEmpty(nullList); // true
CollectionUtils.isEmpty(emptyList); // true
CollectionUtils.isEmpty(hasElements); // false注意哦,Spring 的CollectionUtils和 Apache Commons 的CollectionUtils不是一回事。Spring 版本的更輕量,而且和 Spring 框架的兼容性更好。不過(guò)要注意,CollectionUtils本身的方法是線程安全的,但它操作的集合是否線程安全就要看你傳入的集合類(lèi)型了。如果需要線程安全的集合操作,可以用CollectionUtils結(jié)合SynchronizedList:
// 創(chuàng)建線程安全的列表
List<String> safeList = CollectionUtils.synchronizedList(new ArrayList<>());
// 之后對(duì)safeList的操作就是線程安全的了CollectionUtils還提供了一些集合運(yùn)算的方法,比如求兩個(gè)集合的交集:
List<String> list1 = Arrays.asList("a", "b", "c");
List<String> list2 = Arrays.asList("b", "c", "d");
// 求交集
List<String> intersection = new ArrayList<>(list1);
CollectionUtils.retainAll(intersection, list2);
System.out.println(intersection); // 輸出[b, c]還有判斷一個(gè)集合是否包含另一個(gè)集合的所有元素的containsAll()方法:
List<String> parent = Arrays.asList("a", "b", "c", "d");
List<String> child = Arrays.asList("b", "c");
boolean isSubset = CollectionUtils.containsAll(parent, child);
System.out.println(isSubset); // 輸出true對(duì)于 Map 集合,Spring 也有專門(mén)的MapUtils工具類(lèi),用法和CollectionUtils類(lèi)似,能幫你輕松處理 Map 的判空、獲取值等操作:
Map<String, Object> map = new HashMap<>();
map.put("name", "SpringBoot");
map.put("version", "2.7.0");
// 安全獲取值,如果key不存在返回默認(rèn)值
String name = MapUtils.getString(map, "name", "defaultName");
int version = MapUtils.getInteger(map, "version", 0);
String notExist = MapUtils.getString(map, "notExist", "default");反射工具:ReflectionUtils 讓反射不再頭疼
反射是 Java 的強(qiáng)大特性,但原生的反射 API 用起來(lái)簡(jiǎn)直讓人抓狂。獲取方法、設(shè)置字段可訪問(wèn)性、處理異常... 代碼又長(zhǎng)又難看。好在 Spring 的ReflectionUtils把這些臟活累活都包辦了,讓反射操作變得優(yōu)雅起來(lái)。
ReflectionUtils最常用的功能就是獲取類(lèi)的方法和字段。比如我們想獲取一個(gè)類(lèi)的某個(gè)方法:
class User {
    private String name;
    privateint age;
    
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    
    private void privateMethod() {
        System.out.println("這是私有方法");
    }
}
// 獲取public方法
Method setNameMethod = ReflectionUtils.findMethod(User.class, "setName", String.class);
// 獲取私有方法
Method privateMethod = ReflectionUtils.findMethod(User.class, "privateMethod");獲取字段也一樣簡(jiǎn)單:
// 獲取字段
Field nameField = ReflectionUtils.findField(User.class, "name");拿到方法和字段后,ReflectionUtils還能幫我們調(diào)用方法和設(shè)置字段值,連處理private成員的權(quán)限問(wèn)題都幫我們搞定了:
User user = new User();
// 調(diào)用方法
ReflectionUtils.invokeMethod(setNameMethod, user, "張三");
System.out.println(user.getName()); // 輸出"張三"
// 訪問(wèn)私有方法
ReflectionUtils.makeAccessible(privateMethod); // 確保私有方法可訪問(wèn)
ReflectionUtils.invokeMethod(privateMethod, user); // 調(diào)用私有方法
// 設(shè)置字段值(即使是private字段)
ReflectionUtils.makeAccessible(nameField);
ReflectionUtils.setField(nameField, user, "李四");
System.out.println(user.getName()); // 輸出"李四"ReflectionUtils還能遍歷類(lèi)的所有方法和字段,這在做框架開(kāi)發(fā)或者 AOP 編程時(shí)特別有用:
// 遍歷所有方法
ReflectionUtils.doWithMethods(User.class, method -> {
    System.out.println("方法名:" + method.getName());
});
// 遍歷所有字段
ReflectionUtils.doWithFields(User.class, field -> {
    System.out.println("字段名:" + field.getName());
});如果你只想處理特定的方法或字段,還可以加個(gè)過(guò)濾器:
// 只處理public方法
ReflectionUtils.doWithMethods(User.class, 
    method -> System.out.println("public方法:" + method.getName()),
    method -> Modifier.isPublic(method.getModifiers()) // 過(guò)濾器
);這里有個(gè)小技巧:ReflectionUtils的異常處理很巧妙,它會(huì)把 checked 異常轉(zhuǎn)換成 unchecked 異常,所以我們不用寫(xiě)煩人的 try-catch 塊。但這也意味著你要更小心地處理可能出現(xiàn)的異常情況。
IO 操作:FileCopyUtils 解放你的雙手
Java 的 IO 操作向來(lái)以繁瑣著稱,各種流的創(chuàng)建、關(guān)閉、異常處理能把人逼瘋。好在 Spring 的FileCopyUtils能幫我們簡(jiǎn)化這些操作,讓文件復(fù)制變得像喝水一樣簡(jiǎn)單。
FileCopyUtils提供了一系列靜態(tài)方法,支持各種數(shù)據(jù)源之間的復(fù)制:
// 從輸入流復(fù)制到輸出流
try (InputStream in = new FileInputStream("source.txt");
     OutputStream out = new FileOutputStream("target.txt")) {
    FileCopyUtils.copy(in, out);
} catch (IOException e) {
    // 處理異常
}
// 從字節(jié)數(shù)組復(fù)制到輸出流
byte[] data = "Hello Spring".getBytes();
try (OutputStream out = new FileOutputStream("output.txt")) {
    FileCopyUtils.copy(data, out);
} catch (IOException e) {
    // 處理異常
}
// 從文件復(fù)制到文件
File source = new File("source.txt");
File target = new File("target.txt");
try {
    FileCopyUtils.copy(source, target);
} catch (IOException e) {
    // 處理異常
}FileCopyUtils最貼心的地方是它會(huì)幫我們處理緩沖區(qū),默認(rèn)使用 4096 字節(jié)的緩沖區(qū),比自己手動(dòng)設(shè)置緩沖區(qū)高效多了。而且它的方法都會(huì)確保數(shù)據(jù)被完全復(fù)制,不用擔(dān)心只復(fù)制了部分?jǐn)?shù)據(jù)。除了復(fù)制,F(xiàn)ileCopyUtils還能幫我們把文件內(nèi)容讀到字節(jié)數(shù)組或字符串中:
// 把文件內(nèi)容讀到字節(jié)數(shù)組
try {
    byte[] content = FileCopyUtils.copyToByteArray(new File("test.txt"));
    System.out.println("文件大?。? + content.length + "字節(jié)");
} catch (IOException e) {
    // 處理異常
}
// 把輸入流內(nèi)容讀到字符串
try (InputStream in = new FileInputStream("test.txt")) {
    String content = FileCopyUtils.copyToString(new InputStreamReader(in, StandardCharsets.UTF_8));
    System.out.println("文件內(nèi)容:" + content);
} catch (IOException e) {
    // 處理異常
}這里有個(gè)性能小貼士:FileCopyUtils的所有方法都是一次性讀取全部?jī)?nèi)容到內(nèi)存中的,所以不適合處理超大文件。對(duì)于大文件操作,還是需要手動(dòng)控制緩沖區(qū)和讀取次數(shù)。但對(duì)于日常開(kāi)發(fā)中大多數(shù)中小型文件的處理,F(xiàn)ileCopyUtils絕對(duì)能讓你事半功倍。
AOP 工具:AopUtils 看透代理的本質(zhì)
在 Spring 中使用 AOP 時(shí),我們經(jīng)常會(huì)遇到代理對(duì)象的問(wèn)題。有時(shí)候你以為拿到的是真實(shí)對(duì)象,其實(shí)是個(gè)代理;有時(shí)候你想對(duì)代理對(duì)象做些特殊處理,卻不知道從何下手。這時(shí)候AopUtils就能幫你看透代理的本質(zhì)。
首先,AopUtils能幫你判斷一個(gè)對(duì)象是不是代理對(duì)象,以及是哪種類(lèi)型的代理:
@Service
publicclass UserService {
    // 業(yè)務(wù)方法...
}
// 在使用的地方
@Autowired
private UserService userService;
// 判斷是否是代理對(duì)象
boolean isProxy = AopUtils.isAopProxy(userService);
// 判斷是否是JDK動(dòng)態(tài)代理
boolean isJdkProxy = AopUtils.isJdkDynamicProxy(userService);
// 判斷是否是CGLIB代理
boolean isCglibProxy = AopUtils.isCglibProxy(userService);這個(gè)功能在調(diào)試的時(shí)候特別有用。比如當(dāng)你發(fā)現(xiàn)事務(wù)沒(méi)生效,或者 AOP 切面沒(méi)執(zhí)行時(shí),很可能就是因?yàn)槟阒苯?new 了對(duì)象而沒(méi)有使用 Spring 代理的對(duì)象。這時(shí)候用AopUtils判斷一下就能快速定位問(wèn)題。AopUtils還能幫你獲取代理對(duì)象背后的真實(shí)目標(biāo)對(duì)象類(lèi)型:
// 獲取目標(biāo)對(duì)象的Class
Class<?> targetClass = AopUtils.getTargetClass(userService);
System.out.println("真實(shí)對(duì)象類(lèi)型:" + targetClass.getName());在編寫(xiě)切面的時(shí)候,AopUtils的canApply()方法非常有用,它能判斷某個(gè)切面是否適用于某個(gè)類(lèi):
// 定義一個(gè)切面點(diǎn)
Pointcut pointcut = new AspectJExpressionPointcut("execution(* com.example.service.*.*(..))");
// 判斷這個(gè)切面是否適用于UserService
boolean applicable = AopUtils.canApply(pointcut, UserService.class);
System.out.println("切面是否適用:" + applicable);AopUtils還有個(gè)高級(jí)用法是getMostSpecificMethod(),它能幫你找到接口方法在實(shí)現(xiàn)類(lèi)中的具體實(shí)現(xiàn)方法。這在處理基于接口的 JDK 動(dòng)態(tài)代理時(shí)特別有用:
// 假設(shè)UserService實(shí)現(xiàn)了IUserService接口
Method interfaceMethod = IUserService.class.getMethod("getUser", Long.class);
// 找到在UserService中的具體實(shí)現(xiàn)方法
Method targetMethod = AopUtils.getMostSpecificMethod(interfaceMethod, UserService.class);理解代理機(jī)制對(duì)于 Spring 開(kāi)發(fā)者來(lái)說(shuō)至關(guān)重要,而AopUtils就是你探索代理世界的放大鏡。熟練使用這些方法,能讓你在 AOP 編程中少走很多彎路。
日期處理:SpringBoot 的日期時(shí)間解決方案
日期時(shí)間處理一直是 Java 開(kāi)發(fā)中的老大難問(wèn)題。雖然 Spring 沒(méi)有專門(mén)的日期工具類(lèi),但結(jié)合 Spring 生態(tài)和 Jackson 的支持,我們依然能優(yōu)雅地處理日期時(shí)間。
首先,Spring 的ObjectUtils提供了一些基礎(chǔ)的空值判斷方法,在處理日期時(shí)非常有用:
// 判斷日期是否為空
LocalDateTime date = null;
boolean isEmpty = ObjectUtils.isEmpty(date); // true在 SpringBoot 中,最常用的日期處理方式是通過(guò) Jackson 進(jìn)行 JSON 序列化和反序列化配置。我們可以自定義日期序列化器來(lái)統(tǒng)一處理日期格式:
/**
 * 自定義LocalDateTime序列化器,將日期轉(zhuǎn)換為時(shí)間戳
 */
public class LocalDateTimeToTimestampSerializer extends JsonSerializer<LocalDateTime> {
    @Override
    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) 
            throws IOException {
        if (value != null) {
            long timestamp = value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
            gen.writeNumber(timestamp);
        }
    }
}然后在實(shí)體類(lèi)中使用這個(gè)序列化器:
public class Order {
    private Long id;
    private String orderNo;
    
    // 使用自定義序列化器
    @JsonSerialize(using = LocalDateTimeToTimestampSerializer.class)
    private LocalDateTime createTime;
    
    // getter和setter...
}這樣,當(dāng)我們返回 JSON 響應(yīng)時(shí),createTime字段就會(huì)自動(dòng)轉(zhuǎn)換為時(shí)間戳,前端處理起來(lái)非常方便。如果需要支持多種日期格式的反序列化,我們可以創(chuàng)建一個(gè)通用的日期轉(zhuǎn)換器:
/**
 * 通用日期轉(zhuǎn)換工具類(lèi)
 */
publicclass DateUtil {
    // 支持的日期格式
    privatestaticfinalList<DateTimeFormatter> FORMATTERS = Arrays.asList(
        DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"),
        DateTimeFormatter.ofPattern("yyyy-MM-dd"),
        DateTimeFormatter.ISO_LOCAL_DATE_TIME,
        DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
    );
    
    /**
     * 嘗試用多種格式解析日期字符串
     */
    publicstatic LocalDateTime parse(String dateStr) {
        if (ObjectUtils.isEmpty(dateStr)) {
            returnnull;
        }
        
        for (DateTimeFormatter formatter : FORMATTERS) {
            try {
                return LocalDateTime.parse(dateStr, formatter);
            } catch (DateTimeParseException e) {
                // 解析失敗就嘗試下一種格式
                continue;
            }
        }
        
        // 如果所有格式都失敗,拋出異常
        thrownew IllegalArgumentException("無(wú)法解析日期字符串: " + dateStr);
    }
}在這個(gè)工具類(lèi)中,我們用到了 Spring 的ObjectUtils.isEmpty()來(lái)處理空值,這比直接用== null更加安全,因?yàn)樗€能處理空字符串等情況。SpringBoot 還支持通過(guò)配置文件來(lái)統(tǒng)一設(shè)置日期格式,這樣就不用每個(gè)字段都加注解了:
# application.yml
spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8這個(gè)配置會(huì)讓 Jackson 在序列化日期時(shí)使用指定的格式,對(duì)于java.util.Date、java.time.LocalDateTime等類(lèi)型都有效。
其他實(shí)用工具類(lèi)
除了上面介紹的這些 "明星工具類(lèi)",SpringBoot 中還有一些不太起眼但非常實(shí)用的工具類(lèi),咱們也來(lái)認(rèn)識(shí)一下。
首先是ClassUtils,這個(gè)工具類(lèi)在處理類(lèi)加載、類(lèi)信息相關(guān)操作時(shí)非常有用:
// 獲取類(lèi)的簡(jiǎn)單名稱(不帶包名)
String simpleName = ClassUtils.getShortName(UserService.class); // "UserService"
// 判斷一個(gè)類(lèi)是否是另一個(gè)類(lèi)的子類(lèi)或?qū)崿F(xiàn)類(lèi)
boolean isAssignable = ClassUtils.isAssignable(ArrayList.class, List.class); // true
// 獲取類(lèi)的默認(rèn)類(lèi)加載器
ClassLoader classLoader = ClassUtils.getDefaultClassLoader();然后是Assert斷言工具類(lèi),它能讓你的參數(shù)校驗(yàn)代碼更加簡(jiǎn)潔:
public void createUser(User user) {
    // 斷言用戶不為null
    Assert.notNull(user, "用戶對(duì)象不能為空");
    
    // 斷言用戶名不為空
    Assert.hasText(user.getName(), "用戶名不能為空");
    
    // 斷言年齡大于0
    Assert.isTrue(user.getAge() > 0, "年齡必須大于0");
    
    // 斷言集合不為空
    Assert.notEmpty(user.getRoles(), "用戶至少要有一個(gè)角色");
}如果斷言失敗,Assert會(huì)拋出IllegalArgumentException或IllegalStateException,比手動(dòng)寫(xiě) if-else 判斷清爽多了。還有NumberUtils,專門(mén)處理數(shù)字相關(guān)的轉(zhuǎn)換和判斷:
// 安全地將字符串轉(zhuǎn)換為整數(shù)
Integer num = NumberUtils.parseNumber("123", Integer.class);
// 判斷字符串是否是數(shù)字
boolean isNumber = NumberUtils.isNumber("123.45"); // true
// 數(shù)字區(qū)間判斷
boolean inRange = NumberUtils.isCreatable("100") && NumberUtils.toInt("100") < 200;對(duì)于 UUID 生成,Spring 也提供了UUIDUtils(在較新版本中被java.util.UUID替代,但思路類(lèi)似):
// 生成隨機(jī)UUID
String uuid = UUID.randomUUID().toString();
// 去除橫杠
String simpleUuid = uuid.replaceAll("-", "");最后不得不提的是HttpStatus和MediaType這樣的常量工具類(lèi),在編寫(xiě) Controller 時(shí)非常有用:
@GetMapping("/users")
public ResponseEntity<List<User>> getUsers() {
    List<User> users = userService.findAll();
    // 使用HttpStatus常量
    return new ResponseEntity<>(users, HttpStatus.OK);
}
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) {
    // 處理文件上傳...
    return ResponseEntity.ok()
        .contentType(MediaType.TEXT_PLAIN) // 使用MediaType常量
        .body("上傳成功");
}工具類(lèi)的最佳實(shí)踐
說(shuō)了這么多工具類(lèi),最后咱們來(lái)聊聊使用工具類(lèi)的一些最佳實(shí)踐。畢竟工具再好,用不好也會(huì)出問(wèn)題。
首先,要避免過(guò)度使用工具類(lèi)。雖然工具類(lèi)很方便,但并不是所有場(chǎng)景都需要用。比如StringUtils.isEmpty()雖然好用,但在某些明確不需要處理 null 的場(chǎng)景下,直接用str.isEmpty()可能更高效。
其次,要注意工具類(lèi)的包路徑。Spring 的工具類(lèi)主要集中在org.springframework.util包下,而 SpringBoot 的自動(dòng)配置相關(guān)工具在org.springframework.boot.util包下。了解這個(gè)規(guī)律,你就能更快地找到需要的工具類(lèi)。
再者,要養(yǎng)成查看源碼的習(xí)慣。Spring 的工具類(lèi)源碼都寫(xiě)得非常規(guī)范,通過(guò)閱讀源碼不僅能了解工具類(lèi)的實(shí)現(xiàn)原理,還能學(xué)到很多編程技巧。比如FileCopyUtils的緩沖區(qū)處理、ReflectionUtils的異常處理等,都是非常好的學(xué)習(xí)材料。
另外,要注意工具類(lèi)的版本兼容性。雖然 Spring 的兼容性做得很好,但不同版本之間還是可能有細(xì)微差別。如果你的項(xiàng)目需要升級(jí) Spring 版本,最好檢查一下用到的工具類(lèi)是否有變化。
最后,不要局限于 Spring 提供的工具類(lèi)。Spring 的工具類(lèi)主要解決通用問(wèn)題,對(duì)于特定領(lǐng)域的問(wèn)題,可能需要結(jié)合其他庫(kù)的工具類(lèi)使用,比如 Apache Commons、Google Guava 等。記住,適合當(dāng)前場(chǎng)景的工具才是最好的工具。
總結(jié)
看到這里,相信你已經(jīng)被 SpringBoot 這些強(qiáng)大的工具類(lèi)圈粉了吧!從字符串處理到集合操作,從反射工具到 IO 操作,再到 AOP 和日期處理,SpringBoot 幾乎為我們提供了開(kāi)發(fā)中需要的所有基礎(chǔ)工具類(lèi)。
這些工具類(lèi)不僅能幫我們減少重復(fù)代碼,還能提高代碼質(zhì)量和性能。畢竟這些工具類(lèi)都是經(jīng)過(guò) Spring 團(tuán)隊(duì)精心設(shè)計(jì)和廣泛測(cè)試的,比我們自己臨時(shí)寫(xiě)的工具類(lèi)要可靠得多。
下次再遇到需要工具類(lèi)的場(chǎng)景,先別急著自己動(dòng)手寫(xiě),不妨先看看 SpringBoot 有沒(méi)有提供現(xiàn)成的工具。相信我,掌握這些工具類(lèi),能讓你少寫(xiě)很多代碼,少踩很多坑,開(kāi)發(fā)效率大大提升!















 
 
 








 
 
 
 