스프링 부트 2.0에서 엑셀 다운로드 기능을 구현하는 방법을 정리했다.
pom.xml 설정
https://start.spring.io/ 사이트에서 스프링 부트 2.0.x 버전을 선택해서 프로젝트를 생성한다. Dependencies로는 Web과 Thymeleaf를 선택한다. 생성한 프로젝트 pom.xml 파일에 엑셀 생성을 위해 poi 의존을 추가한다.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>madvirus</groupId>
<artifactId>excel-download</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>excel-download</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
엑셀 다운로드 위한 application.properties 파일 설정
확장자나 파라미터를 이용해서 엑셀 다운로드를 처리할 수 있도록 application.propertie 파일에 다음 설정을 추가한다.
spring.mvc.contentnegotiation.favor-parameter=true
spring.mvc.contentnegotiation.favor-path-extension=true
spring.mvc.contentnegotiation.media-types.xls=application/vnd.ms-excel
스프링 부트는 기본적으로 ContentNegotiationViewResolver를 사용하는데 각 프로퍼티는 다음을 설정한다.
- favor-parameter: 이 값이 true면 ContentNegotiationViewResolver가 format 파라미터로 지정한 미디어 타입을 사용하도록 설정
- favor-path-extension: 이 값이 true면 ContentNegotiationViewResolver가 확장자로 지정한 미디어 타입을 사용하도록 설정
- media-types.타입: 타입에 해당하는 컨텐츠 타입을 지정
예를 들어 위 설정을 사용하면 다음 요청을 엑셀 타입(application/vnd.ms-excel) 요청으로 인지하고, 엑셀 미디어 타입에 해당하는 응답을 처리할 수 있는 뷰를 사용해서 응답을 생성한다.
- stat.xls (확장자가 xls)
- stat?format=xls (format 파라미터가 xls)
예제 컨트롤러
다음 코드는 일반 뷰와 엑셀 다운로드를 처리하는 컨트롤러 코드이다.
@Controller
public class StatController {
private void populateModel(Model model) {
List<StatRow> rows = Arrays.asList(
new StatRow("고객1", 1000, 1500),
new StatRow("고객2", 2000, 2500),
new StatRow("고객3", 3000, 3500)
);
model.addAttribute("rows", rows);
}
@GetMapping("/stat")
public String get(Model model) {
populateModel(model);
return "stat";
}
@GetMapping("/stat.xls")
public String getExcelByExt(Model model) {
populateModel(model);
return "statXls";
}
@GetMapping(path = "/stat", params = "format=xls")
public String getExcelByParam(Model model) {
populateModel(model);
return "statXls";
}
}
get() 메서드는 일반 뷰를 사용해서 응답을 생성한다. getExcelByExt() 메서드는 확장자가 xls인 요청 경로를 처리하므로 "statXls"에 대응하는 뷰 중에서 엑셀 타입을 응답으로 생성할 수 있는 뷰를 선택한다. 비슷하게 getExcelByParam() 역시 format 파라미터가 xls인 요청을 처리하므로 엑셀 타입을 생성할 수 있는 뷰를 선택한다.
엑셀 생성을 위한 뷰 클래스
엑셀 다운로드를 위한 뷰 클래스는 다음과 같이 구현한다. 빈 객체 이름으로 "statxls"를 사용했는데 이 이름은 앞서 컨트롤러에서 리턴한 뷰 이름과 같다.
package exceldownload;
import org.apache.poi.ss.usermodel.*;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.document.AbstractXlsView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
@Component("statXls")
public class StatXlsView extends AbstractXlsView {
@Override
protected void buildExcelDocument(
Map<String, Object> model, Workbook workbook,
HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setHeader("Content-Disposition", "attachment; filename=\"stat.xls\"");
List<StatRow> stats = (List<StatRow>) model.get("rows");
CellStyle numberCellStyle = workbook.createCellStyle();
DataFormat numberDataFormat = workbook.createDataFormat();
numberCellStyle.setDataFormat(numberDataFormat.getFormat("#,##0"));
Sheet sheet = workbook.createSheet("mobilestat");
for (int i = 0 ; i < stats.size() ; i++) {
StatRow stat = stats.get(i);
Row row = sheet.createRow(i);
Cell cell0 = row.createCell(0);
cell0.setCellValue(stat.getName());
Cell cell1 = row.createCell(1);
cell1.setCellType(CellType.NUMERIC);
cell1.setCellValue(stat.getValue1());
cell1.setCellStyle(numberCellStyle);
Cell cell2 = row.createCell(2);
cell2.setCellType(CellType.NUMERIC);
cell2.setCellValue(stat.getValue2());
cell2.setCellStyle(numberCellStyle);
}
}
}
타임리프 뷰 구현
타임리프트를 이용한 뷰 구현 파일인 stat.html은 다음과 같아 간단하게 구현했다. 엑셀 다운로드를 위한 링크를 추가했다.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>통계</title>
</head>
<body>
<a href="stat.xls">엑셀다운, 확장자(stat.xls)</a> |
<a href="stat?format=xls">엑셀다운, 파라미터(stat?format=xls)</a>
<table border="1">
<thead>
<tr>
<th>이름</th>
<th>값1</th>
<th>값2</th>
</tr>
</thead>
<tbody>
<tr th:each="row : ${rows}">
<td th:text="${row.name}"></td>
<td th:text="${#numbers.formatInteger(row.value1, 1, 'COMMA')}"></td>
<td th:text="${#numbers.formatInteger(row.value2, 1, 'COMMA')}"></td>
</tr>
</tbody>
</table>
</body>
</html>
예제 실행
완전한 예제 프로젝트는 https://github.com/madvirus/excel-download 리포지토리에서 구할 수 있다. 명령 프롬프트에서 "mvnw spring-boot:run" 명령어를 부트 어플리케이션을 실행한 뒤에 http://localhost:8080/stat 주소에 연결해보자. 다음 결과를 볼 수 있다.
엑셀 다운로드 링크를 클릭해보자. 두 링크 중 아무거나 클릭하면 엑셀 파일을 다운로드 한다.
실제 다운로드한 파일을 열어보자. 아래와 같이 엑셀 파일이 올바르게 생성된 것을 확인할 수 있다.