잊지 않겠습니다.

'iText'에 해당되는 글 1건

  1. 2013.09.13 24. View의 표현법 - Application 1

* 사내 강의용으로 사용한 자료를 Blog에 공유합니다. Spring을 이용한 Web 개발에 대한 전반적인 내용에 대해서 다루고 있습니다.


View는 다양한 방법으로 표현이 가능합니다. 지금까지 우리는 Html로만 표현되는 View를 알아봤습니다. 그럼 다른 Type의 View는 어떤 것들이 있을까요?

먼저, 가장 자주 쓰이는 Excel과 pdf가 있을수 있습니다. 그리고, json 형태로 표현되어서 다른 이기종간의 데이터 교환으로 사용될 수 있는 json format이 있을 수 있습니다.
마지막으로 View에서 넘길수 있는 특이한 형태로서, 파일을 upload를 알아보도록 하겠습니다. 

1. Excel

excel 파일을 생성하기 위해서는 apache poi jar가 필요합니다. apache poi는 microsoft office 파일을 다루기 위한 java open source library입니다. 
poi에서 다룰 수 있는 파일 종류는 다음과 같습니다. 

# Excel 
# Word
# PowerPoint
# Visio
# Outlook data file
# MS Publisher

2001년부터 시작된 오래된 java project 중 하나입니다. 참고 사이트는 다음과 같습니다. http://poi.apache.org/
poi를 사용하기 위해서 pom.xml에 다음 항목을 추가합니다. 

    <dependency>
      <groupId>org.apache.poi</groupId>
      <artifactId>poi</artifactId>
      <version>3.9</version>
    </dependency>
    <dependency>
      <groupId>net.sourceforge.jexcelapi</groupId>
      <artifactId>jxl</artifactId>
      <version>2.6.12</version>
    </dependency>

Excel 파일을 다루기 위해서는 org.springframework.web.servlet.view.document.AbstractJExcelView 를 상속받는 View를 구성해야지 됩니다. 이 View는 poi를 기반으로 구성이 되어 있으며, 다음 1개의 method만 재 정의하면 excel 파일을 작성할 수 있습니다.  

Spring에서는 Excel에 대해서 2개의 Abstract class를 제공하고 있습니다. AbstractJExcelView와 AbstractExcelView가 바로 그것입니다. 이 둘의 차이는 API의 사용 유무에 따라 다릅니다. JExcelView는 jexcelapi를 기반으로 구성되어 있으며, AbstractExcelView는 poi를 기반으로 구성되어 있습니다. 어느것이나 사용해도 좋지만, 좀 더 사용하기 편한 jexcelapi를 이용해보도록 하겠습니다.


    /**
     * Subclasses must implement this method to create an Excel Workbook
     * document, given the model.
     * @param model the model Map
     * @param workbook the Excel workbook to complete
     * @param request in case we need locale etc. Shouldn't look at attributes.
     * @param response in case we need to set cookies. Shouldn't write to it.
     * @throws Exception in case of failure
     */
    protected abstract void buildExcelDocument(Map<String, Object> model, WritableWorkbook workbook,
            HttpServletRequest request, HttpServletResponse response) throws Exception;

다음은 excel 파일을 만드는 View 코드입니다. 
public class BookExcelView extends AbstractJExcelView {
    @Override
    protected void buildExcelDocument(Map<String, Object> model, WritableWorkbook workbook, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        String fileName = createFileName();
        setFileNameToResponse(request, response, fileName);

        WritableSheet sheet = workbook.createSheet("책목록", 0);

        sheet.addCell(new Label(0, 0, "제목"));
        sheet.addCell(new Label(1, 0, "저자"));
        sheet.addCell(new Label(2, 0, "설명"));
        sheet.addCell(new Label(3, 0, "출판일"));

        @SuppressWarnings("unchecked")
        List<Book> books = (List<Book>) model.get("books");

        int row = 1;
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        for (Book book : books) {
            sheet.addCell(new Label(0, row, book.getTitle()));
            sheet.addCell(new Label(1, row, book.getAuthor()));
            sheet.addCell(new Label(2, row, book.getComment()));
            sheet.addCell(new Label(3, row, dateFormat.format(book.getPublishDate())));
            row++;
        }
    }

