Spring은 사용하려는 프로파일을 정의하여 상황에 따라 컴포넌트에 대한 등록 및 제외를 결정할 수 있다.

@Profile({"dev", "!kr"})

이라고 프로파일을 정의하면 조건식은 dev or !kr 이 되어 dev 혹은 kr 에 대해서 선언되어 있지 않으면 반드시 실행되는 상황이 발생한다.

이런 상황을 피할 수 있는 방법으로 Spring 4.0에서 추가된 @@Conditional 을 사용하는 방법이 있다. 이와 관련한 질문은 How to conditionally declare Bean when multiple profiles are not active? 를 살펴보면 고민하고 있는 유사한 내용과 답변을 볼 수 있다.

간단한 해결책은 Condition을 구현하는 것이다. 다음과 같이 간단하게 dev and !kr을 만족하는 조건식을 작성해보자.

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class PrdAndIgnoreKrProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().acceptsProfiles("prd") && !context.getEnvironment().acceptsProfiles("kr");
}
}

위의 클래스를 사용하여

@Profile({"dev", "!kr"})
//을 대신하여
@Conditional(PrdAndIgnoreKrProfileCondition.class)
// 으로 정의하면 dev and !kr 조건식이 적용가능해진다.


현재 회사에서는 Spring Boot + AWS Beanstalk 조합으로 서비스를 운영하고 있다. 빈즈톡을 이용하여 로드밸런싱 처리를 할 때 nginx 를 사용하고자 하는 경우 빈즈톡에서 사용하는 인스턴스 내에서 nginx 설정을 변경해도 반영되지 않는 문제가 발생한다.

AWS 의 개발가이드가 완전히 한국어화가 되지 않았기 때문에 찾아보기도 이해하기도 쉽지 않다.

이에 프로젝트 내에 빈즈톡과 관련된 설정파일을 두어 배포시 빈즈톡 내에 위치한 nginx 설정 파일 /etc/nginx/conf.d 에 위치하도록 할 수 있다.

적용방법

  1. 프로젝트 내에 aws 설정파일(.conf) 를 둘 디렉토리를 지정하라.

    • ex) $ mkdir boot-spring-boot/aws

  2. boot-spring-boot/aws 하위에 .ebextensions/nginx/conf.d/elasticbeanstalk 디렉토리를 생성한다.

엘라스틱 빈즈톡의 기본 nginx 를 확장하기 위해서는 .ebextensions/nginx/conf.d/ 폴더에 .conf 설정파일을 추가하면 된다. 그러면 자동으로 엘라스틱 빈즈톡의 nginx 구성에 .ebextensions/nginx/conf.d/ 에 있는 .conf 파일들이 추가된다.

~/workspace/boot-spring-boot/aws/
`-- .ebextensions
    `-- nginx
        `-- conf.d
            |-- elasticbeanstalk
            |   `-- 00_server.conf
            `-- boot-spring-boot.conf

conf.d 폴더에 있는 .conf 파일들은 기본 구성의 http 블럭에 포함되며, conf.d/elasticbeanstalk 폴더에 있는 파일의 내용은 http 블록 내에 server 블록에 포함된다.

엘라스틱 빈즈톡의 기본 nginx 구성을 완전히 덮어쓰기 위해서는 .ebextensions/nginx/nginx.conf 으로 구성을 추가하면 된다.

~/workspace/boot-spring-boot/aws/
`-- .ebextensions
    `-- nginx
        `-- nginx.conf

nginx 에서 파일업로드 사이즈를 10M로 올려보자.

  1. 프로젝트 내에 aws/.ebextensions/nginx/conf.d/elasticbeanstalk 폴더를 생성

  2. 00_server.conf 파일 생성

  3. 00_server.conf 내에 client_max_body_size 10M; 를 추가한다.

  4. 스프링 부트 패키징 시 .ebextensions/nginx/conf.d/elasticbeanstalk 폴더가 포함되어 배포되도록 만든다.

build.gradle 에 다음과 같은 태스크를 만들어 실행하면 손쉽게 가능해진다.

task awsEBZip(type: Zip, dependsOn: 'bootRepackage') {
from '../aws' // .ebextensions 위치
from 'build/libs/' + jar.archiveName bootRepackage 된 파일 위치
baseName = 'boot-spring-boot-' + jar.version // 재생성한 파일명
}



Thymeleaf 에서 스프링 환경변수 사용하기

Thymeleaf 에서 스프링 프로퍼티 값 사용하는 방법

  • @ 뒤에 빈(Bean) 이름을 사용하면 그 빈에 접근할 수 있다.

타임리프에서 스프링의 property 파일(application.property 혹은 application.yml 등)에 기술되어 있는 변수를 이용하려는 경우

${@environment.getProperty('property.key')}

프로파일 환경에 따라 표시를 하려면

<div th : if = $ { @environment .acceptsProfiles ( 'production' )}>
This is the production profile
</ div>
or
<div th : if = "$ {# arrays.contains (@ environment.getActiveProfiles () 'production')} " >
     This is the production profile
</ div>

시스템 환경변수를 이용하는 경우

$ { @systemProperties [ 'property.key' ]}



"Spring MVC 4 익히기" 라는 이름으로 번역한 책이 2016/08/25 에 출간되었습니다. ^^

종이책은 9월 중순 이후에 나올 예정입니다.


쉽게쉽게할 수 있을거라고 생각하고 시작했는데 쉽지 않았습니다. ㅎ.

처음 번역할 때보다는 절반이상 시간을 줄였습니다.


@_@); 이제 스프링부트 책을 한번 써보지 않겠냐는 제의가 주변에서 쏟아지고 있는데... 부담이 되네요.

