【问题标题】:Spring 3.2 with MVC, ContentNegotation, REST and PDF Generator带有 MVC、ContentNegotation、REST 和 PDF 生成器的 Spring 3.2
【发布时间】:2013-10-01 07:39:42
【问题描述】:

假设我有一个 REST 样式的控制器映射

@RequestMapping(value="users", produces = {MediaType.APPLICATION_JSON_VALUE})
public List<User> listUsers(@ReqestParams Integer offset, @ReqestParams Integer limit, @ReqestParams String query) {
    return service.loadUsers(query, offset, limit);
}

提供 JSON(甚至 XML)不是问题,使用 ContentNegotation 和 MessageConverters 很容易

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="true" />
    <property name="favorParameter" value="false" />
    <property name="ignoreAcceptHeader" value="false" />
    <property name="mediaTypes" >
        <value>
            html=text/html
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

现在,我需要添加对 PDF 的支持。自然,我想尽可能地使用(Spring)MVC + REST。我发现的大多数示例都使用不使用 REST 样式的显式定义来实现这一点,例如

@RequestMapping(value="users", produces = {"application/pdf"})
public ModelAndView listUsersAsPdf(@ReqestParams Integer offset, @ReqestParams Integer limit, @ReqestParams String query) {
    List<User> users = listUsers(offset, limit, query); // delegated
    return new ModelAndView("pdfView", users);
}

这可行,但不是很舒服,因为对于每个备用输出(PDF、Excel、...),我都会添加一个请求映射。

我已经在内容协商解析器中添加了application/pdf;不幸的是,任何带有后缀.pdf 或Accept-Header application/pdf 的请求都会以406 响应。

REST/MVC 样式模式集成 PDF 等替代输出的理想设置是什么?

【问题讨论】:

  • 我可能会产生误解,但是您是否希望在任何时候对该 URL 的请求带有与 PDF 兼容的 Accept 标头时提供 PDF 文件?我认为您只想在某些 URL 上提供 PDF 文件,并且可能是与 JSON/REST API 不同的 URL。
  • 嗯,基本上,这个想法确实是使用accept-header(实际上是扩展名)来请求pdf。为什么不?它将遵循内容协商模式,并且资源只会存在一次。
  • 我认为将 application/pdf 添加到 ContentNegotiationManagerFactoryBean 是正确的第一步,但之后您需要添加一个能够生成 PDF 文件的 ViewResolver。你也这样做了吗?

标签: spring rest pdf spring-mvc model-view-controller


【解决方案1】:

您可以像下面这样创建一个 WEB-INF/spring/pdf-beans.xml。

 <bean id="listofusers" class="YourPDFBasedView"/>

您的控制器方法将返回视图名称作为用户列表。

@RequestMapping(value="users")
public ModelAndView listUsersAsPdf(@ReqestParams Integer offset, @ReqestParams Integer limit, @ReqestParams String query) {
    List<User> users = listUsers(offset, limit, query); // delegated
    return new ModelAndView("listofusers", users);
}

而且你可以这样使用 contentNegotiationViewResolver:

 <bean class="org.springframework.web.servlet.view.XmlViewResolver">
            <property name="order" value="1"/>
            <property name="location" value="WEB-INF/spring/pdf-views.xml"/>
        </bean>

<!--
        View resolver that delegates to other view resolvers based on the content type
    -->
    <bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
       <!-- All configuration is now done by the manager - since Spring V3.2 -->
       <property name="contentNegotiationManager" ref="cnManager"/>
    </bean>

    <!--
        Setup a simple strategy:
           1. Only path extension is taken into account, Accept headers are ignored.
           2. Return HTML by default when not sure.
     -->
    <bean id="cnManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
        <property name="ignoreAcceptHeader" value="true"/>        
        <property name="defaultContentType" value="text/html" />
    </bean>

对于 JSON:创建如下所示的通用 JSON 视图解析器,并将其注册为上下文文件中的 bean。

public class JsonViewResolver implements ViewResolver {
    /**
      * Get the view to use.
      *
      * @return Always returns an instance of {@link MappingJacksonJsonView}.
     */
    @Override
    public View resolveViewName(String viewName, Locale locale) throws Exception {
        MappingJacksonJsonView view = new MappingJacksonJsonView();
        view.setPrettyPrint(true);      // Lay the JSON out to be nicely readable 
        return view;
    }
}

XML 也一样:

public class MarshallingXmlViewResolver implements ViewResolver {

    private Marshaller marshaller;

    @Autowired
    public MarshallingXmlViewResolver(Marshaller marshaller) {
        this.marshaller = marshaller;
    }

    /**
     * Get the view to use.
     * 
     * @return Always returns an instance of {@link MappingJacksonJsonView}.
     */
    @Override
    public View resolveViewName(String viewName, Locale locale)
            throws Exception {
        MarshallingView view = new MarshallingView();
        view.setMarshaller(marshaller);
        return view;
    }
}

并在上下文文件中注册上述 xml 视图解析器,如下所示:

<oxm:jaxb2-marshaller id="marshaller" >
        <oxm:class-to-be-bound name="some.package.Account"/>
        <oxm:class-to-be-bound name="some.package.Customer"/>
        <oxm:class-to-be-bound name="some.package.Transaction"/>
    </oxm:jaxb2-marshaller>

    <!-- View resolver that returns an XML Marshalling view. -->
    <bean class="some.package.MarshallingXmlViewResolver" >
        <constructor-arg ref="marshaller"/>
    </bean>

