Spring實戰(zhàn)(十六)-使用Spring MVC創(chuàng)建REST API

本文基于《Spring實戰(zhàn)(第4版)》所寫目溉。

Rest含義:

  • 表述性(Representational):REST資源實際上可以用各種形式來進(jìn)行表述,包括XML、JSON(JavaScript Object Notation)甚至HTML—最適合資源使用者的任意形式;
  • 狀態(tài)(State):當(dāng)使用REST的時候永部,我們更關(guān)注資源的狀態(tài)而不是對資源采取的行為;
  • 轉(zhuǎn)移(Transfer):REST涉及到轉(zhuǎn)移資源數(shù)據(jù)呐矾,它以某種表述性形式從一個應(yīng)用轉(zhuǎn)移到另一個應(yīng)用苔埋。

REST的動作(HTTP的方法)以及匹配的CRUD動作:

  • Create: POST
  • Read: GET
  • Update: PUT或PATCH
  • DELETE: DELETE

Spring支持以下方式來創(chuàng)建REST資源:

  • 控制器可以處理所有的HTTP方法,包含四個主要的REST方法:GET蜒犯、PUT组橄、DELETE以及POST。Spring 3.2及以上版本還支持PATCH方法罚随;
  • 借助@PathVariable注解玉工,控制器能夠處理參數(shù)化的URL(將變量輸入作為URL的一部分);
  • 借助Spring的視圖和視圖解析器淘菩,資源能夠以多種方式進(jìn)行表述遵班,包括將模型數(shù)據(jù)渲染為XML、JSON潮改、Atom以及RSS的View實現(xiàn)狭郑;
  • 可以使用ContentNegotiatingViewResolver來選擇最適合客戶端的表述;
  • 借助@ResponseBody注解和和各種HttpMethodConverter實現(xiàn)汇在,能夠替換基于視圖的渲染方式翰萨;
  • 類似地,@RequestBody注解以及HttpMethodConverter實現(xiàn)可以將傳入的HTTP數(shù)據(jù)轉(zhuǎn)化為傳入控制器處理方法的Java對象糕殉;
  • 借助RestTemplate亩鬼,Spring應(yīng)用能夠方便地使用Rest資源。

Spring提供了兩種方法將資源的Java表述轉(zhuǎn)換為發(fā)送給客戶端的表述形式:

  • 內(nèi)容協(xié)商(Content negotiation):選擇一個視圖阿蝶,它能夠?qū)⒛P弯秩緸槌尸F(xiàn)給客戶端表述形式雳锋。不過由于它只能決定資源該如何渲染到客戶端,并沒有涉及到客戶端要發(fā)送什么樣的表述給控制器使用羡洁,比如客戶端發(fā)送JSON或XML玷过,它就無法提供幫助了。而且還有其他限制,不建議使用冶匹。
  • 消息轉(zhuǎn)換器(Message conversion):通過一個消息轉(zhuǎn)換器將控制器所返回的對象轉(zhuǎn)換為呈現(xiàn)給客戶端的表述形式习劫。

使用HTTP信息轉(zhuǎn)換器

當(dāng)使用消息轉(zhuǎn)換功能時咆瘟,DispatcherServlet不再將模型數(shù)據(jù)傳送到視圖中嚼隘。實際上,根本就沒有模型袒餐,也沒有視圖飞蛹,只有控制器產(chǎn)生的數(shù)據(jù),以及消息轉(zhuǎn)換器轉(zhuǎn)換數(shù)據(jù)之后所產(chǎn)生的資源表述灸眼。

Spring自帶了各種各樣的轉(zhuǎn)換器卧檐,比如客戶端通過請求的Accept頭信息表明它能接受“application/json”,并且Jackson JSON在類路徑下焰宣,那么處理方法返回的對象將交給MappingJacksonHttpMessageConverter,并由它轉(zhuǎn)換為返回客戶端的JSON表述形式霉囚。大部分轉(zhuǎn)換器都是自動注冊的,不需要Spring配置匕积。但是為了支持它們盈罐,需要添加一些庫到應(yīng)用程序的類路徑下。