    private void setFileNameToResponse(HttpServletRequest request, HttpServletResponse response, String fileName) {
        String userAgent = request.getHeader("User-Agent");
        if (userAgent.indexOf("MSIE 5.5") >= 0) {
            response.setContentType("doesn/matter");
            response.setHeader("Content-Disposition", "filename=\"" + fileName + "\"");
        } else {
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        }
    }

    private String createFileName() {
        SimpleDateFormat fileFormat = new SimpleDateFormat("yyyyMMdd");
        return new StringBuilder("책리스트").append("-").append(fileFormat.format(new Date())).append(".xls").toString();
    }
}

그리고 작성된 View는 생성의 비용을 줄이기 위해서 applicationContext에 다음과 같이 등록할 수 있습니다. 

<bean id="bookExcelView" class="com.xyzlast.mvc.bookstore.view.BookExcelView"/>

사용하는 Controller 코드는 다음과 같습니다. 
    @RequestMapping(value = "exceldownload", method=RequestMethod.GET)
    public ModelAndView downloadExcel() {
        List<Book> books = bookService.getAll();
        ModelAndView mv = new ModelAndView();
        mv.addObject("books", books);
        mv.setView(bookExcelView);
        return mv;
    }

Controller를 통해 다운 받은 Excel 파일은 다음과 같습니다. 



2. pdf - iText 2.x ~ 4.x

pdf 파일 포멧은 문서 포멧으로 가장 각광받는 포멧입니다. OS platform에 중립적인 문서 포멧으로 각광을 받고 있습니다. pdf 파일을 만들기 위해서는 iText가 필요합니다. 기본적으로 Spring은 iText 2.x대를 기반으로 구성이 되어있습니다. iText의 내용은 너무나 방대하기 때문에, http://itextpdf.com/book/ 를 참고해주시길 바랍니다. 
기본적으로 org.springframework.web.servlet.view.document.AbstractPdfView 객체를 상속받아서 pdf의 내용을 구현하는 것을 기본으로 하고 있습니다. 먼저, iText를 사용하기 위해서 다음 항목을 pom.xml에 추가합니다. 

<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>2.1.7</version>
</dependency>


그리고, Pdf를 만들기 위한 View를 추가합니다. 
public class BookPdfView2 extends AbstractPdfView {

    @Override
    protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        String fileName = createFileName();
        setFileNameToResponse(request, response, fileName);

        Chapter chapter = new Chapter(new Paragraph("this is english"), 1);
        chapter.add(new Paragraph("이건 메세지입니다."));
        document.add(chapter);
    }

    private void setFileNameToResponse(HttpServletRequest request, HttpServletResponse response, String fileName) {
        String userAgent = request.getHeader("User-Agent");
        if (userAgent.indexOf("MSIE 5.5") >= 0) {
            response.setContentType("doesn/matter");
            response.setHeader("Content-Disposition", "filename=\"" + fileName + "\"");
        } else {
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        }
    }

    private String createFileName() {
        SimpleDateFormat fileFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
        return new StringBuilder("설문조사").append("-").append(fileFormat.format(new Date())).append(".pdf").toString();
    }
}

buildPdfDocument를 override 시켜서 문서를 만드는것을 확인해주세요. Controller에서 사용하는 방법은 Excel과 동일하기 때문에 생략하도록 하겠습니다. 꼭 한번 해주시길 바랍니다. 이 코드에는 심각한 버그가 하나 있습니다. ^^


3. pdf - iText 5.x


위에 소개드린것처럼 기본적으로 Spring은 2.x대의 iText를 기반으로 구성되어 있습니다. 그렇지만, iText 5.x대를 사용하도록 View 인터페이스를 구현하면 iText 5.x대의 버젼을 사용할 수 있습니다. iText 5.x를 이용한 새로운 View를 만들어보도록 하겠습니다. 
먼저, iText 5.x를 추가하기 위해서 pom.xml에 다음 설정을 추가합니다. 

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.4.0</version>
</dependency>


