概述
在业务系统中,我们经常需要导出包含下拉选择框的Excel模板,用于规范数据录入。Apache POI作为Java操作Office文档的主流工具,提供了完善的数据验证功能。本文将详细介绍如何使用POI在Excel中创建下拉列表。
核心实现
1. 基础依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
2. 单级下拉列表实现
public class ExcelDropdownDemo {
public void createSingleDropdown() throws IOException {
// 创建工作簿和工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("数据录入");
// 定义下拉选项
String[] options = {"待处理", "处理中", "已完成", "已关闭"};
// 创建数据验证助手
DataValidationHelper helper = sheet.getDataValidationHelper();
// 创建约束:从数组创建下拉列表
DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
// 设置验证范围(例如在A2:A100创建下拉框)
CellRangeAddressList addressList = new CellRangeAddressList(1, 99, 0, 0);
// 创建数据验证对象
DataValidation validation = helper.createValidation(constraint, addressList);
// 设置相关属性
validation.setShowErrorBox(true);
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
validation.createErrorBox("输入错误", "请从下拉列表中选择");
// 应用到工作表
sheet.addValidationData(validation);
// 写入文件
try (FileOutputStream fos = new FileOutputStream("template.xlsx")) {
workbook.write(fos);
}
workbook.close();
}
}
3. 多级联动下拉列表
public void createCascadingDropdown() {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("多级联动");
// 第一级:省份
String[] provinces = {"浙江省", "江苏省", "广东省"};
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint provinceConstraint =
helper.createExplicitListConstraint(provinces);
// 第二级:城市(使用名称定义)
Name cityName = workbook.createName();
cityName.setNameName("cityData");
cityName.setRefersToFormula("INDIRECT($A$2)"); // 根据A2单元格的值动态引用
// 定义命名区域
String[] zhejiangCities = {"杭州", "宁波", "温州"};
String[] jiangsuCities = {"南京", "苏州", "无锡"};
String[] guangdongCities = {"广州", "深圳", "东莞"};
// 在隐藏工作表存储城市数据
Sheet hiddenSheet = workbook.createSheet("hiddenData");
for (int i = 0; i < provinces.length; i++) {
String[] cities = null;
if (i == 0) cities = zhejiangCities;
else if (i == 1) cities = jiangsuCities;
else cities = guangdongCities;
Row row = hiddenSheet.createRow(i);
for (int j = 0; j < cities.length; j++) {
row.createCell(j).setCellValue(cities[j]);
}
}
// 创建数据验证
DataValidationConstraint cityConstraint =
helper.createFormulaListConstraint("cityData");
CellRangeAddressList cityRange = new CellRangeAddressList(1, 1, 1, 1);
DataValidation cityValidation = helper.createValidation(cityConstraint, cityRange);
sheet.addValidationData(cityValidation);
}
4. 从数据库动态加载选项
@Service
public class DynamicDropdownService {
@Autowired
private DepartmentRepository departmentRepo;
public void createDynamicDropdown(Workbook workbook, Sheet sheet) {
// 从数据库获取部门列表
List<Department> departments = departmentRepo.findAll();
String[] deptNames = departments.stream()
.map(Department::getName)
.toArray(String[]::new);
// 创建下拉验证
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint =
helper.createExplicitListConstraint(deptNames);
CellRangeAddressList addressList =
new CellRangeAddressList(1, 1000, 2, 2); // C列
DataValidation validation = helper.createValidation(constraint, addressList);
validation.setSuppressDropDownArrow(true); // 隐藏下拉箭头
sheet.addValidationData(validation);
}
}
高级配置
1. 输入提示和错误信息
// 设置输入提示
validation.createPromptBox("部门选择", "请从列表中选择所属部门");
validation.setShowPromptBox(true);
// 设置错误提示
validation.createErrorBox("无效输入", "输入值不在可选范围内");
validation.setErrorStyle(DataValidation.ErrorStyle.WARNING);
2. 限制输入长度
// 创建文本长度约束
DataValidationConstraint lengthConstraint =
helper.createTextLengthConstraint(
DataValidationConstraint.OperatorType.BETWEEN,
"1", "50"
);
CellRangeAddressList range = new CellRangeAddressList(1, 100, 3, 3);
DataValidation validation = helper.createValidation(lengthConstraint, range);
性能优化建议
-
批量设置验证范围
// 避免为每个单元格单独创建验证
CellRangeAddressList largeRange =
new CellRangeAddressList(1, 10000, 0, 0); -
使用缓存机制
// 缓存常用下拉选项
private static final Map<String, String[]> DROPDOWN_CACHE =
new ConcurrentHashMap<>();public String[] getCachedOptions(String type) {
return DROPDOWN_CACHE.computeIfAbsent(type, k ->
loadOptionsFromDB(k)
);
}
常见问题处理
-
下拉选项过多
// 当选项超过255个字符时,使用隐藏工作表存储
Sheet hidden = workbook.createSheet("options");
Row row = hidden.createRow(0);
for (int i = 0; i < options.length; i++) {
row.createCell(i).setCellValue(options[i]);
}// 使用名称引用
Name namedRange = workbook.createName();
namedRange.setNameName("largeOptions");
namedRange.setRefersToFormula("options!A1:Z100"); -
兼容性处理
// 处理不同Excel版本
if (workbook instanceof XSSFWorkbook) {
// .xlsx文件支持更多选项
validation.setShowErrorBox(true);
} else if (workbook instanceof HSSFWorkbook) {
// .xls文件选项有限制
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
}
完整工具类示例
public class ExcelDropdownUtil {
public static void addDropdown(Sheet sheet, int firstRow, int lastRow,
int firstCol, int lastCol, String[] options) {
DataValidationHelper helper = sheet.getDataValidationHelper();
DataValidationConstraint constraint =
helper.createExplicitListConstraint(options);
CellRangeAddressList addressList =
new CellRangeAddressList(firstRow, lastRow, firstCol, lastCol);
DataValidation validation = helper.createValidation(constraint, addressList);
// 默认配置
validation.setEmptyCellAllowed(true);
validation.setShowPromptBox(true);
validation.createPromptBox("提示", "请从下拉列表中选择");
sheet.addValidationData(validation);
}
}
总结
通过Apache POI的数据验证功能,我们可以轻松实现Excel下拉框的创建。关键点包括:
-
使用
DataValidationHelper创建约束 -
正确设置验证范围
-
合理处理多级联动
-
注意性能和兼容性问题
掌握这些技巧后,就能创建出功能丰富、用户友好的Excel数据录入模板。
实际案例:
一般工作中需要后端下载时,多数用于从数据库动态加载选项
java
/**
* 模板下载
*/
@Override
public void exportExcel(HttpServletResponse response) throws ApiException {
List<CsiServer> csiServerList = csiServerMapper.getAllServer();
List<String> serverList = csiServerList.stream()
.map(CsiServer::getServerAllname)
.filter(Objects::nonNull) // 防止 null 值
.collect(Collectors.toList());
if (serverList.isEmpty()) {
serverList.add(" "); // Excel 下拉不能完全空,至少一个占位
}
String[] serverArray = serverList.toArray(new String[0]);
// String debugJoined = String.join(",", serverArray);
// log.info("下拉选项总长度: {}, 内容预览: {}", debugJoined.length(),
// debugJoined.length() > 100 ? debugJoined.substring(0, 100) + "..." : debugJoined);
//
// if (debugJoined.length() > 255) {
// log.warn("⚠️ 下拉选项超长({} > 255),Excel将无法显示下拉框!", debugJoined.length());
// // 临时截断测试
// // serverArray = new String[]{"选项过多,请联系管理员"};
// }
Workbook workbook = null;
InputStream templateStream = null;
try {
// 1. 从 classpath 加载模板
ClassPathResource classPathResource = new ClassPathResource(ORDERGRAIN_INBOUND_TEMPLATE);
templateStream = classPathResource.getInputStream();
// 2. 根据模板创建 Workbook(支持 .xlsx)
workbook = new XSSFWorkbook(templateStream);
// 3. 获取要添加下拉框的工作表(假设是第一个 sheet,或按名称获取)
Sheet sheet = workbook.getSheetAt(0); // 或 workbook.getSheet("SheetName");
// 4. 创建下拉框(例如:A列第2行到第1000行)
DataValidation dataValidation = PoiUtil.getDataValidation(sheet, serverArray, 23, 1, 999);
// 5. 添加验证规则
sheet.addValidationData(dataValidation);
// 6. 设置响应头,用于浏览器下载
String fileName = URLEncoder.encode("收货数据导入模板.xlsx", StandardCharsets.UTF_8.toString());
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 3. 设置字符编码(避免文件名中文乱码)
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
// 7. 写入响应输出流
workbook.write(response.getOutputStream());
response.getOutputStream().flush();
} catch (IOException e) {
throw new ApiException(ResultCode.FAULT,"导出Excel失败");
} finally {
try {
if (workbook != null) {
workbook.close();
}
if (templateStream != null) {
templateStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
java
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.DataValidation;
import org.apache.poi.ss.usermodel.DataValidationConstraint;
import org.apache.poi.ss.usermodel.DataValidationHelper;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddressList;
@Slf4j
public class PoiUtil {
/**
* Excel 下拉框数据验证工具方法
* 功能:为指定工作表的目标列、行范围创建「静态下拉框」(基于显式选项数组),并配置错误提示机制
* 适用场景:下拉选项少(总字符数≤255,Excel原生限制)、选项固定的场景
*
* @param sheet 目标工作表(如XSSFSheet、HSSFSheet),必须已创建且非空
* @param dropDownOptions 下拉框选项数组,元素为字符串类型;不可为null(空选项需传空数组),元素不可含逗号(Excel下拉框默认用逗号分隔选项)
* @param column 下拉框所在列的索引(POI中列索引从0开始,例:0=A列、1=B列、2=C列...)
* @param firstRow 下拉框生效的起始行索引(POI中行索引从0开始,例:0=Excel第1行、1=Excel第2行...),需≥0
* @param lastRow 下拉框生效的结束行索引(需≥firstRow,例:99=Excel第100行),控制下拉框覆盖的行数范围
* @return DataValidation 配置完成的数据验证对象(包含下拉框规则),需通过 {@link Sheet#addValidationData(DataValidation)} 方法添加到工作表才会生效
* @note 1. 字符限制:选项数组总字符数(含默认分隔符逗号)不可超过255,否则Excel会报错,需改用「动态下拉框」(隐藏工作表+公式引用);
* 2. 选项规范:数组元素不可包含逗号(,),否则会被Excel解析为多个选项,若需含逗号需自定义分隔符(需配合其他API);
* 3. 生效方式:返回的DataValidation对象需手动添加到工作表,未添加则下拉框不显示;
* 4. 错误提示:默认启用错误提示框(输入非下拉选项时触发),可通过返回对象的setXXX方法调整提示文案、样式;
* 5. 版本兼容:支持Excel 2003(.xls,HSSFWorkbook)和2007+(.xlsx,XSSFWorkbook),无需修改方法逻辑
*/
public static DataValidation getDataValidation(Sheet sheet,
String[] dropDownOptions,
int column,
int firstRow,
int lastRow) {
// 1. 定义下拉框生效的单元格范围:[firstRow, lastRow] 行 × [column, column] 列(单个列的连续行)
CellRangeAddressList addressList = new CellRangeAddressList(firstRow, lastRow, column, column);
// 2. 获取工作表的数据验证助手,用于构建验证规则
DataValidationHelper validationHelper = sheet.getDataValidationHelper();
// 3. 创建显式列表约束:下拉选项直接从传入的数组中获取,适用于选项固定的场景
DataValidationConstraint constraint = validationHelper.createExplicitListConstraint(dropDownOptions);
// 4. 绑定约束与单元格范围,生成数据验证对象(下拉框核心配置)
DataValidation dataValidation = validationHelper.createValidation(constraint, addressList);
// 5. 配置错误提示:输入值不在下拉列表中时,显示错误提示框
dataValidation.setShowErrorBox(true);
return dataValidation;
}
}
效果图


手动选择下拉框中选项,也可以粘贴上去,如果不是下拉框中任何一个值就会报错误提示。