如果使用了消息轉(zhuǎn)換功能的話闪唆,我們需要告訴Spring跳過正常的模型/視圖流程盅粪,并使用消息轉(zhuǎn)換器。最簡單的方式是為控制器方法添加@ResponseBody注解悄蕾。例如票顾,如下程序:

@RequestMapping(method=RequestMethod.GET, produces="application/json")
public @ResponseBody List<Spittle> spittles (
@RequestParam(value="max",defaultValue=MAX_LONG_AS_SPRING)) long max,
@RequestParam(value="count",defaultValue="20") int count) {
      return spittleRepository.findSpittles(max, count);
}

@ResponseBody注解會告知Spring,我們要將返回的對象作為資源發(fā)送給客戶端帆调,并將其轉(zhuǎn)換為客戶端可接受的表述形式奠骄。更具體地講,DispatcherServlet將會考慮到請求中Accept頭部信息番刊,并查找能夠為客戶端提供所需表述形式的消息轉(zhuǎn)換器(根據(jù)類路徑下實現(xiàn)庫)戚揭。

需要注意的是,默認(rèn)情況下撵枢,Jackson JSON庫在將返回的對象轉(zhuǎn)換為JSON資源表述時民晒,會使用反射。如果重構(gòu)了Java類型锄禽,比如添加潜必、移除或重命名屬性,那么產(chǎn)生的JSON也將會發(fā)生變化沃但。但是磁滚,我們可以在Java類型上使用Jackson的映射注解,改變產(chǎn)生JSON的行為。

談及Accept頭部信息垂攘,在@RequestMapping注解中维雇,我們使用了produces屬性表明這個方法只處理預(yù)期輸出為JSON的請求,其他任何類型的請求晒他,都不會被這個方法處理吱型。這樣的請求會被其他的方法來進(jìn)行處理,或者返回客戶端HTTP 406響應(yīng)陨仅。

與@ResponseBody類似津滞,@RequestBody也能告訴Spring查找一個消息轉(zhuǎn)換器,將來自客戶端的資源表述為對象灼伤。例如:

@RequestMapping(method=RequestMethod.POST, consumes="application/json")
public @ResponseBody Spittle saveSpittle(@RequestBody Spittle spittle) {
    return spittleRepository.save(spittle);
}

通過使用注解触徐,@RequestMapping表明它只能處理“/spittles”(在類級別的@RequestMapping中進(jìn)行了聲明)的POST請求。POST請求體中預(yù)期要包含一個Spittle的資源表述狐赡。因為Spittle參數(shù)上使用了@RequestBody撞鹉,所以Spring將會查看請求中的Content-Type頭部信息,并查找能夠?qū)⒄埱筠D(zhuǎn)換為Spittle的消息轉(zhuǎn)換器颖侄。

例如鸟雏,如果客戶端發(fā)送的Spittle數(shù)據(jù)是JSON表述形式,那么Content-Type頭部信息可能就會是“application/json”发皿。在這種情況下崔慧,DispatcherServlet會查找能夠?qū)SON轉(zhuǎn)換為Java對象的消息轉(zhuǎn)換器。

注意穴墅,@RequestMapping有一個consumes屬性惶室,我們將其設(shè)置為“application/json”。consumes屬性的工作方式類似于produces玄货,不過它會關(guān)注請求的Content-Type頭部信息皇钞。它會告訴Spring這個方法只會處理對“/spittles”的POST請求,并且要求請求的Content-Type頭部信息為“application/json”松捉。如果無法滿足這些條件的話夹界,會有其他方法來處理請求。

