简单说一下Jackson

如果想要详细了解一下Jackson,可以去其github上的项目主页查看其版本情况以及各项功能。除此以外,需要格外提一下Jackson的版本问题。Jackson目前主流版本有两种,1.x和2.x,而这二者的核心包是不同的。在2.0以后的Jackson版本中,groupId从原来的org.codehaus.jackson.core转而变成了com.fasterxml.jackson.core。所以如果希望用到新性能的Jackson,请将原来的maven依赖改为以下的依赖。

下文中的jackson-2-version是指特定的版本,当前的稳定版本为2.8.9。想要了解最新的版本情况还请去项目主页或是maven官网上查看。但是如果是要和spring mvc配合使用的话,还要注意一下他们之间的兼容问题。目前我采用的是4.2.6版本的springmvc和2.6.6版本的jackson

com.fasterxml.jackson.core
jackson-core
${jackson-2-version}

com.fasterxml.jackson.core
jackson-annotations
${jackson-2-version}

com.fasterxml.jackson.core
jackson-databind
${jackson-2-version}

com.fasterxml.jackson.dataformat
jackson-dataformat-smile
${jackson-2-version}

com.fasterxml.jackson.jaxrs
jackson-jaxrs-json-provider
${jackson-2-version}

com.fasterxml.jackson.module
jackson-module-jaxb-annotations
${jackson-2-version}
" title="" data-original-title="复制">

<!-- the core, which includes Streaming API, shared low-level abstractions (but NOT data-binding) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson-2-version}</version>
</dependency> <!-- Just the annotations; use this dependency if you want to

attach annotations to classes without connecting them to the code. -->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-annotations</artifactId>

<version>${jackson-2-version}</version>

</dependency> <!-- databinding; ObjectMapper, JsonNode and related classes are here -->

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>${jackson-2-version}</version>

</dependency> <!-- smile (binary JSON). Other artifacts in this group do other formats. -->

<dependency>

<groupId>com.fasterxml.jackson.dataformat</groupId>

<artifactId>jackson-dataformat-smile</artifactId>

<version>${jackson-2-version}</version>

</dependency>

<!-- JAX-RS provider -->

<dependency>

<groupId>com.fasterxml.jackson.jaxrs</groupId>

<artifactId>jackson-jaxrs-json-provider</artifactId>

<version>${jackson-2-version}</version>

</dependency>

<!-- Support for JAX-B annotations as additional configuration -->

<dependency>

<groupId>com.fasterxml.jackson.module</groupId>

<artifactId>jackson-module-jaxb-annotations</artifactId>

<version>${jackson-2-version}</version>

</dependency>

如果是在springmvc中配置maven依赖,则需要的依赖包为core,annotation和databind

从一个bug说起

刚开始上手springmvc的时候并没有详细去了解更多的JSON操作,只是简单的了解了一下如何将对象转化为JSON数据传送回前端。但是在这时出现了一个问题,就是当两个entity之间存在双向依赖时,传回的JSON数据会出现无限的读取情况。也就是说,因为两个实体中都存在着指向对方的指针,导致序列化的时候会出现二者之间不断相互访问的情况。hibernate这种实体设计方式一直让我有些困惑,毕竟在一般代码的设计模式中是应当尽量避免出现双向依赖的。


这里举一个具体的例子说明这个情况。
假设我有一个订单,订单中有多个商品。也就是订单和商品之间是一对多的关系。订单和商品的实体类如下:

订单实体类

import javax.persistence.*;

import java.util.ArrayList;

import java.util.Date;

import java.util.List;