iText가 버젼업이 되면서 가장 문제가 되는 것은 package 명이 변경되었다는 점입니다. 사용되는 객체들의 이름은 변경된 것이 없지만, 전체 package 명이 바뀌면서 기존의 AbstractPdfView를 사용할 수가 없게 되어버렸습니다. 새로운 View를 만들어서 iText5를 지원할 수 있도록 해봅시다. 
View를 새로 만들어줄 때는 Spring에서 제공하는 AbstractView를 상속받아서 처리하는 것이 일반적입니다. IText5를 지원하기 위해서, AbstractIText5PdfView 객체를 생성했습니다. 
override된 다음 method들을 살펴볼 필요가 있습니다. 

    protected boolean generatesDownloadContent();
    protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
    protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException;

# generatesDownloadContents
Download가 이루어질 View인지, 아니면 Html과 같이 Rendering될 View인지를 결정하는 method입니다. true/false로 설정하면됩니다.

# renderMergedOutputModel
실질적으로 View가 생성되는 코드입니다. 여기에서는 iText를 이용한 pdf document를 생성하는 코드가 위치하게 됩니다.

# writeToResponse
HttpResponseServlet에 output을 보내는 코드입니다. 

만들어진 AbstractIText5PdfView의 전체 코드는 다음과 같습니다.

public
abstract class AbstractIText5PdfView extends AbstractView { public AbstractIText5PdfView() { setContentType("application/pdf"); } @Override protected boolean generatesDownloadContent() { return true; } @Override protected final void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception { ByteArrayOutputStream baos = createTemporaryOutputStream(); Document document = newDocument(); PdfWriter writer = newWriter(document, baos); prepareWriter(model, writer, request); buildPdfMetadata(model, document, request); document.open(); buildPdfDocument(model, document, writer, request, response); document.close(); writeToResponse(response, baos); } @Override protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException { response.setContentType(getContentType()); ServletOutputStream out = response.getOutputStream(); baos.writeTo(out); out.flush(); } protected Document newDocument() { return new Document(PageSize.A4); } protected PdfWriter newWriter(Document document, OutputStream os) throws DocumentException { return PdfWriter.getInstance(document, os); } protected void prepareWriter(Map<String, Object> model, PdfWriter writer, HttpServletRequest request) throws DocumentException { writer.setViewerPreferences(getViewerPreferences()); } protected int getViewerPreferences() { return PdfWriter.ALLOW_PRINTING | PdfWriter.PageLayoutSinglePage; } //Document가 open되기 전, pdf의 meta 정보를 넣을 때 사용 protected abstract void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) throws Exception; //pdf의 내용을 추가하는데 이용 protected abstract void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception; }


여기에서 중요한 method는 buildPdfMetaData와 buildPdfDocument method입니다. 

# buildPdfMetadata
Pdf 문서의 정보를 지정할 수 있는 method입니다. 이때는 pdf document가 아직 open이 되지 않은 상태이기 때문에, 문서에 내용을 추가하거나 만드는 것이 아닌 문서 자체의 정보를 기록하게 됩니다. 이 method에서 할 수 있는 중요한 일은 암호화와 권한을 설정할 수 있습니다. 이 부분에 대해서는 watermark를 지원하는 pdf 또는 pdf security 부분을 참조해주시면 될 것 같습니다. 

# buildPdfDocument
Pdf 문서를 작성하는 method입니다. pdf document를 작성하는 실질적인 method입니다. IText 2.x를 지원하는 View와 동일한 코드를 작성해주면 됩니다.

아래는 만들어진 AbstractIText5PdfView를 상속받아서 구현된 BookPdfView입니다. 

public class BookPdfView extends AbstractIText5PdfView {
    @Override
    protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        String fileName = createFileName();
        setFileNameToResponse(request, response, fileName);
        Chapter chapter = new Chapter(new Paragraph("this is english"), 1);
        chapter.add(new Paragraph("이건 메세지입니다."));
        document.add(chapter);
    }

    private void setFileNameToResponse(HttpServletRequest request, HttpServletResponse response, String fileName) {
        String userAgent = request.getHeader("User-Agent");
        if (userAgent.indexOf("MSIE 5.5") >= 0) {
            response.setContentType("doesn/matter");
            response.setHeader("Content-Disposition", "filename=\"" + fileName + "\"");
        } else {
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
        }
    }

    private String createFileName() {
        SimpleDateFormat fileFormat = new SimpleDateFormat("yyyyMMdd_HHmmss");
        return new StringBuilder("pdf").append("-").append(fileFormat.format(new Date())).append(".pdf").toString();
    }