Spring 4.0引入了@RestController注解隘世。如果在控制器類上使用@RestController來代替@Controller的話可柿,Spring將會為該控制器的所有處理方法應(yīng)用消息轉(zhuǎn)換功能。我們不必為每個方法都添加@ResponseBody了丙者。添加@RestController注解复斥,此類中所有處理器方法都不需要使用@ResponseBody注解了,因為控制器使用了@RestController械媒,所有它的方法所返回的對象將會通過消息轉(zhuǎn)換機(jī)制目锭,產(chǎn)生客戶端所需的資源表述评汰。

發(fā)送錯誤信息到客戶端

如果一個處理器方法本應(yīng)返回一個對象,但由于查找不到相應(yīng)的對象而返回null痢虹。我們考慮一下在這種場景下應(yīng)該發(fā)生什么被去。至少,狀態(tài)碼不應(yīng)是200奖唯,而應(yīng)該是404惨缆,告訴客戶端它們所要求的內(nèi)容沒有找到。如果響應(yīng)體中能夠包含錯誤信息而不是空的話就更好了臭埋。

Spring提供了多種方式來處理這樣的場景:

  • 使用@ResponseStatus注解可以指定狀態(tài)碼踪央;
  • 控制器方法可以返回ResponseEntity對象臀玄,該對象能夠包含更多響應(yīng)相關(guān)的元數(shù)據(jù)瓢阴;
  • 異常處理器能夠應(yīng)對錯誤場景,這樣處理器方法就能關(guān)注于正常的狀況健无。

使用ResponseEntity

作為@ResponseBody的替代方案荣恐,控制器方法可以返回一個ResponseEntity對象。ResponseEntity中可以包含響應(yīng)相關(guān)的元數(shù)據(jù)(如頭部信息和狀態(tài)碼)以及要轉(zhuǎn)換成資源表述的對象累贤。

@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Spittle> spittleById(@PathVariable long id) {
    Spittle spittle = spittleRepository.findOne(id);
    HttpStatus status = spittle != null ? HttpStatus.OK : HttpStatus.NOT_FOUND;
    return new RepositoryEntity<Spittle>(spittle, status);
}

注意叠穆,如果返回ResponseEntity的話,那就沒有必要在方法上使用@ResponseBody注解了臼膏。

如果我們希望在響應(yīng)體中包含一些錯誤信息硼被。我們需要定義一個包含錯誤信息的Error對象:

public class Error {
    private int code;
    private String message;
    
    public Error(int code ,String message) {
        this.code = code;
        this.message = message;
    }

    public String getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

然后,我們可以修改spittleById()渗磅,讓它返回Error:

@RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<?> spittleById(@PathVariable long id ) {
    Spittle spittle = spittleRepository.findOne(id);
    if (spittle == null) {
        Error error = new Error(4, "Spittle [" + id + "] not found");
        return new ResponseEntity<Error> (error, HttpStatus.NOT_FOUND);
    }
    return new ResponseEntity<Spittle>(spittle, HttpStatus.OK);
}

處理錯誤

我們重構(gòu)一下代碼來使用錯誤處理器嚷硫。首先,定義能夠?qū)ο骃pittleNotFoundException的錯誤處理器:

@ExceptionHandler(SpittleNotFoundException.class)
public ResponseEntity<Error> spittleNotFound(SpittleNotFoundException e) {
    long spittleId = e.getSpittleId();
    Error error = new Error(4, "Spittle [" + spittleId + "] not found");
    return new ResponseEntity<Error> (error, HttpStatus.NOT_FOUND);
}

@ExceptionHandler注解能夠用到控制器方法中始鱼,用來處理特定的異常仔掸。至于SpittleNotFoundException,它是一個很簡單異常類:

public class SpittleNotFoundException extends RuntimeException {
    private long spittleId;
    public SpittleNotFoundException(long spittleId) {
        this.spittleId = spittleId;
    }

    public long getSpittleId() {
        return spittleId;
    }
}

現(xiàn)在医清,我們可以移除掉spittleById() 方法中大多數(shù)的錯誤代碼:

@RequestMapping(value="/{id}" , method=RequestMethod.GET)
public ResponseEntity<Spittle> spittleById(@PathVariable long id) {
    Spittle spittle = spittleRepository.findOne(id);
    if (spittle == null) { throw new SpittleNotFoundException(id); }
    return new ResponseEntity<Spittle>(spittle, HttpStatus.OK);
}

更簡潔的版本是(控制器類上使用@RestController)

@RequestMapping(value="/{id}" , method=RequestMethod.GET)
public Spittle spittleById(@PathVariable long id) {
    Spittle spittle = spittleRepository.findOne(id);
    if (spittle == null) { throw new SpittleNotFoundException(id); }
    return spittle;
}

鑒于錯誤處理器的方法會始終返回Error起暮,并且HTTP狀態(tài)碼為404,那么現(xiàn)在我們可以對spittleNotFound() 方法進(jìn)行類似的清理(控制器類上使用@RestController):

@ExceptionHandler(SpittleNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
    long spittleId = e.getSpittleId();
    return  Error error = new Error(4, "Spittle [" + spittleId + "] not found");
}

在響應(yīng)中設(shè)置頭部信息

如果我們需要在POST請求后会烙,返回201且把資源的URL返回給客戶端负懦,可以用@ResponseEntity實現(xiàn)

@RequestMapping(method=RequestMethod.POST, consumes="application/json")
public ResponseEntity<Spittle> saveSpittle(@RequestBody Spittle spittle) {
    Spittle spittle = spittleRepository.save(spittle);
    HttpHeaders headers = new HttpHeaders();
    URI locationUri = URI.create("http://localhost:8080/spittr/spittles/" + spittle.getId());
    headers.setLocation(locationUri);
    ResponseEntity<Spittle> responseEntity = 
                new ResponseEntity<Spittle>(spittle, headers, HttpStatus.CREATED);
    return responseEntity;
}

其實我們沒有必要手動構(gòu)建URL,Spring 提供了UriComponentsBuilder柏腻。它是一個構(gòu)建類纸厉,通過逐步指定URL中的各種組成部分(如host、端口葫盼、路徑以及查詢)残腌,我們能夠使用它來構(gòu)建UriComponents實例。

為了使用UriComponentsBuilder,我們需要做的就是在處理器方法中將其作為一個參數(shù)抛猫,如下面的程序清單所示蟆盹。

@RequestMapping(method=RequestMethod.POST, consumes="application/json")
public ResponseEntity<Spittle> saveSpittle(@RequestBody Spittle spittle,
                               UriComponentsBuilder ucb) {
    Spittle spittle = spittleRepository.save(spittle);
    HttpHeaders headers = new HttpHeaders();
    URI locationUri = ucb.path("/spittles/").path(String.valueOf(spittle.getId()))
                                .build().toUri();
    headers.setLocation(locationUri);
    ResponseEntity<Spittle> responseEntity = 
                new ResponseEntity<Spittle>(spittle, headers, HttpStatus.CREATED);
    return responseEntity;
}

在處理器方法所得到的UriComponentsBuilder中,會預(yù)先配置已知的信息如host闺金、斷端口以及Servlet內(nèi)容逾滥。

注意,路徑的構(gòu)建分為兩步败匹。第一步調(diào)用path()方法寨昙,將其設(shè)置“/spittles/”,也就是這個控制器所能處理的基礎(chǔ)路徑掀亩。然后舔哪,在第二次調(diào)用path()的時候,使用了已使用Spittle的ID槽棍。在路徑設(shè)置完成之后捉蚤,調(diào)用build()方法來構(gòu)建UriComponents對象,根據(jù)這個對象調(diào)用toUri()就能得到新創(chuàng)建Spittle的URI炼七。

了解RestTemplate的操作

RestTemplate可以減少我們使用HttpClient創(chuàng)建客戶端所帶來的樣板式代碼缆巧。它定義了36個(只有11個獨立方法,其他都是重載這些方法)與REST資源交互的方法豌拙,其中的大多數(shù)都對應(yīng)于HTTP的方法陕悬。下表展示了這11個獨立方法