/**

  • Created by rale on 7/15/17.

  • 销售单

    */

    @Entity

    @Table(name="sales_order")

    public class SalesOrder {

    @Id

    @Column(name = "sales_order_id")

    private Long salesOrderId;

    /订单创建人员/

    @Column(name = "salesman_id", nullable = false)

    private Long userId;

    @Temporal(TemporalType.TIMESTAMP)

    @Column(name = "sales_order_created_at")

    private Date createAt;

    /订单中商品列表清单/

    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

    @OrderBy(value = "order_item_id")

    @Fetch(FetchMode.JOIN)

    private List salesOrderItems;

    public Long getSalesOrderId() {

    return salesOrderId;

    }

    public void setSalesSource(SalesSource salesSource) {

    this.salesSource = salesSource;

    }

    public Long getUserId() {

    return userId;

    }

    public void setUserId(Long userId) {

    this.userId = userId;

    }

    public Date getCreateAt() {

    return createAt;

    }

    public void setCreateAt(Date createAt) {

    this.createAt = createAt;

    }

    public List getSalesOrderItems() {

    return salesOrderItems==null ? new ArrayList() : salesOrderItems;

    }

    public void setSalesOrderItems(List salesOrderItems) {

    this.salesOrderItems = salesOrderItems;

    }

    public void addSalesOrderItem(SalesOrderItem salesOrderItem){

    if (this.salesOrderItems == null) this.salesOrderItems = new ArrayList();

    salesOrderItem.setSalesOrder(this);

    this.salesOrderItems.add(salesOrderItem);

    }

}

" title="" data-original-title="复制">



import org.hibernate.annotations.Fetch;

import org.hibernate.annotations.FetchMode; import javax.persistence.*;

import java.util.ArrayList;

import java.util.Date;

import java.util.List; /**
  • Created by rale on 7/15/17.
  • 销售单

    */

    @Entity

    @Table(name="sales_order")

    public class SalesOrder { @Id

    @Column(name = "sales_order_id")

    private Long salesOrderId; /订单创建人员/

    @Column(name = "salesman_id", nullable = false)

    private Long userId; @Temporal(TemporalType.TIMESTAMP)

    @Column(name = "sales_order_created_at")

    private Date createAt; /订单中商品列表清单/

    @OneToMany(mappedBy = "salesOrder", cascade = CascadeType.ALL, orphanRemoval = true)

    @OrderBy(value = "order_item_id")

    @Fetch(FetchMode.JOIN)

    private List<SalesOrderItem> salesOrderItems; public Long getSalesOrderId() {

    return salesOrderId;

    } public void setSalesSource(SalesSource salesSource) {

    this.salesSource = salesSource;

    } public Long getUserId() {

    return userId;

    } public void setUserId(Long userId) {

    this.userId = userId;

    } public Date getCreateAt() {

    return createAt;

    } public void setCreateAt(Date createAt) {

    this.createAt = createAt;

    } public List<SalesOrderItem> getSalesOrderItems() {

    return salesOrderItems==null ? new ArrayList<SalesOrderItem>() : salesOrderItems;

    } public void setSalesOrderItems(List<SalesOrderItem> salesOrderItems) {

    this.salesOrderItems = salesOrderItems;

    } public void addSalesOrderItem(SalesOrderItem salesOrderItem){

    if (this.salesOrderItems == null) this.salesOrderItems = new ArrayList<SalesOrderItem>();

    salesOrderItem.setSalesOrder(this);

    this.salesOrderItems.add(salesOrderItem);

    }
}

订单商品实体类

/**

  • Created by rale on 7/15/17.

  • 销售清单中的单品和数量

    */

    @Entity

    @Table(name = "sales_order_item")

    public class SalesOrderItem {

    @Id

    @GeneratedValue

    @Column(name = "order_item_id")

    private Long salesOrderItemId;

    @Column(name = "order_item_quantity")

    private int quantity;

    @Column(name = "order_item_price")

    private double salesPrice;

    //对应的销售单实体类

    @ManyToOne

    @JoinColumn(name = "order_id")

    private SalesOrder salesOrder;

    public SalesOrderItem() {

    }

    public SalesOrderItem(Long salesOrderItemId){

    this.salesOrderItemId = salesOrderItemId;

    }

    public Long getSalesOrderItemId() {

    return salesOrderItemId;

    }

    public void setSalesOrderItemId(Long salesOrderItemId) {

    this.salesOrderItemId = salesOrderItemId;

    }

    public int getQuantity() {

    return quantity;

    }

    public void setQuantity(int quantity) {

    this.quantity = quantity;

    }

    public double getSalesPrice() {

    return salesPrice;

    }

    public void setSalesPrice(double salesPrice) {

    this.salesPrice = salesPrice;

    }

    public SalesOrder getSalesOrder() {

    return salesOrder;

    }

    public void setSalesOrder(SalesOrder salesOrder) {

    this.salesOrder = salesOrder;

    }

    }

    " title="" data-original-title="复制">

