EasyExcel自定義下拉注解的三種實(shí)現(xiàn)方式總結(jié)
一、簡(jiǎn)介
在使用EasyExcel設(shè)置下拉數(shù)據(jù)時(shí),每次都要?jiǎng)?chuàng)建一個(gè)SheetWriteHandler組件確實(shí)比較繁瑣。為了優(yōu)化這個(gè)過程,我們可以通過自定義注解來簡(jiǎn)化操作,使得只需要在需要添加下拉數(shù)據(jù)的字段上添加注解即可。
注解實(shí)現(xiàn)三種方式可供選擇
- 方式一:固定值
- 方式二:動(dòng)態(tài)獲取復(fù)雜數(shù)據(jù)
- 方式三:通過碼值獲取碼值表的數(shù)據(jù)列表
二、關(guān)鍵組件
1、ExcelSelected注解
用于在數(shù)據(jù)模型類中標(biāo)注需要添加下拉列表的字段及其屬性
三種方式都是通過此注解實(shí)現(xiàn)
/**
* 定義Excel列下拉列表屬性的注解。
*/
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelSelected {
/**
* 方式一:固定的下拉選項(xiàng)
*/
String[] source() default {};
/**
* 方式二:提供動(dòng)態(tài)下拉選項(xiàng)的類
*/
Class<? extends ExcelDynamicSelect>[] sourceClass() default {};
/**
* 方式三:基于碼值從數(shù)據(jù)庫查詢數(shù)據(jù)
*/
String codeField() default "";
/**
* 下拉列表的起始行(默認(rèn)從第二行開始)。
*/
int firstRow() default 1;
/**
* 下拉列表的結(jié)束行(默認(rèn)到第65536行)。
*/
int lastRow() default 65536;
}
2、ExcelDynamicSelect接口(僅用于方式二)
方式二定義動(dòng)態(tài)獲取下拉列表數(shù)據(jù)的規(guī)范
實(shí)現(xiàn)該接口的類可以從數(shù)據(jù)庫、外部服務(wù)或其他動(dòng)態(tài)來源獲取數(shù)據(jù)
/**
* 動(dòng)態(tài)下拉列表數(shù)據(jù)提供者接口。
*/
public interface ExcelDynamicSelect {
/**
* 獲取動(dòng)態(tài)生成的下拉列表選項(xiàng)。
*
* @return 下拉選項(xiàng)數(shù)組。
*/
String[] getSource();
}
3、ExcelSelectedResolve類
負(fù)責(zé)解析ExcelSelected注解,獲取下拉列表的具體數(shù)據(jù)
/**
* 根據(jù) ExcelSelected 注解解析下拉列表數(shù)據(jù)源。
*/
@Data
@Slf4j
public class ExcelSelectedResolve {
/**
* 下拉選項(xiàng)數(shù)組。
*/
private String[] source;
/**
* 下拉列表的起始行。
*/
private int firstRow;
/**
* 下拉列表的結(jié)束行。
*/
private int lastRow;
/**
* 解析下拉列表數(shù)據(jù)來源
*
* @param excelSelected 下拉框注解對(duì)象
* @return 下拉框選項(xiàng)數(shù)組
*/
public String[] resolveSelectedSource(ExcelSelected excelSelected) {
if (excelSelected == null) {
return null;
}
// 方式一:獲取固定下拉框的內(nèi)容
String[] source = excelSelected.source();
if (source.length > 0) {
return source;
}
// 方式二:獲取動(dòng)態(tài)下拉框的內(nèi)容
Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();
if (classes.length > 0) {
try {
ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();
String[] dynamicSelectSource = excelDynamicSelect.getSource();
if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {
return dynamicSelectSource;
}
} catch (InstantiationException | IllegalAccessException e) {
log.error("解析動(dòng)態(tài)下拉框數(shù)據(jù)異常", e);
}
}
// 方式三:獲取碼值下拉數(shù)據(jù)(動(dòng)態(tài)下拉)
String codeField = excelSelected.codeField();
if (ObjectUtils.isNotEmpty(codeField)) {
try {
// 這里就是通過碼值查詢碼值表,寫死了,每次傳碼值查詢即可
String[] codeFieldSource = SpringUtil.getBean(xxxService.class)
.selectByCode(codeField);
if (ObjectUtils.isNotEmpty(codeFieldSource)) {
return codeFieldSource;
}
} catch (Exception e) {
log.error("解析動(dòng)態(tài)下拉框(碼值)數(shù)據(jù)異常", e);
}
}
return null;
}
}
4、SelectedSheetWriteHandler類
- SheetWriteHandler實(shí)現(xiàn)類,在Sheet創(chuàng)建后設(shè)置下拉列表
- 在隱藏的sheet中存儲(chǔ)下拉選項(xiàng),然后設(shè)置數(shù)據(jù)驗(yàn)證以實(shí)現(xiàn)下拉功能
- 最后這里添加了阻止輸入非下拉選項(xiàng)的值的校驗(yàn)
/**
* 處理Excel下拉列表的SheetWriteHandler實(shí)現(xiàn)類。
*/
@Slf4j
@Data
public class SelectedSheetWriteHandler implements SheetWriteHandler {
// 存儲(chǔ)列索引與對(duì)應(yīng)下拉列表解析器的映射
private Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();
/**
* 構(gòu)造方法,解析表頭類中的下拉列表注解信息。
*
* @param head 表頭類。
*/
public SelectedSheetWriteHandler(Class<?> head) {
// 獲取所有聲明的字段
Field[] fields = head.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
// 獲取 ExcelSelected 注解
ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
if (selected != null) {
ExcelSelectedResolve resolve = new ExcelSelectedResolve();
// 解析下拉列表數(shù)據(jù)源
String[] source = resolve.resolveSelectedSource(selected);
if (source != null && source.length > 0) {
resolve.setSource(source);
resolve.setFirstRow(selected.firstRow());
resolve.setLastRow(selected.lastRow());
// 使用注解中的索引或字段順序作為列索引
if (property != null && property.index() >= 0) {
selectedMap.put(property.index(), resolve);
} else {
selectedMap.put(i, resolve);
}
}
}
}
}
/**
* 在創(chuàng)建Sheet之前調(diào)用的方法。
*/
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
// 此處無需操作,保持空實(shí)現(xiàn)
}
/**
* 在Sheet創(chuàng)建后調(diào)用的方法,用于設(shè)置Excel下拉列表。
*
* @param writeWorkbookHolder 寫入的工作簿持有者。
* @param writeSheetHolder 寫入的Sheet持有者。
*/
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
Sheet sheet = writeSheetHolder.getSheet();
Workbook workbook = sheet.getWorkbook();
// SXSSFWorkbook 是 Apache POI 庫中用于處理大文件的一種特殊工作簿類型
SXSSFWorkbook sw = (SXSSFWorkbook) workbook;
// 1.創(chuàng)建一個(gè)隱藏的sheet,名稱為hidden,用于存儲(chǔ)下拉列表選項(xiàng)
String hiddenName = "hidden";
XSSFSheet hiddenSheet = sw.getXSSFWorkbook().createSheet(hiddenName);
// 將隱藏的sheet設(shè)置為不可見
workbook.setSheetHidden(workbook.getSheetIndex(hiddenName), true);
// 創(chuàng)建數(shù)據(jù)驗(yàn)證輔助器
DataValidationHelper helper = sheet.getDataValidationHelper();
// 為每個(gè)需要下拉列表的列創(chuàng)建數(shù)據(jù)驗(yàn)證
selectedMap.forEach((index, selectedResolve) -> {
// 設(shè)置下拉列表的范圍:起始行,結(jié)束行,起始列,結(jié)束列
CellRangeAddressList rangeList = new CellRangeAddressList(
selectedResolve.getFirstRow(),
selectedResolve.getLastRow(),
index,
index
);
// 在隱藏的sheet中生成下拉列表選項(xiàng)值
String[] values = selectedResolve.getSource();
generateSelectValue(hiddenSheet, index, values);
// 獲取Excel列標(biāo),例如A, B, AA
String excelLine = getExcelLine(index);
// 引用隱藏sheet中的單元格區(qū)域,例如hidden!$H$1:$H$50
String refers = hiddenName + "!$" + excelLine + "$1:$" + excelLine + "$" + values.length;
// 使用引用的內(nèi)容作為下拉列表的值
DataValidationConstraint constraint = helper.createFormulaListConstraint(refers);
DataValidation validation = helper.createValidation(constraint, rangeList);
// 設(shè)置驗(yàn)證屬性,阻止輸入非下拉選項(xiàng)的值
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.setShowErrorBox(true);
validation.setSuppressDropDownArrow(true);
validation.createErrorBox("提示", "請(qǐng)輸入下拉選項(xiàng)中的內(nèi)容");
// 將驗(yàn)證添加到當(dāng)前的sheet中
sheet.addValidationData(validation);
});
}
/**
* 獲取Excel列標(biāo)(例如:A-Z, AA-ZZ)。
*
* @param num 列索引,從0開始。
* @return Excel列標(biāo)字符串。
*/
public static String getExcelLine(int num) {
StringBuilder line = new StringBuilder();
// 計(jì)算列標(biāo),使用字母表示,例如 A, B, ..., Z, AA, AB, ...
int first = num / 26;
int second = num % 26;
if (first > 0) {
line.append((char) ('A' + first - 1));
}
line.append((char) ('A' + second));
return line.toString();
}
/**
* 在隱藏的sheet中生成下拉列表選項(xiàng)值。
*
* @param sheet 隱藏的sheet對(duì)象。
* @param col 列索引。
* @param values 下拉列表選項(xiàng)值數(shù)組。
*/
private void generateSelectValue(Sheet sheet, int col, String[] values) {
// 將下拉列表選項(xiàng)值寫入隱藏的sheet中,每個(gè)選項(xiàng)值占用一行
for (int i = 0, length = values.length; i < length; i++) {
Row row = sheet.getRow(i);
if (row == null) {
row = sheet.createRow(i);
}
// 在指定列中創(chuàng)建單元格并設(shè)置下拉列表選項(xiàng)值
row.createCell(col).setCellValue(values[i]);
}
}
}
三、實(shí)際應(yīng)用
包含三種方式,固定值、動(dòng)態(tài)獲取、碼值數(shù)據(jù)庫獲取
@Data
public class Employee {
@ExcelProperty(value = "用戶編號(hào)")
private Integer id;
@ExcelProperty(value = "姓名")
private String name;
@ExcelProperty(value = "性別")
@ExcelSelected(source = {"男", "女"})
private String gender;
@ExcelProperty(value = "職位")
@ExcelSelected(sourceClass = {PositionDynamicSelect.class})
private String position;
@ExcelProperty(value = "國家")
@ExcelSelected(codeField = "country_code")
private String country;
}
方式二的動(dòng)態(tài)獲取數(shù)據(jù)
public class PositionDynamicSelect implements ExcelDynamicSelect {
@Override
public String[] getSource() {
// 動(dòng)態(tài)生成職位列表
return new String[]{"軟件工程師", "項(xiàng)目經(jīng)理", "人事專員", "財(cái)務(wù)分析師"};
}
}
測(cè)試類
public class EmployeeExcelTest {
public static void main(String[] args) {
String fileName = "/Users/xuchang/Documents/employee.xlsx";
EasyExcel.write(fileName, Employee.class)
.registerWriteHandler(new SelectedSheetWriteHandler(Employee.class))
.sheet().doWrite((Collection<?>) null);
}
}
下拉效果