方法 描述
delete() 在特定的URL上對資源執(zhí)行HTTP DELETE操作
exchange() 在URL上執(zhí)行特定的HTTP方法,返回包含對象的ResponseEntity按傅,這個對象是從響應(yīng)體中映射得到的
execute() 在URL上執(zhí)行特定的HTTP方法捉超,返回一個從響應(yīng)體映射得到的對象
getForEntity() 發(fā)送一個HTTP GET請求,返回的ResponseEntity包含了響應(yīng)體所映射成的對象
getForObject() 發(fā)送一個HTTP GET請求逞敷,返回的請求體將映射為一個對象
headForHeaders() 發(fā)送HTTP HEAD請求狂秦,返回包含特定資源URL的HTTP頭
optionsForAllow() 發(fā)送HTTP OPTIONS請求,返回對特定的URL的Allow頭信息
postForEntity() POST數(shù)據(jù)到一個URL推捐,返回包含一個對象的ResponseEntity裂问,這個對象是從響應(yīng)體中映射得到的
postForLocation() POST數(shù)據(jù)到一個URL,返回新創(chuàng)建資源的URL
postForObject() POST數(shù)據(jù)到一個URL牛柒,返回根據(jù)響應(yīng)體匹配形成的對象
put() PUT資源到特定的URL

GET資源

getForObject()都有三種形式的重載

<T> T getForObject(URI url, Class<T> responseType) 
                                  throws RestClientException;
<T> T getForObject(String url, Class<T> responseType, Object... uriVariables) 
                                  throws RestClientException;   
<T> T getForObject(String url, Class<T> responseType,
                                  Map<String,?>  uriVariables)  throws RestClientException;   

檢索資源

public Profile fetchFacebookProfile(String id) {
    RestTemplate rest = new RestTemplate();
    return rest.getForObject("http://graph.facebook.com/{spritter}",Profile.class, id);
}

另一種方案

public Profile fetchFacebookProfile(String id) {
    Map<String, String> urlVariables = new HashMap<>();
    urlVariables.put("id", id);
    RestTemplate rest = new RestTemplate();
    return rest.getForObject("http://graph.facebook.com/{spritter}",
                                            Profile.class, urlVariables);
}

getForEntity()都有三種形式的重載

<T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) 
                                  throws RestClientException;
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, 
                                  Object... uriVariables)  throws RestClientException;   
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType,
                                  Map<String,?>  uriVariables)  throws RestClientException;   

抽取響應(yīng)的元數(shù)據(jù)

public Spittle fetchSpittle(long id) {
    RestTemplate rest = new RestTemplate();
    ResponseEntity<Spittle> response = rest.getForEntity(
          "http://localhost:8080/spittr-api/spittles/{id}",
          Spittle.class, id);
    if(response.getStatusCode() == HttpStatus.NOT_MODIFIED) {
          throw new NotModifiedException();  
    }
    return response.getBody();
}

PUT資源

put() 有三種形式:

void put(URI url, Object request) throws RestClientException;
void put(String url, Object request, Object... uriVariables)
                                    throws RestClientException;
void put(String url, Object request, Map<String, ?> uriVariables)
                                    throws RestClientException;

例如

public void updateSpittle( Spittle spittle) throws SpitterException {
    RestTemplate rest = new RestTemplate();
    String url = "http://localhost:8080/spittr-api/spittles/" + spittle.getId();
    rest.put(URI.create(url), spittle);
}
public void updateSpittle( Spittle spittle) throws SpitterException {
    RestTemplate rest = new RestTemplate();
    String url = "http://localhost:8080/spittr-api/spittles/{id}";
    rest.put(url, spittle,  spittle.getId());
}

DLELTE資源

delete()方法有三個版本