import javax.persistence.*;

/**
  • Created by rale on 7/15/17.
  • 销售清单中的单品和数量

    */

    @Entity

    @Table(name = "sales_order_item")

    public class SalesOrderItem {

    @Id

    @GeneratedValue

    @Column(name = "order_item_id")

    private Long salesOrderItemId; @Column(name = "order_item_quantity")

    private int quantity; @Column(name = "order_item_price")

    private double salesPrice; //对应的销售单实体类

    @ManyToOne

    @JoinColumn(name = "order_id")

    private SalesOrder salesOrder; public SalesOrderItem() {

    } public SalesOrderItem(Long salesOrderItemId){

    this.salesOrderItemId = salesOrderItemId;

    } public Long getSalesOrderItemId() {

    return salesOrderItemId;

    } public void setSalesOrderItemId(Long salesOrderItemId) {

    this.salesOrderItemId = salesOrderItemId;

    } public int getQuantity() {

    return quantity;

    } public void setQuantity(int quantity) {

    this.quantity = quantity;

    } public double getSalesPrice() {

    return salesPrice;

    } public void setSalesPrice(double salesPrice) {

    this.salesPrice = salesPrice;

    } public SalesOrder getSalesOrder() {

    return salesOrder;

    } public void setSalesOrder(SalesOrder salesOrder) {

    this.salesOrder = salesOrder;

    }

    }

解决双向依赖的方法如下:

@JsonIgnore

在不希望被序列化的field或property上使用@JsonIgnore标记,即可使该属性在序列化和解序列化的过程中不被访问。

...

/**订单中商品列表清单**/
@OneToMany(mappedBy = &quot;salesOrder&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy(value = &quot;order_item_id&quot;)
@Fetch(FetchMode.JOIN)
private List<SalesOrderItem> salesOrderItems;

...
//getters and setters

}

@Entity

@Table(name = "sales_order_item")

public class SalesOrderItem {

...

//对应的销售单实体类
@ManyToOne
@JoinColumn(name = &quot;order_id&quot;)
@JsonIgnore
private SalesOrder salesOrder; ...
//getters and setters

}" title="" data-original-title="复制">

@Entity

@Table(name="sales_order")

public class SalesOrder {
...

<span class="hljs-comment">/**订单中商品列表清单**/</span>
<span class="hljs-meta">@OneToMany(mappedBy = <span class="hljs-meta-string">"salesOrder"</span>, cascade = CascadeType.ALL, orphanRemoval = true)</span>
<span class="hljs-meta">@OrderBy(value = <span class="hljs-meta-string">"order_item_id"</span>)</span>
<span class="hljs-meta">@Fetch(FetchMode.JOIN)</span>
<span class="hljs-keyword">private</span> List&lt;SalesOrderItem&gt; salesOrderItems; ...
<span class="hljs-comment">//getters and setters</span>

}

@Entity

@Table(name = "sales_order_item")

public class SalesOrderItem {

...

<span class="hljs-comment">//对应的销售单实体类</span>
<span class="hljs-meta">@ManyToOne</span>
<span class="hljs-meta">@JoinColumn(name = <span class="hljs-meta-string">"order_id"</span>)</span>
<span class="hljs-meta">@JsonIgnore</span>
<span class="hljs-keyword">private</span> SalesOrder salesOrder; ...
<span class="hljs-comment">//getters and setters</span>

}

这里可能会出现不适用的场景,比如说,当我希望从SalesOrderItem的方向获取SalesOrder的数据,将会出现无法被序列化的情况。

