엑셀 데이터를 만든다.
응답의 contentType을 지정한다.
xls : application/vnd.ms-excel
xlsx : application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
header에 Content-Disposition 추가한다.
attachment; filename={filename}
-> 해당 파일 이름으로 바디 데이터를 다운 받으라는 의미이다.
스프링의 View 를 통해서 쉽게 구현할 수 있다. 엑셀 다운로드를 위해 추상화 된 View를 제공한다. (poi 라이브러리 사용)
(AbstractXlsView, AbstractXlsxView, AbstractXlsxStreamingView)
각 추상화된 View는 다음과 같은 구조를 가진다.
예로 AbstractXlsxView를 살펴보면 Content-Type과 XSSFWorkbook 를 사용하는 것을 알 수 있다.
예제는 간단하게 다음과 같은 구조 이다.
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation group: 'org.apache.poi', name: 'poi', version: '5.0.0' // xls
implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '5.0.0' // xlsx
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
@Component
public class ExcelXlsxView extends AbstractXlsxView {
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 엑셀 파일 생성 로직
new ExcelWriter(workbook, model, response).create();
}
@GetMapping("/excel/download")
public ModelAndView excelDownload(HttpServletRequest request) {
Map<String, Object> excelData = excelService.excelDownload(request);
return new ModelAndView(new ExcelXlsxView(), excelData);
}
< ExcelWriter.java >
public class ExcelWriter {
private final Workbook workbook;
private final Map<String, Object> data;
private final HttpServletResponse response;
// 생성자
public ExcelWriter(Workbook workbook, Map<String, Object> data, HttpServletResponse response) {
this.workbook = workbook;
this.data = data;
this.response = response;
}
// 엑셀 파일 생성
public void create() {
setFileName(response, mapToFileName());
Sheet sheet = workbook.createSheet();
createHead(sheet, mapToHeadList());
createBody(sheet, mapToBodyList());
}
// 모델 객체에서 파일 이름 꺼내기
private String mapToFileName() {
return (String) data.get("filename");
}
// 모델 객체에서 헤더 이름 리스트 꺼내기
@SuppressWarnings("unchecked")
private List<String> mapToHeadList() {
return (List<String>) data.get("head");
}
// 모델 객체에서 바디 데이터 리스트 꺼내기
@SuppressWarnings("unchecked")
private List<List<String>> mapToBodyList() {
return (List<List<String>>) data.get("body");
}
// 파일 이름 지정
private void setFileName(HttpServletResponse response, String fileName) {
response.setHeader("Content-Disposition",
"attachment; filename=\"" + getFileExtension(fileName) + "\"");
}
// 넘어온 뷰에 따라서 확장자 결정
private String getFileExtension(String fileName) {
if (workbook instanceof XSSFWorkbook) {
fileName += ".xlsx";
}
if (workbook instanceof SXSSFWorkbook) {
fileName += ".xlsx";
}
if (workbook instanceof HSSFWorkbook) {
fileName += ".xls";
}
return fileName;
}
// 엑셀 헤더 생성
private void createHead(Sheet sheet, List<String> headList) {
createRow(sheet, headList, 0);
}
// 엑셀 바디 생성
private void createBody(Sheet sheet, List<List<String>> bodyList) {
int rowSize = bodyList.size();
for (int i = 0; i < rowSize; i++) {
createRow(sheet, bodyList.get(i), i + 1);
}
}
// 행 생성
private void createRow(Sheet sheet, List<String> cellList, int rowNum) {
int size = cellList.size();
Row row = sheet.createRow(rowNum);
for (int i = 0; i < size; i++) {
row.createCell(i).setCellValue(cellList.get(i));
}
}
// 모델 객체에 담을 형태로 엑셀 데이터 생성
public static Map<String, Object> createExcelData(List<? extends ExcelDto> data, Class<?> target) {
Map<String, Object> excelData = new HashMap<>();
excelData.put("filename", createFileName(target));
excelData.put("head", createHeaderName(target));
excelData.put("body", createBodyData(data));
return excelData;
}
// @ExcelColumnName에서 헤더 이름 리스트 생성
private static List<String> createHeaderName(Class<?> header) {
List<String> headData = new ArrayList<>();
for (Field field : header.getDeclaredFields()) {
field.setAccessible(true);
if (field.isAnnotationPresent(ExcelColumnName.class)) {
String headerName = field.getAnnotation(ExcelColumnName.class).headerName();
if (headerName.equals("")) {
headData.add(field.getName());
} else {
headData.add(headerName);
}
}
}
return headData;
}
// @ExcelFileName 에서 엑셀 파일 이름 생성
private static String createFileName(Class<?> file) {
if (file.isAnnotationPresent(ExcelFileName.class)) {
String filename = file.getAnnotation(ExcelFileName.class).filename();
return filename.equals("") ? file.getSimpleName() : filename;
}
throw new RuntimeException("excel filename not exist");
}
// 데이터 리스트 형태로 가공
private static List<List<String>> createBodyData(List<? extends ExcelDto> dataList) {
List<List<String>> bodyData = new ArrayList<>();
dataList.forEach(v -> bodyData.add(v.mapToList()));
return bodyData;
}
}
< ExcelDto.java >
public interface ExcelDto {
List<String> mapToList();
}
< ExcelFileName.java >
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelFileName {
String filename() default "";
}
< ExcelColumnName.java >
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumnName {
String headerName() default "";
}
< BackgroundMusic.java >
@Data
@AllArgsConstructor
@NoArgsConstructor
@ExcelFileName
public class BackgroundMusic implements ExcelDto{
@ExcelColumnName(headerName = "music id")
private int id;
@ExcelColumnName
@JsonProperty("file-name")
private String filename;
@ExcelColumnName
private String hour;
@ExcelColumnName
private String weather;
@ExcelColumnName(headerName = "music uri")
@JsonProperty("music_uri")
private String musicUri;
@Override
public List<String> mapToList() {
return Arrays.asList(String.valueOf(id), filename, hour, weather, musicUri);
}
}
< ExcelService.java >
@Service
public class ExcelService {
public Map<String, Object> excelDownload(HttpServletRequest request) {
// 데이터 가져오기
String uri = "https://acnhapi.com/v1a/backgroundmusic";
RestTemplate restTemplate = new RestTemplate();
BackgroundMusic[] result = restTemplate.getForObject(uri, BackgroundMusic[].class);
List<BackgroundMusic> backgroundMusics = Arrays.asList(result);
return ExcelWriter.createExcelData(backgroundMusics, BackgroundMusic.class);
}
}
@Component
public class ExcelXlsView extends AbstractXlsView {
@Override
protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
new ExcelWriter(workbook, model, response).create();
}
}
@GetMapping("/excel/download")
public ModelAndView excelDownload(HttpServletRequest request) {
Map<String, Object> excelData = excelService.excelDownload(request);
return new ModelAndView(new ExcelXlsView(), excelData);
}
정말 좋은 자료 고맙습니다. 빠르게 적용되네요!