    @Override
    protected void buildPdfMetadata(Map<String, Object> model, Document document, HttpServletRequest request) throws Exception {
        //Meta data 처리는 하지 않습니다.
    }
}

만들어서 사용해보면 이상한점을 발견할 수 있습니다. 영어 메세지는 표시가 되지만, 한글 메세지는 표시가 되지 않습니다. 그건 기본적으로 pdf가 영문 font만을 사용하도록 문서가 구성이 되어있기 때문입니다. pdf에서 영문 이외의 한글/한문/일어 를 보기 위해서는 itext-asian이라는 library를 추가로 사용해야지 됩니다. 

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>

library를 추가를 하고, BookPdfView의 buildPdfDocument를 다음과 같이 수정하도록 합니다. 
    @Override
    protected void buildPdfDocument(Map<String, Object> model, Document document, PdfWriter writer, HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        String fileName = createFileName();
        setFileNameToResponse(request, response, fileName);
        BaseFont cfont = BaseFont.createFont("HYGoThic-Medium", "UniKS-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font objFont = new Font(cfont, 12);

        Chapter chapter = new Chapter(new Paragraph("this is english"), 1);
        chapter.add(new Paragraph("이건 메세지입니다.", objFont));
        document.add(chapter);
    }

한글 font를 지정하고, 그 Font를 사용하는 것을 볼 수 있습니다. 실질적으로 itext-asian은 java code가 하나도 없는 jar 파일입니다. 내부는 Font에 대한 meta data들만 추가되어 있는 상태에서 그 Font를 사용하도록 설정을 조금 변경시킨 것 밖에는 없습니다. 
다음은 만들어진 pdf 파일입니다. 




4. Json

json은 web api의 데이터 Protocol로 주로 사용됩니다. HTML과 atom은 xml을 base로 한 문서용 마크업언어입니다. 그런데, 이 형태는 데이터를 기술하기에는 표기가 너무나 중복이 됩니다. 그래서, 좀 더 단순한 데이터의 포멧이 제안되었고, 그 중에서 가장 각광받고, 자주 사용되고 있는 것이 json format입니다. 
지금 Book 객체에 대한 json 은 다음과 같이 표현됩니다. 

{
  • id335,
  • title"Changed Title > Title : 8",
  • authornull,
  • comment"Changed Comment : Comment : 8",
  • publishDate1362641616000,
  • createDate1362641616000,
  • updateDate1362641616000,
  • status"MISSING",
  • imageUrlnull,
  • rentUser:  {
    • id329,
    • loginId"User Id 8",
    • name"Name 8",
    • password"Password 8",
    • checkSum3069349407,
    • lastLoginTime1362641616000,
    • joinTime1362641616000,
    • pointnull,
    • levelnull,
    • histories: [ ]
    }
},

xml보다 간단한 표시 방법에, 다양한 데이터를 표현할 수 있어서 자주 사용되는 데이터 포멧입니다. 기술적으로는 다음과 같은 장점을 갖습니다. 

1) 데이터의 사이즈가 xml보다 작기 때문에 network 부하가 작습니다.
2) javascript에서 처리가 매우 간단합니다. 
3) 사용하기 쉽고, 직관적입니다. 

java에서 json data format으로 객체를 변경시키는 library는 jackson, gson 등 다양한 library 들이 존재합니다. spring에서는 jackson을 json default converter로 이용합니다. jackson에 대한 jar 설정을 pom.xml에 추가합니다. 

    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-core</artifactId>
      <version>2.1.4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>2.1.4</version>
    </dependency>
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-annotations</artifactId>
      <version>2.1.4</version>
    </dependency>

또한, messageConvert에 jackson을 사용하도록 spring의 request adapter를 수정해야지 됩니다. 

그리고, controller에 book list를 jackson으로 얻어내는 method를 추가하도록 하겠습니다. 기본적으로 categories, histories는 json으로 얻어낼 필요가 없다고 생각되어서 이 두개의 property를 제외했습니다. 또한 user역시 histories를 얻을 필요가 없어서 property를 제거하도록 하겠습니다. jackson으로 변환되지 않기를 원하면 @JsonIgnore annotaion을 붙여 사용하면 됩니다. 변경된 Book 입니다.