这里需要特别强调一下 不要使用transient标记属性 会报错

@JsonManagedReference and @JsonBackReference.

@JsonManagedReference标记在父类对子类的引用变量上,并将@JsonBackReference标记在子类对父类的引用变量上。

...

/**订单中商品列表清单**/
@OneToMany(mappedBy = &quot;salesOrder&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy(value = &quot;order_item_id&quot;)
@Fetch(FetchMode.JOIN)
@JsonManagedReference
private List<SalesOrderItem> salesOrderItems;

...
//getters and setters

}

@Entity

@Table(name = "sales_order_item")

public class SalesOrderItem {

...

//对应的销售单实体类
@ManyToOne
@JoinColumn(name = &quot;order_id&quot;)
@JsonBackReference
private SalesOrder salesOrder; ...
//getters and setters

}" title="" data-original-title="复制">

@Entity

@Table(name="sales_order")

public class SalesOrder {
...

<span class="hljs-comment">/**订单中商品列表清单**/</span>
<span class="hljs-meta">@OneToMany(mappedBy = <span class="hljs-meta-string">"salesOrder"</span>, cascade = CascadeType.ALL, orphanRemoval = true)</span>
<span class="hljs-meta">@OrderBy(value = <span class="hljs-meta-string">"order_item_id"</span>)</span>
<span class="hljs-meta">@Fetch(FetchMode.JOIN)</span>
<span class="hljs-meta">@JsonManagedReference</span>
<span class="hljs-keyword">private</span> List&lt;SalesOrderItem&gt; salesOrderItems; ...
<span class="hljs-comment">//getters and setters</span>

}

@Entity

@Table(name = "sales_order_item")

public class SalesOrderItem {

...

<span class="hljs-comment">//对应的销售单实体类</span>
<span class="hljs-meta">@ManyToOne</span>
<span class="hljs-meta">@JoinColumn(name = <span class="hljs-meta-string">"order_id"</span>)</span>
<span class="hljs-meta">@JsonBackReference</span>
<span class="hljs-keyword">private</span> SalesOrder salesOrder; ...
<span class="hljs-comment">//getters and setters</span>

}

通过这种方式确保在双向关系中只有单个反向的实例被序列化

@JsonIdentityInfo

该annotation用于标注在entity上。当entity被标注后,jackson在每一次序列化的时候都会为该实例生成专门的ID(也可以是实例自带的属性),通过这种方式辨别实例。这种方式适用于存在一个实体关联链的场景。比如Order -> OrderLine -> User -> Order

...

/**订单中商品列表清单**/
@OneToMany(mappedBy = &quot;salesOrder&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy(value = &quot;order_item_id&quot;)
@Fetch(FetchMode.JOIN)
private List<SalesOrderItem> salesOrderItems;

...
//getters and setters

}

@Entity

@Table(name = "sales_order_item")

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")

public class SalesOrderItem {

...

//对应的销售单实体类
@ManyToOne
@JoinColumn(name = &quot;order_id&quot;)
private SalesOrder salesOrder; ...
//getters and setters

}" title="" data-original-title="复制">

@Entity

@Table(name="sales_order")

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")

public class SalesOrder {
...

<span class="hljs-comment">/**订单中商品列表清单**/</span>
<span class="hljs-meta">@OneToMany(mappedBy = <span class="hljs-meta-string">"salesOrder"</span>, cascade = CascadeType.ALL, orphanRemoval = true)</span>
<span class="hljs-meta">@OrderBy(value = <span class="hljs-meta-string">"order_item_id"</span>)</span>
<span class="hljs-meta">@Fetch(FetchMode.JOIN)</span>
<span class="hljs-keyword">private</span> List&lt;SalesOrderItem&gt; salesOrderItems; ...
<span class="hljs-comment">//getters and setters</span>

}

@Entity

@Table(name = "sales_order_item")

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")

