Java利用構(gòu)建器模式重構(gòu)Excel導(dǎo)出工具類
引言:Excel導(dǎo)出的痛點(diǎn)與重構(gòu)價(jià)值
在Java企業(yè)級開發(fā)中,Excel導(dǎo)出功能幾乎成為業(yè)務(wù)系統(tǒng)的標(biāo)準(zhǔn)配置。從數(shù)據(jù)報(bào)表到業(yè)務(wù)分析,從用戶信息導(dǎo)出到財(cái)務(wù)數(shù)據(jù)歸檔,Excel作為數(shù)據(jù)交換的標(biāo)準(zhǔn)格式,在業(yè)務(wù)場景中扮演著不可或缺的角色。然而,在實(shí)際開發(fā)過程中,我們常常面臨諸多挑戰(zhàn):
- 參數(shù)爆炸:傳統(tǒng)方法需要傳遞大量參數(shù)(文件名、表頭、數(shù)據(jù)映射等),導(dǎo)致方法簽名冗長
- 代碼可讀性差:調(diào)用時(shí)參數(shù)順序依賴性強(qiáng),難以直觀理解每個(gè)參數(shù)含義
- 擴(kuò)展性受限:新增導(dǎo)出配置需修改方法簽名,破壞現(xiàn)有調(diào)用
- 樣式耦合:業(yè)務(wù)代碼與樣式配置混雜,維護(hù)困難
- 使用不一致:文件導(dǎo)出與Web導(dǎo)出API不統(tǒng)一,增加學(xué)習(xí)成本
本文將介紹如何通過構(gòu)建器模式和流暢接口設(shè)計(jì),重構(gòu)Excel導(dǎo)出工具類,實(shí)現(xiàn)API的優(yōu)雅封裝與調(diào)用。
重構(gòu)方案:構(gòu)建器模式的精妙運(yùn)用
核心設(shè)計(jì)思想
我們采用構(gòu)建器模式對導(dǎo)出參數(shù)進(jìn)行封裝,結(jié)合流暢接口設(shè)計(jì)實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用。這種設(shè)計(jì)帶來三大核心優(yōu)勢:
- 參數(shù)封裝:將所有配置項(xiàng)封裝在內(nèi)部Context對象中
- 鏈?zhǔn)秸{(diào)用:通過方法鏈實(shí)現(xiàn)自描述式API
- 默認(rèn)配置:為常用參數(shù)提供合理默認(rèn)值,簡化調(diào)用
完整工具類實(shí)現(xiàn)
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.metadata.style.WriteCellStyle; import com.alibaba.excel.write.metadata.style.WriteFont; import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URLEncoder; import java.time.YearMonth; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.Function; /** * 基于構(gòu)建器模式的Excel導(dǎo)出工具類 * 提供流暢API接口,支持Web導(dǎo)出和文件導(dǎo)出兩種方式 */ public class ExcelExporter { // 參數(shù)封裝內(nèi)部類 - 隱藏實(shí)現(xiàn)細(xì)節(jié) private static class ExportContext<T> { Object target; // 導(dǎo)出目標(biāo)(文件或輸出流) String sheetName = "Sheet1"; // 默認(rèn)工作表名稱 String mainTitle = "數(shù)據(jù)報(bào)表"; // 默認(rèn)主標(biāo)題 List<String> headers; // 列標(biāo)題 List<T> dataList; // 數(shù)據(jù)列表 Function<T, List<Object>> dataMapper; // 數(shù)據(jù)映射函數(shù) HorizontalCellStyleStrategy styleStrategy; // 樣式策略 boolean autoDateSuffix = true; // 是否自動添加日期后綴 int batchSize = 5000; // 分批寫入批次大小 } /** * 創(chuàng)建構(gòu)建器實(shí)例 - 泛型方法確保類型安全 * @param clazz 數(shù)據(jù)類型(用于類型推斷) * @return 構(gòu)建器對象 */ public static <T> Builder<T> builder(Class<T> clazz) { return new Builder<>(); } /** * 構(gòu)建器類 - 實(shí)現(xiàn)流暢API */ public static class Builder<T> { private final ExportContext<T> context = new ExportContext<>(); /** * 設(shè)置導(dǎo)出目標(biāo)為Web響應(yīng) * @param response HttpServletResponse對象 * @param baseFileName 基礎(chǔ)文件名(不含擴(kuò)展名) * @return 當(dāng)前構(gòu)建器 */ public Builder<T> toWeb(HttpServletResponse response, String baseFileName) { try { String fileName = buildFileName(baseFileName); setResponseHeaders(response, fileName); context.target = response.getOutputStream(); } catch (IOException e) { throw new RuntimeException("創(chuàng)建輸出流失敗", e); } return this; } /** * 設(shè)置導(dǎo)出目標(biāo)為文件系統(tǒng) * @param filePath 完整文件路徑(含文件名) * @return 當(dāng)前構(gòu)建器 */ public Builder<T> toFile(String filePath) { return toFile(null, filePath); } /** * 設(shè)置導(dǎo)出目標(biāo)為文件系統(tǒng)(指定目錄和基礎(chǔ)文件名) * @param directory 目錄路徑 * @param baseFileName 基礎(chǔ)文件名 * @return 當(dāng)前構(gòu)建器 */ public Builder<T> toFile(String directory, String baseFileName) { String fileName = buildFileName(baseFileName); String fullPath = (directory != null) ? directory + File.separator + fileName + ".xlsx" : fileName + ".xlsx"; ensureDirectoryExists(fullPath); context.target = new File(fullPath); return this; } // 確保目錄存在 private void ensureDirectoryExists(String fullPath) { File file = new File(fullPath); File parentDir = file.getParentFile(); if (parentDir != null && !parentDir.exists()) { parentDir.mkdirs(); } } // 其他配置方法(sheetName, mainTitle, headers等)... // 完整代碼參考前文實(shí)現(xiàn) /** * 執(zhí)行導(dǎo)出操作 - 核心入口 */ public void execute() { validateContext(); applyDefaultStyleIfNeeded(); doExport(context); } // 參數(shù)校驗(yàn)確保健壯性 private void validateContext() { if (context.target == null) { throw new IllegalStateException("導(dǎo)出目標(biāo)未設(shè)置"); } if (context.headers == null || context.headers.isEmpty()) { throw new IllegalStateException("列標(biāo)題未設(shè)置"); } if (context.dataMapper == null) { throw new IllegalStateException("數(shù)據(jù)映射函數(shù)未設(shè)置"); } } // 應(yīng)用默認(rèn)樣式 private void applyDefaultStyleIfNeeded() { if (context.styleStrategy == null) { context.styleStrategy = createDefaultStyleStrategy(); } } } // 獲取當(dāng)前年月字符串(yyyyMM格式) private static String getCurrentYearMonth() { return YearMonth.now().format(DateTimeFormatter.ofPattern("yyyyMM")); } // 設(shè)置Web響應(yīng)頭 private static void setResponseHeaders(HttpServletResponse response, String fileName) throws IOException { String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replace("+", "%20"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("UTF-8"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx"); } // 創(chuàng)建默認(rèn)樣式策略 - 專業(yè)美觀的默認(rèn)樣式 private static HorizontalCellStyleStrategy createDefaultStyleStrategy() { // 主標(biāo)題樣式 - 天藍(lán)色背景,16號加粗字體,居中顯示 WriteCellStyle titleStyle = new WriteCellStyle(); titleStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex()); titleStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); WriteFont titleFont = new WriteFont(); titleFont.setFontHeightInPoints((short) 16); titleFont.setBold(true); titleStyle.setWriteFont(titleFont); // 表頭樣式 - 淺黃色背景,12號加粗字體,居中顯示 WriteCellStyle headerStyle = new WriteCellStyle(); headerStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex()); headerStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); WriteFont headerFont = new WriteFont(); headerFont.setFontHeightInPoints((short) 12); headerFont.setBold(true); headerStyle.setWriteFont(headerFont); // 內(nèi)容樣式 - 左對齊,自動換行 WriteCellStyle contentStyle = new WriteCellStyle(); contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT); contentStyle.setWrapped(true); // 自動換行 return new HorizontalCellStyleStrategy(titleStyle, headerStyle, contentStyle); } // 執(zhí)行導(dǎo)出核心邏輯 private static <T> void doExport(ExportContext<T> context) { List<List<String>> headList = prepareHeaders(context.mainTitle, context.headers); List<List<Object>> data = prepareData(context.dataList, context.dataMapper); try (ExcelWriter excelWriter = createExcelWriter(context)) { WriteSheet writeSheet = EasyExcel.writerSheet(context.sheetName).build(); writeDataInBatches(excelWriter, writeSheet, data, context.batchSize); } } // 創(chuàng)建ExcelWriter實(shí)例 private static <T> ExcelWriter createExcelWriter(ExportContext<T> context) { return EasyExcel.write(context.target) .registerWriteHandler(context.styleStrategy) .head(prepareHeaders(context.mainTitle, context.headers)) .build(); } // 分批寫入數(shù)據(jù) - 優(yōu)化內(nèi)存使用 private static void writeDataInBatches(ExcelWriter excelWriter, WriteSheet writeSheet, List<List<Object>> data, int batchSize) { int total = data.size(); int pages = (int) Math.ceil((double) total / batchSize); for (int i = 0; i < pages; i++) { int fromIndex = i * batchSize; int toIndex = Math.min((i + 1) * batchSize, total); excelWriter.write(data.subList(fromIndex, toIndex), writeSheet); } } // 準(zhǔn)備表頭數(shù)據(jù) private static List<List<String>> prepareHeaders(String mainTitle, List<String> headers) { List<List<String>> headList = new ArrayList<>(); headList.add(Collections.singletonList(mainTitle)); // 主標(biāo)題(跨所有列) headList.add(new ArrayList<>(headers)); // 列標(biāo)題 return headList; } // 準(zhǔn)備表格數(shù)據(jù) private static <T> List<List<Object>> prepareData( List<T> dataList, Function<T, List<Object>> dataMapper) { if (dataList == null || dataList.isEmpty()) { return Collections.emptyList(); } List<List<Object>> result = new ArrayList<>(dataList.size()); for (T item : dataList) { result.add(dataMapper.apply(item)); } return result; } }
設(shè)計(jì)亮點(diǎn)深度解析
1. 流暢API設(shè)計(jì) - 優(yōu)雅的鏈?zhǔn)秸{(diào)用
重構(gòu)后的API采用自然語言式的鏈?zhǔn)秸{(diào)用,代碼即文檔:
// 清晰的導(dǎo)出流程:目標(biāo)→配置→數(shù)據(jù)→執(zhí)行 ExcelExporter.builder(User.class) .toWeb(response, "用戶報(bào)表") // 設(shè)置導(dǎo)出目標(biāo) .sheetName("用戶數(shù)據(jù)") // 配置工作表名 .mainTitle("2024年用戶分析報(bào)告") // 設(shè)置主標(biāo)題 .headers("ID", "姓名", "郵箱") // 設(shè)置列標(biāo)題 .data(users, user -> Arrays.asList( // 提供數(shù)據(jù)和映射 user.getId(), user.getName(), user.getEmail() )) .batchSize(2000) // 優(yōu)化大數(shù)據(jù)量處理 .execute(); // 執(zhí)行導(dǎo)出
這種設(shè)計(jì)使代碼具有自解釋性,即使不查文檔也能理解每個(gè)步驟的意圖。
2. 智能默認(rèn)值 - 開箱即用的體驗(yàn)
工具類為常用參數(shù)提供精心設(shè)計(jì)的默認(rèn)值:
- 工作表名稱:默認(rèn)為"Sheet1"
- 主標(biāo)題:默認(rèn)為"數(shù)據(jù)報(bào)表"
- 樣式策略:專業(yè)美觀的藍(lán)黃配色方案
- 日期后綴:自動添加年月標(biāo)識(如"Report_202405")
- 批處理大小:默認(rèn)5000行/批
這些默認(rèn)值經(jīng)過實(shí)踐檢驗(yàn),能滿足大部分業(yè)務(wù)場景需求,真正實(shí)現(xiàn)開箱即用。
3. 多目標(biāo)統(tǒng)一接口 - 一致的調(diào)用體驗(yàn)
工具類抽象了導(dǎo)出目標(biāo),提供統(tǒng)一的API:
// Web導(dǎo)出 - 直接輸出到HttpServletResponse .toWeb(response, "用戶報(bào)表") // 文件導(dǎo)出到指定目錄 .toFile("/reports", "月度銷售") // 文件導(dǎo)出到當(dāng)前目錄 .toFile("臨時(shí)報(bào)告")
這種設(shè)計(jì)消除了不同導(dǎo)出方式的學(xué)習(xí)成本,開發(fā)者只需關(guān)注業(yè)務(wù)邏輯。
4. 健壯性保障 - 防御式編程
工具類內(nèi)置多重保障機(jī)制:
private void validateContext() { // 必須參數(shù)校驗(yàn) if (context.target == null) throw ...; if (context.headers == null) throw ...; if (context.dataMapper == null) throw ...; // 數(shù)據(jù)空值保護(hù) if (dataList == null) return Collections.emptyList(); }
這些校驗(yàn)在execute()
方法入口處進(jìn)行,確保導(dǎo)出過程不會因參數(shù)缺失而意外終止。
使用場景示例
場景1:基礎(chǔ)用戶數(shù)據(jù)導(dǎo)出(Web)
@GetMapping("/export/users") public void exportUsers(HttpServletResponse response) { List<User> users = userService.findActiveUsers(); ExcelExporter.builder(User.class) .toWeb(response, "活躍用戶") .headers("ID", "用戶名", "注冊郵箱", "最后登錄時(shí)間") .data(users, user -> Arrays.asList( user.getId(), user.getUsername(), user.getEmail(), formatDateTime(user.getLastLogin()) )) .execute(); } // 日期格式化輔助方法 private String formatDateTime(LocalDateTime dateTime) { return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); }
場景2:銷售報(bào)表生成(文件導(dǎo)出)
public void generateDailySalesReport() { List<SalesRecord> records = salesService.getYesterdayRecords(); ExcelExporter.builder(SalesRecord.class) .toFile("/reports/sales", "日銷售報(bào)告") .withoutDateSuffix() // 禁用日期后綴 .sheetName("銷售明細(xì)") .mainTitle("昨日銷售匯總") .headers("銷售員", "產(chǎn)品", "數(shù)量", "單價(jià)", "總金額") .data(records, record -> Arrays.asList( record.getSalesperson(), record.getProductName(), record.getQuantity(), record.getUnitPrice(), record.getTotalAmount() )) .batchSize(10000) // 大數(shù)據(jù)量優(yōu)化 .execute(); }
場景3:財(cái)務(wù)數(shù)據(jù)導(dǎo)出(自定義樣式)
public void exportFinancialReport(HttpServletResponse response) { // 創(chuàng)建專業(yè)財(cái)務(wù)樣式 WriteCellStyle moneyStyle = createMoneyStyle(); WriteCellStyle headerStyle = createFinanceHeaderStyle(); List<FinancialData> data = financeService.getQuarterlyReport(); ExcelExporter.builder(FinancialData.class) .toWeb(response, "Q1財(cái)務(wù)報(bào)表") .headers("科目", "1月", "2月", "3月", "季度合計(jì)") .data(data, item -> Arrays.asList( item.getAccount(), item.getJanuary(), item.getFebruary(), item.getMarch(), item.getQuarterTotal() )) .customStyle( createFinanceTitleStyle(), headerStyle, moneyStyle ) .execute(); } // 創(chuàng)建貨幣樣式 private WriteCellStyle createMoneyStyle() { WriteCellStyle style = new WriteCellStyle(); style.setDataFormat((short) 4); // 貨幣格式 style.setHorizontalAlignment(HorizontalAlignment.RIGHT); WriteFont font = new WriteFont(); font.setColor(IndexedColors.DARK_GREEN.getIndex()); style.setWriteFont(font); return style; }
性能優(yōu)化策略
1. 分批次寫入 - 內(nèi)存控制
private static void writeDataInBatches(ExcelWriter excelWriter, WriteSheet writeSheet, List<List<Object>> data, int batchSize) { int total = data.size(); int pages = (int) Math.ceil((double) total / batchSize); for (int i = 0; i < pages; i++) { int fromIndex = i * batchSize; int toIndex = Math.min((i + 1) * batchSize, total); excelWriter.write(data.subList(fromIndex, toIndex), writeSheet); } }
這種分批處理機(jī)制確保即使導(dǎo)出百萬級數(shù)據(jù),內(nèi)存占用也能保持穩(wěn)定。
2. 樣式復(fù)用 - 提升性能
// 樣式對象池 public class StylePool { private static final Map<String, WriteCellStyle> pool = new ConcurrentHashMap<>(); public static WriteCellStyle getStyle(String key, Supplier<WriteCellStyle> creator) { return pool.computeIfAbsent(key, k -> creator.get()); } } // 使用樣式池 WriteCellStyle headerStyle = StylePool.getStyle("financeHeader", () -> { WriteCellStyle style = new WriteCellStyle(); // ... 樣式配置 return style; });
通過復(fù)用樣式對象,避免重復(fù)創(chuàng)建,顯著提升導(dǎo)出性能。
3. 異步導(dǎo)出 - 避免阻塞
@GetMapping("/export/large") public ResponseEntity<Void> exportLargeData() { CompletableFuture.runAsync(() -> { ExcelExporter.builder(Data.class) .toFile("/reports", "bigdata") .data(largeData, ...) .execute(); }, taskExecutor); return ResponseEntity.accepted() .header("Location", "/export/status/123") .build(); }
結(jié)合Spring的異步處理,避免大文件導(dǎo)出阻塞主線程。
擴(kuò)展功能實(shí)現(xiàn)
多級表頭支持
private List<List<String>> prepareMultiLevelHeaders() { List<List<String>> headList = new ArrayList<>(); // 第一級:主標(biāo)題(跨所有列) headList.add(Collections.singletonList("2024年度銷售分析報(bào)告")); // 第二級:分類標(biāo)題 headList.add(Arrays.asList("基本信息", "", "業(yè)績指標(biāo)")); // 第三級:詳細(xì)列名 headList.add(Arrays.asList("區(qū)域", "銷售員", "銷售額", "同比增長", "目標(biāo)達(dá)成率")); return headList; }
動態(tài)列寬調(diào)整
// 在customStyle后添加列寬策略 builder.customStyle(...) .registerColumnWidthHandler((sheet, cell, head, relativeRowIndex, isHead) -> { if (cell.getColumnIndex() == 2) { sheet.setColumnWidth(cell.getColumnIndex(), 20 * 256); // 20字符寬 } });
重構(gòu)前后對比分析
維度 | 傳統(tǒng)實(shí)現(xiàn) | 構(gòu)建器模式重構(gòu) |
---|---|---|
方法參數(shù) | 5-6個(gè)必要參數(shù) | 鏈?zhǔn)脚渲?,按需設(shè)置 |
代碼可讀性 | 參數(shù)順序依賴,可讀性差 | 自描述鏈?zhǔn)秸{(diào)用,清晰明了 |
擴(kuò)展性 | 新增參數(shù)需修改方法簽名 | 添加構(gòu)建器方法,不影響現(xiàn)有調(diào)用 |
默認(rèn)配置 | 需要多個(gè)重載方法 | 內(nèi)置智能默認(rèn)值 |
使用一致性 | Web/文件導(dǎo)出接口不同 | 統(tǒng)一流暢API |
異常處理 | 分散在各處 | 集中入口校驗(yàn) |
維護(hù)成本 | 高(修改影響大) | 低(內(nèi)部封裝) |
最佳實(shí)踐與總結(jié)
實(shí)施建議
- 樣式標(biāo)準(zhǔn)化:在企業(yè)內(nèi)部建立統(tǒng)一的樣式規(guī)范,通過
customStyle
實(shí)現(xiàn)復(fù)用 - 模板化配置:對常用導(dǎo)出場景創(chuàng)建配置模板
- 異步處理:大數(shù)據(jù)量導(dǎo)出務(wù)必使用異步機(jī)制
- 監(jiān)控集成:添加導(dǎo)出耗時(shí)和成功率監(jiān)控
- 權(quán)限控制:敏感數(shù)據(jù)導(dǎo)出添加權(quán)限驗(yàn)證
總結(jié)
通過構(gòu)建器模式重構(gòu)Excel導(dǎo)出工具類,我們實(shí)現(xiàn)了:
- 參數(shù)精簡:消除多參數(shù)問題,提升API整潔度
- 調(diào)用優(yōu)雅:鏈?zhǔn)紸PI使代碼更易讀寫
- 擴(kuò)展靈活:新增配置不影響現(xiàn)有代碼
- 性能優(yōu)化:分批處理支持大數(shù)據(jù)量
- 體驗(yàn)統(tǒng)一:Web/文件導(dǎo)出一致API
重構(gòu)后的工具類不僅解決了參數(shù)過多的問題,更通過流暢接口設(shè)計(jì)提升了開發(fā)體驗(yàn)。這種模式不僅適用于Excel導(dǎo)出,也可推廣到其他需要復(fù)雜配置的場景,如PDF導(dǎo)出、文件上傳等。
以上就是Java利用構(gòu)建器模式重構(gòu)Excel導(dǎo)出工具類的詳細(xì)內(nèi)容,更多關(guān)于Java重構(gòu)Excel導(dǎo)出工具類的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java 堆內(nèi)存分區(qū)的實(shí)現(xiàn)示例
本文主要介紹了Java 堆內(nèi)存分區(qū)的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2025-04-04Java中本地緩存的4種實(shí)現(xiàn)方式總結(jié)
這篇文章主要介紹了Java中本地緩存的4種實(shí)現(xiàn)方式,分別是基礎(chǔ)緩存實(shí)現(xiàn)、GuavaLoadingCache、SpringBoot整合Caffeine和JetCache,通過實(shí)例代碼,詳細(xì)講解了每種緩存技術(shù)的特點(diǎn)和使用方法,需要的朋友可以參考下2025-04-04使用@Value為靜態(tài)變量導(dǎo)入并使用導(dǎo)入的靜態(tài)變量進(jìn)行初始化方式
這篇文章主要介紹了使用@Value為靜態(tài)變量導(dǎo)入并使用導(dǎo)入的靜態(tài)變量進(jìn)行初始化方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-02-02IntelliJ IDEA 2019.2 x64的安裝、應(yīng)用與簡單配置(圖文)
這篇文章主要介紹了IntelliJ IDEA 2019.2 x64的安裝、應(yīng)用與簡單配置,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10解決idea中javaweb的mysql8.0.15配置問題
這篇文章主要介紹了idea中javaweb的mysql8.0.15配置問題 ,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05springboot logback如何從apollo配置中心讀取變量
這篇文章主要介紹了springboot logback如何從apollo配置中心讀取變量的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08spring?security?自定義Provider?如何實(shí)現(xiàn)多種認(rèn)證
這篇文章主要介紹了spring?security?自定義Provider實(shí)現(xiàn)多種認(rèn)證方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12JavaWeb 中Cookie實(shí)現(xiàn)記住密碼的功能示例
cookie是一種WEB服務(wù)器通過瀏覽器在訪問者的硬盤上存儲信息的手段。Cookie的目的就是為用戶帶來方便,為網(wǎng)站帶來增值。這篇文章主要介紹了JavaWeb 中Cookie實(shí)現(xiàn)記住密碼的功能示例,需要的朋友可以參考下2017-06-06深入淺析ArrayList 和 LinkedList的執(zhí)行效率比較
這篇文章主要介紹了ArrayList 和 LinkedList的執(zhí)行效率比較的相關(guān)資料,需要的朋友可以參考下2017-08-08