써야지 하고 뼈대는 잡아놨는데...


다음 책의 이름은 "Boot Spring Boot" 입니다. 뒤집어 읽어도 "Boot Spring Boot!"


과연 완료할 수 있을지!!


the Java API for Microsoft Documents 을 지향하는Apache POI를 이용한 엑셀 생성 및 다운로드 기능 구현을 해보겠다.

현재 배포중인 버전에 대해서는 Maven central Repository 를 통해서 그때그때 확인하기 바란다.

1. 의존성 추가

1.1. pom.xml

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.14</version>
</dependency>

1.2. build.gradle

compile ('org.apache.poi:poi:3.14')

2. 간단한 테스트

POI에 대한 의존성을 추가했으면 간단한 예제를 구현하여 엑셀과 관련된 WorkBook(=file), Sheet, Row, Cell 에 대한 기능을 확인하자.

@Slf4j
public class SheetServiceTest {
    /**
     * @throws Exception
     * @see <a href="https://poi.apache.org/spreadsheet/quick-guide.html#NewWorkbook"></a>
     */
    @Test
    public void generateWorkBook() throws Exception {
        List<Dump> dumps = generateDump(10, "a", "b", "c", "d");
        String excelFileName = "test.xls";
        String sheetname = "send-check-1";
 
        Workbook workbook = new HSSFWorkbook();
        workbook.createSheet(sheetname);
 
 
        Sheet sheet = workbook.getSheet(sheetname);
 
        int size = dumps.size();
        Row headerRow = sheet.createRow(0);
        headerRow.createCell(0).setCellValue("A column");
        headerRow.createCell(1).setCellValue("B column");
        headerRow.createCell(2).setCellValue("C column");
        headerRow.createCell(3).setCellValue("D column");
 
        for (int rownum = 0; rownum < size; rownum++) {
            Row row = sheet.createRow(rownum + 1);
            row.createCell(0).setCellValue(dumps.get(rownum).getA());
            row.createCell(1).setCellValue(dumps.get(rownum).getB());
            row.createCell(2).setCellValue(dumps.get(rownum).getC());
            row.createCell(3).setCellValue(dumps.get(rownum).getD());
        }
 
        generateExcel(Paths.get(excelFileName).toString(), workbook);
 
        assertThat(Paths.get(excelFileName).toFile().exists()).isTrue();
        assertThat(workbook.getSheet(sheetname)).isNotNull();
        assertThat(sheet.getLastRowNum()).isEqualTo(10);
        assertThat(sheet.getRow(sheet.getFirstRowNum()).getCell(0).toString()).isEqualTo("A column");
        assertThat(sheet.getRow(sheet.getLastRowNum()).getCell(0).toString()).isEqualTo("a9");
 
        Paths.get(excelFileName).toFile().delete();
    }
 
    private List<Dump> generateDump(int it, String a, String b, String c, String d) {
        List<Dump> dumps = Lists.newArrayList();
        for (int i = 0; i < it; i++) {
            dumps.add(Dump.builder().a(+ "" + i).b(+ "" + i).c(+ "" + i).d(+ "" + i).build());
        }
        return dumps;
    }
 
    private void generateExcel(String filePath, Workbook workbook) {
        log.debug("generate excel: {}", filePath);
        try (FileOutputStream fileOut = new FileOutputStream(filePath);) {
            workbook.write(fileOut);
        } catch (Exception e) {
            log.error("Occur exception: {}", e);
        }
    }
 
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Dump {
        String a;
        String b;
        String c;
        String d;
    }
}

3. 스프링MVC 에서 구현

스프링에서 제공하던 AbstractExcelView 는 4.2 에서 Deprecated되었다. AbstractXlsView 를 사용해볼까 했는데 apache POI 3.14 버전에서는 XSSFWorkbook 관련한 패키지가 사라져서 쓸 수 없다. 3.16 버전에는 SXSSFWorkbook 관련 패키지가 추가될 것으로 보이니 3.16 쓸 때는 SXSSFWorkbook 사용을 고려해보자. AbstractXlsView를 사용하도록 하자.

3.1. AbstractXlsView 을 구현

@Component("downloadXlsView") // 공통적으로 사용할 수도 있고... 특정 기능에 따라 뷰이름을 구분지어 사용할 수 있겠다. 
public class SendCheckRequestExportXlsView extends AbstractXlsView {
  // 코드는 중략... 상상의 나래를 활짝 펴라~ 
  @Override
    protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
 
    }
}

org.springframework.web.servlet.view.document 패키지에AbstractXlsView 와 AbstractPdfView 가 있다. AbstractXlsxView는 AbstractXlsView 를 확장했는데 import org.apache.poi.xssf.usermodel.XSSFWorkbook 를 필요로 한다. 그런데 POI 3.14 에는 org.apache.poi.xssf 패키지가 빠져있다. 히스토리는 모르겠다.

3.2. Controller 에서 view 를 반환

@Controller
public class DownloadController {
  @RequestMapping("/download-xls")
  public ModelAndView() {
    // view 에서 처리하는데 필요한 데이터를 담아주면 되겠다. 
    return new ModelAndView("downloadXlsView", "model", model);
  }
}

이렇게 하고 이 컨트롤러에 대한 링크를 제공하면 다운로드 처리가 완료된다.

<a href="/download-xls"></a>

4. 정리

엑셀에 필요한 정보를 담기 위해서는 여전히 많은 노력이 필요하다. 뭐 그래도 어떤 데이터들을 출력할지만 정의하면 비교적 처리가 수월해진다.


+ Recent posts