輸入非下拉框數(shù)據(jù)效果

總結(jié)
方式一只需要添加注解@ExcelSelected(source = {"x1", "x2"})即可
方式二在查詢復(fù)雜的情況下使用,每個(gè)下拉都需要?jiǎng)?chuàng)建一個(gè)ExcelDynamicSelect的實(shí)現(xiàn)類,并添加注解@ExcelSelected(sourceClass = {xxx.class})
方式三只需要添加注解@ExcelSelected(codeField = "xxx_code"),所有系統(tǒng)應(yīng)該都有碼值表,在ExcelSelectedResolve類中已寫好通過碼值查詢數(shù)據(jù)的方法
同樣也支持@ExcelSelected注解的擴(kuò)展,添加屬性,然后在ExcelSelectedResolve類中去添加獲取下拉數(shù)據(jù)的方法。
以上就是EasyExcel自定義下拉注解的三種實(shí)現(xiàn)方式總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于EasyExcel自定義下拉注解的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Spring-Security自定義登陸錯(cuò)誤提示信息
這篇文章主要介紹了Spring-Security自定義登陸錯(cuò)誤提示信息,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Springboot手動(dòng)連接庫并獲取指定表結(jié)構(gòu)的示例代碼
這篇文章主要介紹了Springboot手動(dòng)連接庫并獲取指定表結(jié)構(gòu)的示例代碼,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07
HashMap方法之Map.getOrDefault()解讀及案例
這篇文章主要介紹了HashMap方法之Map.getOrDefault()解讀及案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Spring Security自定義身份認(rèn)證的實(shí)現(xiàn)方法
這篇文章主要介紹了Spring Security自定義身份認(rèn)證的實(shí)現(xiàn)方法,下面對(duì)Spring Security的這三種自定義身份認(rèn)證進(jìn)行詳細(xì)講解,需要的朋友可以參考下2025-04-04
使用SpringAI整合Ollama實(shí)現(xiàn)工具鏈調(diào)用功能
文章介紹了SpringAI的ToolCalling能力,允許大模型自主調(diào)用外部工具以完成多步驟任務(wù),具體而言,通過@Tool注解標(biāo)記方法,SpringAI自動(dòng)解析生成JSONSchema,@Tools注解注冊(cè)多個(gè)工具,模型可自主選擇調(diào)用順序和組合,需要的朋友可以參考下2026-05-05
java自帶命令行工具jmap、jhat與jinfo的使用實(shí)例代碼詳解
本篇文章主要通過代碼實(shí)例對(duì)java自帶命令行工具jmap、jhat與jinfo的使用做出了詳解,需要的朋友可以參考下2017-04-04