@Entity
@Table(name="books")
public class Book {
    @Id
    @Column(name="id")
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id;
    @Column(name="title", length=255)
    private String title;
    @Column(name="author", length=255)
    private String author;
    @Column(name="comment", length=255)
    private String comment;
    @Column(name="publishDate")
    private Date publishDate;
    @Column(name="createDate")
    private Date createDate;
    @Column(name="updateDate")
    private Date updateDate;
    @Column(name="status")
    @Enumerated(EnumType.ORDINAL)
    private BookStatus status;
    @Column(name="imageUrl")
    private String imageUrl;
    @ManyToOne
    @JoinColumn(name="rentUserId", nullable=true)
    private User rentUser;
    @JsonIgnore
    @ManyToMany
    @JoinTable(name="books_categories",
               joinColumns = {@JoinColumn(name="bookId")},
               inverseJoinColumns = {@JoinColumn(name="categoryId")}
            )
    private Set<Category> categories = new HashSet<>();
    @JsonIgnore
    @OneToMany(mappedBy="book")
    private Set<History> histories = new HashSet<>();;

그리고, Controller에 다음 method를 추가합니다. 

    @RequestMapping(value="list/json", method=RequestMethod.GET)
    @ResponseBody
    public List<Book> listup() {
        return bookService.getAll();
    }

마지막으로 테스트코드를 통해서 json output이 정상적으로 되는지 확인해보도록  합니다.

    @Test
    public void listupJson() throws Exception {
        MvcResult result = mock.perform(get("/book/list/json")).andExpect(status().isOk()).andReturn();
        System.out.println(result.getResponse().getContentAsString());
    }

그리고 위와 같이 Entity를 바로 넘기게 되는 순환 참조 오류를 해결하기 위해서 다른 DTO를 생성해서 그 DTO를 넘겨주기도 합니다. 간단히 Map 객체를 만들어서 넘기는 Controller 코드는 다음과 같습니다.

    @RequestMapping(value = "book/json")
    @ResponseBody
    public Object convertToJson() {
        List<Book> books = bookService.listup();
        List<Map<String, Object>> maps = new ArrayList<>();

        for (Book book : books) {
            Map<String, Object> item = new HashMap<>();
            item.put("title", book.getTitle());
            item.put("comment", book.getComment());
            maps.add(item);
        }
        return maps;
    }

Controller에서 @ResponseBody를 처음 사용해봤습니다. @ResponseBody는 따로 View를 갖지 않고, html body 자체에 output을 적는 방법입니다. web api를 사용하는 경우에는 대부분 이러한 방법으로 output을 처리합니다. json에 대해서는 자주 사용되기 때문에 꼭 사용법을 익혀두시길 바랍니다.

5. File upload

File upload는 View가 아닙니다. 이번 장에서 설명하는 영역으로 약간 부적절한 면이 있긴 하지만 이 부분은 Controller 영역입니다. Spring에서 제공하는 HttpRequestServlet이 보내는 Multipart file upload 데이터를 Handling하는 방법을 소개하도록 하겠습니다. Multipart file upload를 처리하기 위해서는 다음 jar들이 필요합니다. 

    <dependency>
      <groupId>commons-fileupload</groupId>
      <artifactId>commons-fileupload</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.4</version>
    </dependency>

먼저, file을 upload하기 위해서는 반드시 다음과 같은 설정이 applicationContext.xml에 위치해야지 됩니다. 

  <bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="2000000" />
  </bean>
  <bean id="uploadDirResource" class="org.springframework.core.io.FileSystemResource">
    <constructor-arg>
      <value>C:/upload/</value>
    </constructor-arg>
  </bean>

위 설정은 매우 민감한 설정입니다. bean의 id도 무조건 multipartResolver라는 이름으로 지정이 되어야지 되며, uploadDirResource 역시 bean의 id가 고정되어야지 됩니다. 이 id를 바꿔주는 것은 불가능합니다. 각 bean들은 file upload의 max size와 저장될 위치를 결정하게 됩니다. 반드시 bean id를 위 설정과 동일하게 설정해야지 된다는 것을 잊지 말아주세요. 

upload 할 데이터에 대한 dto를 만들어줍니다. 이 dto 객체는 Request에서 오는 http 데이터를 Controller로 전달하는 역활을 담당하게 됩니다. 

public class UploadItem {
    private String name;