void delete(String url ,Object... uriVariables) throws RestClientException;
void delete(String url ,Map<String, ?> uriVariables) throws RestClientException;
void delete(URI url) throws RestClientException;

POST資源數(shù)據(jù)

postForObject() 方法的三個變種簽名如下:

<T> T postForObject(URI url, Object request, Class<T> responseType)
                            throws RestClientException;
<T> T postForObject(String url, Object request, Class<T> responseType,
                            Object... uriVariables)  throws RestClientException;
<T> T postForObject(String url, Object request, Class<T> responseType,
                            Map<String, ?> uriVariables)  throws RestClientException;

在所有情況下堪簿,第一個參數(shù)都是資源要POST的URL,第二個參數(shù)是要發(fā)送的對象皮壁,而第三個參數(shù)是預(yù)期返回的Java類型椭更。在將URL作為String類型的兩個版本中,第四個參數(shù)指定了URL變量(要么是可變參數(shù)列表蛾魄,要么是一個Map)虑瀑。

例如

public Spitter postSpitterForObject(Spitter spitter) {
    RestTemplate rest = new RestTemplate();
    return rest.postForObject("http://localhost:8080/spittr-api/spitters",
                spitter, Spitter.class);
}

postForEntity() 方法的三個變種簽名如下:

<T> ResponseEntity<T> postForEntity(URI url, Object request, 
              Class<T> responseType) throws RestClientException;
<T> ResponseEntity<T> postForEntity(String url, Object request, 
              Class<T> responseType,  Object... uriVariables)  
              throws RestClientException;
<T> ResponseEntity<T> postForEntity(String url, Object request, 
              Class<T> responseType, Map<String, ?> uriVariables)  
              throws RestClientException;

例如:

RestTemplate rest = new RestTemplate();
ResponseEntity<Spitter> response = rest.postForEntity(
        "http://localhost:8080/spittr-api/spitters",
        spitter, Spitter.class);
Spitter spitter = response.getBody();
URI url = response.getHeaders().getLocation();

如果只是需要的是Location頭信息的值湿滓,那么使用RestTemplate的postForLocation()方法會更簡單。以下是postForLocation()的三個方法簽名:

URI postForLocation(String url, Object request, Object... uriVariables)
                throws RestClientException;
URI postForLocation(String url, Object request, Map<String,?> uriVariables)
                throws RestClientException;
URI postForLocation(URI url, Object request) throws RestClientException;

例如:

public String postSpitter(Spitter spitter) {
    RestTemplate rest = new RestTemplate();
    return rest.postForLocation(
         "http://localhost:8080/spittr-api/spitters",
          spitter).toString();
}

交換資源

如果想在發(fā)送給服務(wù)端的請求中設(shè)置頭信息的話舌狗,那就是RestTemplate的exchange()的用武之地了叽奥。

exchange()也有三個簽名格式

<T> ResponseEntity<T> exchange(URI url, HttpMethod method,
                      HttpEntity<?> requestEntity, Class<T> responseType) 
                      throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method,
                      HttpEntity<?> requestEntity, Class<T> responseType,
                      Object... uriVariables) throws RestClientException;
<T> ResponseEntity<T> exchange(String url, HttpMethod method,
                      HttpEntity<?> requestEntity, Class<T> responseType,
                      Map<String,?> uriVariables) throws RestClientException;

exchange() 方法使用HttpMethod參數(shù)來表明要使用的HTTP動作。根據(jù)這個參數(shù)的值痛侍,exchange()能夠執(zhí)行與其他RestTemplate方法一樣的工作朝氓。

例如,從服務(wù)器端獲取Spitter資源的一種方式是使用RestTemplate的getForEntity()方法主届,如下所示:

ResponseEntity<Spitter> response = rest.getForEntity(
      "http://localhost:8080/spittr-api/spitters/{spitter}",
      Spitter.class, spitterId);
Spitter spitter = response.getBody();

在下面的代碼片段中赵哲,可以看到exchange() 也可以完成這項任務(wù):