public class SalesOrderItem {

...

<span class="hljs-comment">//对应的销售单实体类</span>
<span class="hljs-meta">@ManyToOne</span>
<span class="hljs-meta">@JoinColumn(name = <span class="hljs-meta-string">"order_id"</span>)</span>
<span class="hljs-keyword">private</span> SalesOrder salesOrder; ...
<span class="hljs-comment">//getters and setters</span>

}

@JsonIgnoreProperties 个人心中的全场最佳

@JsonIgnoreProperties不同于@JsonIgnore在于,你可以注明该变量中的哪个属相不被序列化。从而允许在双向访问上都不存在环或是缺失。

...

/**订单中商品列表清单**/
@OneToMany(mappedBy = &quot;salesOrder&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy(value = &quot;order_item_id&quot;)
@Fetch(FetchMode.JOIN)
@JsonIgnoreProperties(&quot;salesOrder&quot;)
private List<SalesOrderItem> salesOrderItems;

...
//getters and setters

}

@Entity

@Table(name = "sales_order_item")

public class SalesOrderItem {

...

//对应的销售单实体类
@ManyToOne
@JoinColumn(name = &quot;order_id&quot;)
@JsonIgnoreProperties(&quot;salesOrderItems&quot;)
private SalesOrder salesOrder; ...
//getters and setters

}" title="" data-original-title="复制">

@Entity

@Table(name="sales_order")

public class SalesOrder {
...

<span class="hljs-comment">/**订单中商品列表清单**/</span>
<span class="hljs-meta">@OneToMany(mappedBy = <span class="hljs-meta-string">"salesOrder"</span>, cascade = CascadeType.ALL, orphanRemoval = true)</span>
<span class="hljs-meta">@OrderBy(value = <span class="hljs-meta-string">"order_item_id"</span>)</span>
<span class="hljs-meta">@Fetch(FetchMode.JOIN)</span>
<span class="hljs-meta">@JsonIgnoreProperties(<span class="hljs-meta-string">"salesOrder"</span>)</span>
<span class="hljs-keyword">private</span> List&lt;SalesOrderItem&gt; salesOrderItems; ...
<span class="hljs-comment">//getters and setters</span>

}

@Entity

@Table(name = "sales_order_item")

public class SalesOrderItem {

...

<span class="hljs-comment">//对应的销售单实体类</span>
<span class="hljs-meta">@ManyToOne</span>
<span class="hljs-meta">@JoinColumn(name = <span class="hljs-meta-string">"order_id"</span>)</span>
<span class="hljs-meta">@JsonIgnoreProperties(<span class="hljs-meta-string">"salesOrderItems"</span>)</span>
<span class="hljs-keyword">private</span> SalesOrder salesOrder; ...
<span class="hljs-comment">//getters and setters</span>

}

半场小结

其实jackson中还有很多很实用的功能,例如如何将Date类序列化成界面展示的格式等,将在下一次更新中说明。有兴趣的可以收藏加关注哦。

其它教程传送门

springmvc + ajax 实现
http://www.mkyong.com/spring-...

jackson annotation教程
http://tutorials.jenkov.com/j...

stack overflow上相关问题回答
https://stackoverflow.com/que...
https://stackoverflow.com/que...
https://stackoverflow.com/que...

这里需要指出的是,虽然某些回答说,要将annotation标注在私有变量的get方法上,但是po主发现标注在私有变量上还是可以实现功能的。

data hiding using jsonignore and spring data jpa
https://dzone.com/articles/da...


想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注我的微信公众号!将会不定期的发放福利哦~

原文地址:https://segmentfault.com/a/1190000010246362