您可以在此链接中找到更多信息: http://spring.io/blog/2013/06/03/content-negotiation-using-views/

使用所有视图解析器技术,您可以避免在控制器中编写重复的方法,例如一个用于 xml/json,另一个用于 excel,另一个用于 pdf,另一个用于 doc,rss 和所有。

【讨论】:

  • 您好,谢谢您的回答,我等了很久。不幸的是,这不是我要找的东西。使用您的解决方案,eiter 我仍然有两个控制器绑定(一个带有 @ResponseBody List&lt;&gt; listUsers() 和一个 MAV listUsersAsXY()),或者我将只有使用 JSON ViewResolver 但实际上不使用 @ResponseBody 的 MAV 变体(我必须使用)。你和我一起去吗?
  • 为什么不在所有情况下都返回 MAV,它也会处理您的 @ResponseBody 要求。我这里有一个示例配置。 punitusingh.wordpress.com/2014/03/07/…,对解析器进行排序的基本思路。
  • 当你返回一个视图时,你的 json 响应将由 org.springframework.web.servlet.view.json.MappingJacksonJsonView 类处理,而 xml 响应将由 org.springframework.web 处理.servlet.view.xml.MarshallingView 类,如我上面评论中的给定链接所示。对于 pdf,您需要定义自己的视图解析器,就像我定义 pdfResolver bean 一样。
【解决方案2】:

Knalli,如果您将@ResponseBody 替换为 ModelAndView(),您可以同时实现这两个功能。 您有什么理由要保留 @ResponseBody 吗?我只是想知道我是否缺少任何东西,只是想学习。

其他选项是编写 HttpMessageConverters 然后: 一些样品在这里。

Custom HttpMessageConverter with @ResponseBody to do Json things

http://www.javacodegeeks.com/2013/07/spring-mvc-requestbody-and-responsebody-demystified.html

【讨论】:

  • 我试图通过内容协商来实现这一切;转换器不适用于 MAV?当我返回一个 MAV 时,我会定义一个实际上已经特定于情况的视图。
【解决方案3】:

这是工作示例。我为此配置了 contentnegotiationviewresolver,并给出了最高的顺序。之后,我有用于 JSTL 和 Tiles View 的 ResourceBundleViewResolver,然后是用于 excelResolver、pdfResolver、rtfResolver 的 XmlViewResolver。 excelResolver、pdfResolver、rtfResolver。 XmlViewResolver 和 ResourceBundleViewResolver 仅适用于 MAV,但 MappingJacksonJsonView 和 MarshallingView 负责 MAV 和 @ResponseBody 返回值。

<bean id="contentNegotiatingResolver" class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="order"
                  value="#{T(org.springframework.core.Ordered).HIGHEST_PRECEDENCE}" />
<property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
                <entry key="xml" value="application/xml" />
                <entry key="pdf" value="application/pdf" />
                <entry key="xlsx" value="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" />
                <entry key="doc" value="application/msword" />
            </map>
        </property>

        <property name="defaultViews">
            <list>
                <!-- JSON View -->
                <bean class="org.springframework.web.servlet.view.json.MappingJacksonJsonView" />

                <!-- XML View -->
                <bean class="org.springframework.web.servlet.view.xml.MarshallingView">
                        <constructor-arg>
                            <bean id="jaxbMarshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
                            <property name="classesToBeBound">
                            <list>
                                <value>Employee</value>
                                <value>EmployeeList</value>
                            </list>
                            </property>
                            </bean>
                        </constructor-arg>
            </bean>
            </list>
        </property>
        <property name="ignoreAcceptHeader" value="true" />
 </bean>
<bean class="org.springframework.web.servlet.view.ResourceBundleViewResolver"
        id="resourceBundleResolver">
        <property name="order" value="#{contentNegotiatingResolver.order+1}" />
    </bean>
<bean id="excelResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
       <property name="location">
           <value>/WEB-INF/tiles/spring-excel-views.xml</value>
       </property>
       <property name="order" value="#{resourceBundleResolver.order+1}" />
    </bean>

    <bean id="pdfResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
       <property name="location">
           <value>/WEB-INF/tiles/spring-pdf-views.xml</value>
       </property>
       <property name="order" value="#{excelResolver.order+1}" />
    </bean>

    <bean id="rtfResolver" class="org.springframework.web.servlet.view.XmlViewResolver">
       <property name="location">
           <value>/WEB-INF/tiles/spring-rtf-views.xml</value>
       </property>
       <property name="order" value="#{excelResolver.order+1}" />
    </bean>

而我们的 XMLViewResolver spring-pdf-views.xml 看起来像这样。

<bean id="employees"
        class="EmployeePDFView"/>

EmployeePDFView 将具有生成 pdf 和在 Response 对象上写入 pdf 字节流的代码。这将解析为以 .pdf 扩展名结尾的 url,以及当您返回带有“员工”ID 的 MAV 时。

【讨论】:

    猜你喜欢
    • 2016-02-19
    • 2015-12-10
    • 2013-03-16
    • 1970-01-01
    • 2016-03-20
    • 1970-01-01
    • 2016-03-05
    • 2015-06-15
    • 1970-01-01
    相关资源
    最近更新 更多