ResponseEntity<Spitter> response = rest.exchange(
      "http://localhost:8080/spittr-api/spitters/{spitter}",
      HttpMethod.GET, null ,Spitter.class, spitterId);
Spitter spitter = response.getBody();

如果不指明頭信息,exchange() 對Spitter的GET請求會帶有如下的頭信息:

GET /Spitter/spitters/habuma HTTP/1.1
Accept: application/xml, test/xml, application/*+xml, application/json
Content-Length: 0
User-Agent: Java/1.6.0_20
Host: location:8080
Connection: keep-alive

如果我們需要將“application/json”設(shè)置為Accept頭信息的唯一值君丁。

設(shè)置請求頭信息是很簡單的枫夺,只需要構(gòu)造發(fā)送給exchange()方法的 HttpEntity對象即可,HttpEntity中包含承載頭信息的MultiValueMap:

MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Accept", "application/json");
HttpEntity<Object> requestEntity = new HttpEntity<Object>(headers);

如果這是一個PUT或POST請求谈截,我們需要為HttpEntity設(shè)置在請求體中發(fā)送的對象—對于GET請求來說筷屡,這是沒有必要的涧偷。

現(xiàn)在我們可以傳入HttpEntity來調(diào)用exchange();

ResponseEntity<Spitter> response = rest.exchange(
      "http://localhost:8080/spittr-api/spitters/{spitter}",
      HttpMethod.GET, headers ,Spitter.class, spitterId);
Spitter spitter = response.getBody();
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末簸喂,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子燎潮,更是在濱河造成了極大的恐慌喻鳄,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件确封,死亡現(xiàn)場離奇詭異除呵,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)爪喘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門颜曾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人秉剑,你說我怎么就攤上這事泛豪。” “怎么了侦鹏?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵诡曙,是天一觀的道長。 經(jīng)常有香客問我略水,道長价卤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任渊涝,我火速辦了婚禮慎璧,結(jié)果婚禮上床嫌,老公的妹妹穿的比我還像新娘。我一直安慰自己胸私,他們只是感情好既鞠,可當(dāng)我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著盖文,像睡著了一般嘱蛋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上五续,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天洒敏,我揣著相機(jī)與錄音,去河邊找鬼疙驾。 笑死凶伙,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的它碎。 我是一名探鬼主播函荣,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扳肛!你這毒婦竟也來了傻挂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤挖息,失蹤者是張志新(化名)和其女友劉穎金拒,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體套腹,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡绪抛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了电禀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片幢码。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尖飞,靈堂內(nèi)的尸體忽然破棺而出症副,到底是詐尸還是另有隱情,我是刑警寧澤葫松,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布瓦糕,位于F島的核電站,受9級特大地震影響腋么,放射性物質(zhì)發(fā)生泄漏咕娄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一珊擂、第九天 我趴在偏房一處隱蔽的房頂上張望圣勒。 院中可真熱鬧费变,春花似錦、人聲如沸圣贸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吁峻。三九已至滑负,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間用含,已是汗流浹背矮慕。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留啄骇,地道東北人痴鳄。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像缸夹,于是被迫代替她去往敵國和親痪寻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理虽惭,服務(wù)發(fā)現(xiàn)橡类,斷路器,智...
    卡卡羅2017閱讀 134,654評論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架趟妥,建立于...
    Hsinwong閱讀 22,394評論 1 92
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,807評論 6 342
  • 12月22日下午4點猫态,由中文系主辦、心泉文學(xué)社承辦的“圓夢行”征文大賽頒獎典禮在北院文科樓第七階梯教室舉行披摄。出席本...
    于papa閱讀 166評論 0 0
  • 16軌道 芹菜開花了,幾棵辣椒苗在茁壯成長中勇凭,有棵花有些枯疚膊,可適當(dāng)淋些水。有小草要除哈虾标。 16自動化1 不知幾時寓盗,...
    JuN_78b0閱讀 542評論 0 2