hibernate中因双向依赖而造成的json怪相--springmvc项目的更多相关文章

  1. Hibernate中双向的一对多关系

    何为双向,双向的意思就是你我之间可以互相通信(customer(1)和order(n)) 也就是说customer可以访问order,order也可以访问customer 二者构成了双向的关系 在Hi ...

  2. Hibernate中双向多对多的两种配置方式

    Hibernate中双向多对多的两种配置方式 1.建立多对多双向关联关系 package cn.happy.entitys; import java.util.HashSet; import java ...

  3. MySql和Hibernate中关于cascade的用法

    数据库里的cascade的用法,Mysql和Hibernate里面是不相同. 在数据库里,进行增加.修改.删除记录的时候,经常会涉及到父子关系的表. 例如:有省份表和城市表,其中城市表有一个外键pro ...

  4. hibernate 中文文档

    转载:http://blog.csdn.net/kevon_sun/article/details/42850387 Hibernate Annotations 参考文档 3.2.0 CR1 目录 前 ...

  5. hibernate多对一双向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  6. hibernate(七) hibernate中查询方式详解

    序言 之前对hibernate中的查询总是搞混淆,不明白里面具体有哪些东西.就是因为缺少总结.在看这篇文章之前,你应该知道的是数据库的一些查询操作,多表查询等,如果不明白,可以先去看一下 MySQL数 ...

  7. Hibernate框架之双向多对多关系映射

    昨天跟大家分享了Hibernate中单向的一对多.单向多对一.双向一对多的映射关系,今天跟大家分享下在Hibernate中双向的多对多的映射关系 这次我们以项目和员工举个栗子,因为大家可以想象得到,在 ...

  8. Hibernate中一级缓存和二级缓存使用详解

    一.一级缓存二级缓存的概念解释 (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个 session(一定要同一个ses ...

  9. hibernate中@Entity和@Table的区别

    Java Persistence API定义了一种定义,可以将常规的普通Java对象(有时被称作POJO)映射到数据库.这些普通Java对象被称作Entity Bean.除了是用Java Persis ...

随机推荐

  1. C#中的事件注册和注销

    C#中的事件注册和注销 由于.NET框架对消息循环机制进行了很好的封装,开发人员不再需要深入的了解Windows事件/消息实现的具体机制,也无需创建复杂的事件结构体和所谓的消息句柄.我们所要做的无非就 ...

  2. The method getTextContent() is undefined ?

    晚上下班的时候,把班上写了半截的代码带了回来.结果回到家后出乎意料的是回来的时候将代码导入eclipse后,下面这行代码就直接报错了,显示 getTextContent()未定义 . ((Elemen ...

  3. 关于Python缩进,我们该了解哪些?

    Python是一门独特的语言,它的代码块是通过缩进(Indentation)来标记的(大部分语言都是使用花括号作为代码块的标记),具有相同缩进的多行代码属于同一个代码块.如果代码莫名其妙的乱缩进,Py ...

  4. Linux 7.X 网络配置

    Linux 7.X 网络配置 环境: 笔记本中安装了虚拟机,在虚拟机中安装了Redhat 7.4版本的操作系统,现配置该操作系统网络.(IP.网关等) 相关指令如下: # nmcli connecti ...

  5. iView3.x Anchor(锚点)组件 导航锚点

    iView3.x Anchor(锚点)组件 导航锚点 iview 3.x框架中新添了一个Anchor(锚点组件),用这个组件去做页面的分类导航正好合适,但是苦于官方文档太过抽象研究了一整天,才勉强可以 ...

  6. 【有用】rn-viewpager中的IndicatorViewPager

    实现引导页滑动 //js/pages/GuidePage.js import {StyleSheet, View, Button, Text, Image, TouchableOpacity,Asyn ...

  7. 【51NOD1028】大数乘法 V2

    ╰( ̄▽ ̄)╭ 给出2个大整数A,B,计算A*B的结果. (A,B的长度 <= 100000,A,B >= 0) (⊙ ▽ ⊙) 把大整数A看做一个次数界为lenA的多项式A(x),其中x ...

  8. [Offer收割]编程练习赛104

    题目过于简单,没啥好说的,但是拿了个第一感觉很爽,记录一下 题目1 : 小Hi与魔法 排序,从1开始递增 #include <bits/stdc++.h> using namespace ...

  9. 如何在iPhone 显示一个 星级评分

    http://blog.csdn.net/fanjunxi1990/article/details/8663914 由于项目需求,需要做一个列表,里面有各个商品的评分,就是app store里面所有a ...

  10. 跨域知识(一)——CORS

    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从 ...