    private CommonsMultipartFile fileData;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public CommonsMultipartFile getFileData() {
        return fileData;
    }
    public void setFileData(CommonsMultipartFile fileData) {
        this.fileData = fileData;
    }
}

CommonsMultipartFile 객체를 property로 갖는 것을 확인해보실 수 있습니다. 이제 이 객체와 mapping되는 Html 을 구성하도록 하겠습니다. 
기본적으로 Spring @MVC에서 form을 통한 데이터 전달을 할때는 form안에 위치한 input tag의 이름과 property의 이름간에 1:1로 mapping이 되게 됩니다. UploadItem DTO는 아래와 같은 Html과 mapping이 될 수 있습니다. file upload를 하기 위해서 enctype이 multipart/form-data로 되어있는 것을 확인하시길 바랍니다.

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head><META http-equiv="Content-Type" content="text/html;charset=UTF-8"><title>Upload Example</title></head><body>
    <form name="uploadItem" method="post" enctype="multipart/form-data">
        <fieldset>
            <legend>Upload Fields</legend>
            <p>
                <label for="name-id">Name</label>
                <input type="text" id="name-id" name="name" />
            </p>
            <p>
                <label for="fileData-id">파일 업로드</label>
                <input type="file" id="fileData-id" name="fileData" type="file" />
            </p>
            <p>
                <input type="submit" />
            </p>
        </fieldset>
    </form></body></html>



이제 이 form data를 받아올 Controller code를 알아보도록 하겠습니다. 지금까지 데이터를 form이나 url을 통해서 언제나 int, String 형태로만 받아오던 데이터를 이제는 DTO를 통해서 받아보도록 하겠습니다. 

    @RequestMapping(value="index", method=RequestMethod.POST)
    public String uploadCompleted(UploadItem uploadItem, BindingResult result) {
        if (result.hasErrors()) {
            for (ObjectError error : result.getAllErrors()) {
                System.err.println("Error: " + error.getCode() + " - " + error.getDefaultMessage());
            }
            return "upload/uploadForm";
        }

        if (!uploadItem.getFileData().isEmpty()) {
            String filename = uploadItem.getFileData().getOriginalFilename();
            String imgExt = filename.substring(filename.lastIndexOf(".") + 1, filename.length());

            // upload 가능한 파일 타입 지정
            if (imgExt.equalsIgnoreCase("JPG") || imgExt.equalsIgnoreCase("JPEG") || imgExt.equalsIgnoreCase("GIF")) {
                byte[] bytes = uploadItem.getFileData().getBytes();
                try {
                    File outFileName = new File(fsResource.getPath() + "_" + filename);
                    FileOutputStream fileoutputStream = new FileOutputStream(outFileName);
                    fileoutputStream.write(bytes);
                    fileoutputStream.close();
                } catch (IOException ie) {
                    System.err.println("File writing error! ");
                }
                System.err.println("File upload success! ");
            } else {
                System.err.println("File type error! ");
            }
        }

받아온 데이터를 이용해서 file을 저장할 수 잇는 것을 알 수 있습니다. 그런데, 지금까지보던 Controller의 input값과는 조금 다른 값이 보입니다. BindingResult가 그 결과인데요. BindingResult는 Request에서 보내지는 응답을 DTO로 변환하게 될 때, 변환과정에서 에러가 발생하는지를 확인하는 객체입니다. 그리고 Request가 DTO로 변환하는 과정을 Binding이라고 합니다. 


Summay

지금까지 5개의 Controller Action에 대해서 알아봤습니다. 

# excel
# pdf (iText 2.x)
# pdf (iText 5.x) - AbstractView를 이용한 신규 View의 생성
# json
# file upload

이 부분에 대해서 직접 돌아가는 web page를 한번 만들어보시길 바랍니다. 그리고 그 동작이 어떻게 일어나는지 확인하는 것이 이쪽까지의 과제입니다. 
다음은 오늘 잠시 나왔던 Binding에 대해서 좀 더 알아보도록 하겠습니다.


Posted by Y2K
,