• Spring MVC Content Negotiation 转载


    Spring MVC Content Negotiation

    Spring MVC有两种方式生成output的方法:

    • 你可以使用restful式的@responsebody方法和HTTP消息转换器,通常是返回像JSON或XML这样的数据格式。编程客户端、移动应用程序和启用AJAX的浏览器都是常见的客户端。
    • 你也可以使用视图解析。尽管视图完全能够生成JSON和XML(在我的下一篇文章中有更多的内容),但视图通常用于为传统的web应用程序生成类似HTML的表示格式。
    • 实际上还有第三种可能——一些应用程序需要两者,而Spring MVC很容易支持这样的组合。我们会在最后回到这个问题上。

    在这两种情况下,你都需要处理由控制器返回的相同数据的多个表示(或视图)。计算出要返回的数据格式称为内容协商(Content Negotiation)。

    有三种情况下,我们需要知道在HTTP响应中发送哪种类型的数据格式:

    • HttpMessageConverters:确定要使用的正确的转换器。
    • Request Mappings:将传入的HTTP请求映射到返回不同格式的不同方法。
    • View Resolution:选择正确的视图来使用。

    确定用户请求的格式依赖于ContentNegotationStrategy。有默认的实现可用,但如果你愿意,也可以实现自己的实现。

    在这篇文章中,我想讨论如何配置和使用Spring的内容协商,主要是使用HTTP消息转换器的RESTful控制器。在另一篇博文中,我将展示如何设置专门为使用内容协商使用Spring的ContentNegotiatingViewResolver视图。

    1、内容协商如何工作

    这里写图片描述

    通过HTTP发出请求时,可以通过设置Accept header来指定你想要的响应类型。Web浏览器有这个预设来请求HTML(包括其他东西)。事实上,如果你看的话,你会发现浏览器实际上会发送非常混乱的Accept标头,这使得依赖它们变得不切实际。请参阅http://www.gethifi.com/blog/browser-rest-http-accept-headers的一个很好的讨论这个问题。Accept-header是混乱的,你也不能正常地改变它们(除非你使用JavaScript和AJAX)。

    因此,针对Accept-header这种不是令人满意的情况,Spring提供了一些可使用的约定。(这是Spring 3.2的一个很好的变化,它使一个灵活的内容选择策略在所有Spring MVC中都可以使用,而不仅仅是在使用视图的时候)。你可以集中地配置一次内容协商策略,并在需要确定不同格式(媒体类型)的地方应用它。

    2、Spring MVC 如何使用内容协商

    Spring支持两种约定来选择所需格式:URL后缀或者URL参数。这些工作与使用Accept header一起工作。因此,可以以三种方式请求内容类型。默认情况下,按以下顺序进行检查:

    • 在URL中添加路径扩展(后缀)。所以,如果传入的URL是http://myserver/myapp/accounts/list.html, HTML是必需的。对于一个电子表格应该是http://myserver/myapp/accounts/list.xls的URL。媒体类型映射的后缀是通过JavaBeans激活框架或JAF(即激活,自动定义的。jar必须在类路径上)。
    • 一个URL参数如下:http://myserver/myapp/accounts/list?format=xls。参数的名称是默认格式,但这可能会被更改。默认情况下,使用参数是禁用的,但是在启用时,它会被检查。
    • 最后,检查Accept HTTP报头属性。这就是HTTP实际上是如何定义的,但是,正如前面提到的,使用它是有问题的。

    设置这个的Java配置,看起来是这样的。只需通过它的配置器定制预定义的内容协商管理器。注意,MediaType助手类为大多数著名的media-types提供了预定义的常量。

    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {
    
      /**
        * Setup a simple strategy: use all the defaults and return XML by default when not sure. 
        */
      @Override
      public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_XML);
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当使用XML配置时,内容协商策略是最容易通过ContentNegotiationManagerFactoryBean设置:

       <!--
            Setup a simple strategy: 
               1. Take all the defaults.
               2. Return XML by default when not sure. 
           -->
      <bean id="contentNegotiationManager"
                 class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
           <property name="defaultContentType" value="application/xml" />
      </bean>
    
     <!-- Make this available across all of Spring MVC -->
     <mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ContentNegotiationManage通过设置一个实现了ContentNegotationStrategy来实现上面提到的PPA策略 (path extension, then parameter, then Accept header)。

    3、额外的配置选项

    在Java配置中,可以使用配置器上的方法完全定制策略:

    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {
    
      /**
        *  Total customization - see below for explanation.
        */
      @Override
      public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.favorPathExtension(false).
                favorParameter(true).
                parameterName("mediaType").
                ignoreAcceptHeader(true).
                useJaf(false).
                defaultContentType(MediaType.APPLICATION_JSON).
                mediaType("xml", MediaType.APPLICATION_XML).
                mediaType("json", MediaType.APPLICATION_JSON);
      }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在XML中,可以使用工厂bean上的方法来配置策略:

     <!-- Total customization - see below for explanation. -->
      <bean id="contentNegotiationManager"
                 class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
        <property name="favorPathExtension" value="false" />
        <property name="favorParameter" value="true" />
        <property name="parameterName" value="mediaType" />
        <property name="ignoreAcceptHeader" value="true"/>
        <property name="useJaf" value="false"/>
        <property name="defaultContentType" value="application/json" />
    
        <property name="mediaTypes">
            <map>
                <entry key="json" value="application/json" />
                <entry key="xml" value="application/xml" />
           </map>
        </property>
    </bean>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们在这两种情况下都做了什么:

    • 禁用路径扩展。注意,偏爱并不意味着使用一种方法来选择另一种方法,它只是启用或禁用它。检查的顺序总是路径扩展、参数、Accept标头。
    • 启用URL参数的使用,而不是使用默认的参数,格式,我们将使用mediaType。
    • 完全忽略Accept标头。如果您的大多数客户实际上都是web浏览器(通常通过AJAX进行REST调用),那么这通常是最好的方法。
    • 不要使用JAF,而是手动地指定媒体类型映射——我们只希望支持JSON和XML。

    4、用户帐户列表的例子

    这里写图片描述

    为了演示,我将一个简单的帐户清单应用程序作为我们的工作示例—屏幕截图显示了HTML中一个典型的帐户列表。完整的代码可以在Github上找到。

    要返回JSON或XML的帐户列表,我需要这样的控制器。现在,我们将忽略HTML生成方法。

    @Controller
    class AccountController {
        @RequestMapping(value="/accounts", method=RequestMethod.GET)
        @ResponseStatus(HttpStatus.OK)
        public @ResponseBody List<Account> list(Model model, Principal principal) {
            return accountManager.getAccounts(principal) );
        }
    
        // Other methods ...
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    下面是内容协商策略的设置:

        <!-- Simple strategy: only path extension is taken into account -->
        <bean id="cnManager"
            class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
            <property name="favorPathExtension" value="true"/>
            <property name="ignoreAcceptHeader" value="true" />
            <property name="defaultContentType" value="text/html" />
            <property name="useJaf" value="false"/>
    
            <property name="mediaTypes">
                <map>
                    <entry key="html" value="text/html" />
                    <entry key="json" value="application/json" />
                    <entry key="xml" value="application/xml" />
                </map>
            </property>
        </bean>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    或者,使用Java配置,代码看起来如下:

        @Override
        public void configureContentNegotiation(
                ContentNegotiationConfigurer configurer) {
            // Simple strategy: only path extension is taken into account
            configurer.favorPathExtension(true).
                ignoreAcceptHeader(true).
                useJaf(false).
                defaultContentType(MediaType.TEXT_HTML).
                mediaType("html", MediaType.TEXT_HTML).
                mediaType("xml", MediaType.APPLICATION_XML).
                mediaType("json", MediaType.APPLICATION_JSON);
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果我在类路径上有JAXB2和Jackson,Spring MVC将自动设置必要的httpmessageconverter。我的domain class还必须使用JAXB2和Jackson注释来进行转换(否则消息转换器不知道该做什么)。为了响应注释(下面),注释的Account类如下所示。

    /**
     * Represents an account for a member of a financial institution. An account has
     * zero or more {@link Transaction}s and belongs to a {@link Customer}. An aggregate entity.
     */
    @Entity
    @Table(name = "T_ACCOUNT")
    @XmlRootElement
    public class Account {
    
        // data-members omitted ...
    
        public Account(Customer owner, String number, String type) {
            this.owner = owner;
            this.number = number;
            this.type = type;
        }
    
        /**
         * Returns the number used to uniquely identify this account.
         */
        @XmlAttribute
        public String getNumber() {
            return number;
        }
    
        /**
         * Get the account type.
         * 
         * @return One of "CREDIT", "SAVINGS", "CHECK".
         */
        @XmlAttribute
        public String getType() {
            return type;
        }
    
        /**
         * Get the credit-card, if any, associated with this account.
         * 
         * @return The credit-card number or null if there isn't one.
         */
        @XmlAttribute
        public String getCreditCardNumber() {
            return StringUtils.hasText(creditCardNumber) ? creditCardNumber : null;
        }
    
        /**
         * Get the balance of this account in local currency.
         * 
         * @return Current account balance.
         */
        @XmlAttribute
        public MonetaryAmount getBalance() {
            return balance;
        }
    
    
        /**
         * Returns a single account transaction. Callers should not attempt to hold
         * on or modify the returned object. This method should only be used
         * transitively; for example, called to facilitate reporting or testing.
         * 
         * @param name
         *            the name of the transaction account e.g "Fred Smith"
         * @return the beneficiary object
         */
        @XmlElement   // Make these a nested <transactions> element
        public Set<Transaction> getTransactions() {
            return transactions;
        }
    
        // Setters and other methods ...
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    下面是我们的帐户应用程序的JSON输出(请注意URL中的路径扩展)。

    这里写图片描述

    系统如何知道要转换为XML还是JSON?因为内容协商(PPA策略)的任何一个选项将使用上面讨论取决于ContentNegotiationManager是如何配置的。在本例中,URL以帐户结束。json因为路径扩展是唯一启用的策略。

    在示例代码中,您可以通过在web.XML中设置一个活动概要文件,在XML或Java配置之间切换。这些概要文件分别是“xml”和“javaconfig”。

    5、组合数据和表示格式

    Spring MVC的REST支持构建在现有的MVC控制器框架之上。因此,可以使用相同的web应用程序返回信息,既包括原始数据(如JSON),也可以使用表示格式(如HTML)。

    这两种技术都可以很容易地在同一个控制器中同时使用,就像这样:

    @Controller
    class AccountController {
        // RESTful method
        @RequestMapping(value="/accounts", produces={"application/xml", "application/json"})
        @ResponseStatus(HttpStatus.OK)
        public @ResponseBody List<Account> listWithMarshalling(Principal principal) {
            return accountManager.getAccounts(principal);
        }
    
        // View-based method
        @RequestMapping("/accounts")
        public String listWithView(Model model, Principal principal) {
            // Call RESTful method to avoid repeating account lookup logic
            model.addAttribute( listWithMarshalling(principal) );
    
            // Return the view to use for rendering the response
            return ¨accounts/list¨;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这里有一个简单的模式:@responsebody方法处理所有数据访问和与底层服务层(AccountManager)的集成。第二个方法调用第一个方法,并在模型中设置响应,以供视图使用。这就避免了重复的逻辑。

    为了确定要选择的两个@requestmapping方法中的哪一个,我们再次使用了我们的PPA内容协商策略。它允许生产选项工作。url以账户。xml或账户。json映射到第一个方法,任何在帐户中结束的url。任何东西都映射到第二个。

    6、另一种方法

    或者,如果我们使用视图来生成所有可能的内容类型,那么我们可以只使用一个方法来完成整个事情。这是ContentNegotiatingViewResolver的由来,那将是我的下一篇文章的主题。

    文章地址:content-negotiation-using-spring-mvc

  • 相关阅读:
    谈对信息增益与决策树的理解
    k近邻法
    感知机相关难点细解
    点到空间中面的距离
    统计学习方法中的标注问题
    Hoeffding不等式与泛化误差上界
    经验风险与期望风险
    先验概率与后验概率
    spring和springboot常用注解总结
    多环境下读取不同的配置文件
  • 原文地址:https://www.cnblogs.com/Jeely/p/10769959.html
Copyright © 2020-2023  润新知