Свое представление (view) в Spring MVC

    Если вы работали с Spring MVC то наверняка обращали внимание, что он поддерживает множество различных представлений (view), которые позволяют генерировать pdf, excel, csv использовать jstl, freemarker, velocity и jasperReports. Но что делать если вам потребовалось заполнить какой либо документ в формате docx по шаблону и передать его пользователю? С одной стороны можно в методе воспользоваться доступом к стандартному response и если планируется использовать такую генерацию множество раз, то вынести ее в отдельный класс. Но такое решение не слишком изящно и нарушает MVC-паттерн. Чтобы этого не происходило можно написать свое представление (view). Для этого вам потребуется унаследоваться от одного из абстрактных представлений (view) к примеру от AbstractTemplateView. Для docx этот view выглядит так:
    package ru.habrahabr.spring.view;
     
    import org.apache.commons.codec.binary.Base64;
    import org.docx4j.XmlUtils;
    import org.docx4j.openpackaging.io.SaveToZipFile;
    import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
    import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
    import org.docx4j.wml.Document;
    import org.springframework.web.servlet.view.AbstractTemplateView;
     
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.net.URLEncoder;
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
     
    public class DocxView extends AbstractTemplateView {
     
    public DocxView() {
    setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
    }
     
    @Override
    protected boolean generatesDownloadContent() {
    return true;
    }
     
    @Override
    protected void renderMergedTemplateModel(Map<String, Object> model, 
    HttpServletRequest request, HttpServletResponse response) throws Exception {
     
    WordprocessingMLPackage wordMLPackage =
    WordprocessingMLPackage.load(getApplicationContext().getResource(getUrl()).getFile());
     
    MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
    org.docx4j.wml.Document wmlDocumentEl = (org.docx4j.wml.Document) documentPart.getJaxbElement();
    String xml = XmlUtils.marshaltoString(wmlDocumentEl, true);
     
    HashMap<String, String> mappings = new HashMap<String, String>();
    for (Object key : model.keySet()) {
          mappings.put(key.toString(), model.get(key).toString());
    }
     
    Object obj = XmlUtils.unmarshallFromTemplate(xml, mappings);
    documentPart.setJaxbElement((Document) obj);
     
    String name = model.get("filename")+".docx";
     
    response.setHeader("Content-Disposition""attachment" + name);
     
    ServletOutputStream out = response.getOutputStream();
    (new SaveToZipFile(wordMLPackage)).save(out);
     
    out.flush();
     
    }
     
    public boolean checkResource(Locale locale) {
    return getApplicationContext().getResource(getUrl()).exists();
    }
     
    }
     

    В конструкторе задаем тип контента, а в методе generatesDownloadContent указываем, что тип контента скачиваемый. А в renderMergedTemplateModel осуществляем считывание файла получая путь до него из getUrl и заполняем документ по шаблону. Чтение данных шаблона происходит из model. checkResouce необходим для проверки наличия файла представления.

    Теперь когда представление готово необходимо настроить цепочку локаторов представлений (ResolveViewers), для этого в mvc-config.xml добавляем:
        <bean id="docxViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
         <property name="viewClass" value="ru.habrahabr.spring.view.DocxView"></property>
         <property name="order" value="1"/>
            <property name="prefix" value="/WEB-INF/views/"/>
            <property name="suffix" value=".docx"/>
        </bean>
     
        <bean id="jspViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/views/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
     

    • InternalResourceViewResolver — стандартный локатор представлений.
    • UrlBasedViewResolver — локатор на базе url. В качестве url подразумеваются url представлений.

    На настройке UrlBasedViewResolver стоит остановиться поподробнее. Этот локатор позволяет использовать любые View в том числе и самописные, что указывается через viewClass, далее указываются prefix, suffix точно так же как и для стандартного локатора представлений, order необходим для указания порядка обработки локаторов в цепочке. В результате сначала будет происходить обращение к локатору UrlBasedViewResolver который создаст View и вызовет метод checkResource() для проверки наличия необходимого файла для представления. Если его нет, то метод вернет ложь, а локатор вместо представления вернет null и произойдет переход по цепочке к InternalResourceViewResolver.
    Далее можно в views поместить test.docx файл к примеру в каталог views/docx. И далее обычным образом создаем метод контроллера:
    package ru.habrahabr.spring.controller;
     
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.util.MultiValueMap;
    import org.springframework.web.bind.annotation.RequestMapping;
     
    Controller
    public class DocxController {
     
    @RequestMapping(value = "/get/docx", method = RequestMethod.GET)
    public String doGenerate(Model model) {
    model.addAttribute("filename","test");
    model.addAttribute("test","test");
    return "docx/test";
     

    Далее вызываем метод get/docx и получаем файл в docx.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 20
    • 0
      А файл будет целеком находиться в памяти? Можно ли использовать стили форматирования и т.п.? Спрашиваю, потому что есть задача выгружать огромные excel доки, а проблема в их объеме (слишком уж много памяти кушает).
      • 0
        И еще укажите плз какие либы вы использовали.
      • 0
        А файл будет целеком находиться в памяти?

        Тут да целиком, еще JAXB там память кушает, но ровно один раз. Он сначала в буфер кладется, а уже потом начинает отдаваться. Делается для оптимизации производительности.

        Можно ли использовать стили форматирования и т.п.?

        Не совсем понял вопроса. Этот пример делает подстановку строк вида ${color} в файле на значения из Model по ключу. Полезно при генерации типовых документов на основе базы данных. Если надо что-то больше читайте API в docx4j.

        Спрашиваю, потому что есть задача выгружать огромные excel доки, а проблема в их объеме (слишком уж много памяти кушает).

        Если сильно большие то есть смысл писать на диск и потом пользователя редиректить на nginx, чтобы он отдавал файл.
        • 0
          Писать на диск — не вариант. С таким же успехом можно сразу выдвать в поток.

          Тут задача в том, чтобы документ целиком не хранился в памяти, а сразу выдавался клиенту.
          • 0
            Это вам надо посмотреть тогда спецификацию servlets. Как это можно сделать. У меня файлы небольшие :)
            • 0
              Спасибо за совет. Сейчас правда уже пробуем выгружать в xml.
          • 0
            Этот пример делает подстановку строк вида ${color} в файле на значения из Model по ключу.


            В каком формате находятся файлы шаблонов документов в папке docx-представлений?

            Спрашиваю, потому что
            1) не получилось по вашему примеру запустить подстановку ${color}, находящегося в теле docx-документа шаблона.
            2) Сам год назад задавался проблемами процессинга docx, что в итоге вылилось в создание своей библиотеки для процессинга Groovy-скриптлетов внутри docx-документов. habrahabr.ru/post/142350/
            • 0
              В каком формате находятся файлы шаблонов документов в папке docx-представлений?

              В docx. Если что-то работает не так проверьте все что указано тут norguhtar.livejournal.com/62598.html
              И дружно скажем спасибо за счастливое детство microsoft.
              • 0
                О, вы приятно порадовали меня комментарием. Свое решение писал специально для обхода вот таких страданий с форматированием.

                Хорошо, что docx хоть xml-based, можно работать с этим нормально…
                • 0
                  Можно. Просто надо знать особенности генерации документа вордом и отключать то что нарушает нормальное представление документа. В odt с этим кстати намного лучше.
        • 0
          Мы делали проще. Свой view унаследованный от xslt на груви, который создает xml + стандартный шаблон из альтовы. работало кстати быстро. правда приходилось под каждый report писать свои xsd, создавать шаблоны и писать view.
          • 0
            Я бы не сказал что проще :)
            • 0
              Учитывая простоту груви и наитивность работы с xslt… =)
              Да — более громоздко. И больше сущностей. Но тем не менее)
          • 0
            Спасибо, пригодится.
            • –1
              Извините за занудство, но Spring НЕ относится к Java Enterprise Edition.

              Java EE (J2EE) — это конкретная платформа и набор стандартов от Sun/Oracle (http://www.oracle.com/technetwork/java/javaee/overview/index.html), с EJB, WAR, EAR, «косынкой» и бухгалтерами.

              Spring — это фреймворк для создания Enterprise Java приложений.

              Enterprise Java — это платформа и стандарты, а скорее, область применения и класс приложений. То есть, приложения для веба и больших предприятий.

              Да, в Спринге используются отдельные технологии из Java EE (сервлеты, JSP, и т.д.), но он не является имплементацией стандартов J2EE.
              • +1
                Пардон, рановато отправил.

                Spring framework был создан в пику J2EE, и не стоит помещать топики про спринг в блог Java Enterprise Edition (java2ee).

                • +1
                  Извините за занудство, но Spring НЕ относится к Java Enterprise Edition.
                  Я вот не знаю почему, но большая часть топиков про Spring находятся в JEE к примеру:
                  habrahabr.ru/blogs/java2ee/100984/
                  habrahabr.ru/blogs/java2ee/83860/

                  А так я отлично знаю, что НЕ относится.
                  • +1
                    Я не спорю, что на Хабре исторически так сложилось, но, может, еще не поздно это исправить? :) Не пора ли завести блог про Spring?
                    • 0
                      У меня нету такой кнопки :]

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.