SpringCloud(五) 使用Ribbon進行Restful請求


寫在前面

本文由markdown格式寫成,為本人第一次這么寫,排版可能會有點亂,還望各位海涵。
主要寫的是使用Ribbon進行Restful請求,測試各個方法的使用,代碼冗余較高,比較適合初學者,介意輕噴謝謝。

前提

  • 一個可用的Eureka注冊中心(文中以之前博客中雙節點注冊中心,不重要)
  • 一個連接到這個注冊中心的服務提供者
  • 一個ribbon的消費者
注意:文中使用@GetMapping@PostMapping@PutMapping@DeleteMapping等注解需要升級 spring-boot-starter-parent版本到1.5.9.REALEASE以上(1.3.7.RELEASE版本沒有這些注解)
建議:每個微服務應用都有自己的spring-boot-maven-pluginmaven-compiler-plugin並指定jdk編譯版本為1.8 ,指定方式如下,pom.xml中添加
    <build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			<plugin>
				<groupId>org.apache.maven.plugins</groupId>
				<artifactId>maven-compiler-plugin</artifactId>
				<configuration>
					<source>1.8</source>
					<target>1.8</target>
				</configuration>
			</plugin>
		</plugins>
	</build>

測試項目構建

Eureka注冊中心:參考注冊中心的搭建
服務提供者:參考注冊服務提供者
ribbon消費者:參考服務發現與消費

項目搭建完后,記得按照這幾個教程中提到的配置hosts文件

為了防止項目中的RequestMapping相同,這里就刪除所有的controller類(服務提供者和消費者),接下來我會將每個restful方法都封裝成一個類,方便大家查看

Get請求

getForEntity:此方法有三種重載形式,分別為:

  • getForEntity(String url, Class<T> responseType)
  • getForEntity(String url, Class<T> responseType, Object... uriVariables)
  • getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
  • getForEntity(URI url, Class<T> responseType)
注意:此方法返回的是一個包裝對象ResponseEntity<T>其中TresponseType傳入類型,想拿到返回類型需要使用這個包裝類對象的getBody()方法

getForObject:此方法也有三種重載形式,這點與getForEntity方法相同:

  • getForObject(String url, Class<T> responseType)
  • getForObject(String url, Class<T> responseType, Object... uriVariables)
  • getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
  • getForObject(URI url, Class<T> responseType)
注意:此方法返回的對象類型為responseType傳入類型

為了方便測試,這里分別在服務提供者和服務消費者中提供相同的User類,用於方便測試

package com.cnblogs.hellxz;

/**
 * 用於測試的pojo
 */
public class User {

    private String name;
    private String sex;
    private String phone;
    public User(){}
    public User(String name, String sex, String phone) {
        this.name = name;
        this.sex = sex;
        this.phone = phone;
    }

    public String toString(){
        return "user:{"
                +"name: " + name + ", "
                +"sex: " + sex + ", "
                +"phone: " + phone
                +" }";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

下邊我們在服務提供者處創建一個GetRequestController

package com.cnblogs.hellxz;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;

/**
 * @Author : Hellxz
 * @Description: 服務提供者
 * @Date : 2018/4/18 11:36
 */
@RestController
public class GetRequestController {

    @Autowired
    private DiscoveryClient client; //注入發現客戶端

    private final Logger logger = Logger.getLogger(GetRequestController.class);

    /**
     * go straight test
     */
    @GetMapping(value = "/hello")
    public String hello(){
        //獲取服務實例,作用為之后console顯示效果
        ServiceInstance serviceInstance = client.getLocalServiceInstance();
        logger.info("/hello host:"+serviceInstance.getHost()+" service_id:" +serviceInstance.getServiceId());
        return "hello";
    }

    /**
     * parameter test
     */
    @GetMapping(value = "/greet/{dd}")
    public String greet(@PathVariable String dd){
        ServiceInstance serviceInstance = client.getLocalServiceInstance();
        logger.info("/hello host:"+serviceInstance.getHost()+" service_id:" +serviceInstance.getServiceId());
        return "hello "+dd;
    }

    /**
     * 返回測試對象
     */
    @GetMapping("/user")
    public User getUser(){
        ServiceInstance serviceInstance = client.getLocalServiceInstance();
        logger.info("/user "+serviceInstance.getHost()+" port:"+serviceInstance.getPort()+" serviceInstanceid:"+serviceInstance.getServiceId());
        return new User("hellxz","male", "123456789");
    }

    /**
     * 根據名稱返回對象,這里模擬查數據庫操作
     */
    @GetMapping("/user/{name}")
    public User getUserSelect(@PathVariable String name){
        ServiceInstance serviceInstance = client.getLocalServiceInstance();
        logger.info("/user "+serviceInstance.getHost()+" port:"+serviceInstance.getPort()+" serviceInstanceid:"+serviceInstance.getServiceId());
        if(name.isEmpty()){
            return new User();
        }else if(name.equals("hellxz")){
            return new User("hellxz","male", "123456789");
        }else{
            return new User("隨機用戶","male", "987654321");
        }
    }
}

接下來我們在服務消費者項目中創建GetRequestController

package com.cnblogs.hellxz;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author : Hellxz
 * @Description: ribbon消費者應用Controller,get請求
 * @Date : 2018/4/16 15:54
 */
@RestController
public class GetRequestController {

    private Logger logger = Logger.getLogger(GetRequestController.class);

    @Autowired
    //注入restTemplate
    private RestTemplate restTemplate;

    /**
     * ResponseEntity<T> getForEntity(String url, Class<T> responseType)
     * T getBody()  以下此方法相同
     */
    @GetMapping(value="/entity/noparam")
    public String noParamGetForEntity(){
        //這里注釋掉,因為之前想當然使用了直鏈訪問服務提供者的接口,這樣是不會返回結果的,而且會報錯
        //return restTemplate.getForEntity("http://localhost:8080/hello",String.class).getBody();
        //使用restTemplate調用微服務接口
        return restTemplate.getForEntity("http://hello-service/hello", String.class).getBody();

    }

    /**
     * ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
     */
    @GetMapping("/entity/type")
    public User getForEntityIdentifyByType(){
        //不傳參返回指定類型結果
        ResponseEntity<User> entity = restTemplate.getForEntity("http://hello-service/user", User.class);
        User body = entity.getBody();
        logger.info("user:"+body);
        return body;
        //以上可簡寫為
//        return restTemplate.getForEntity("http://hello-service/user", User.class).getBody();
    }

    /**
     * ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)
     * 使用占位符對參數進行替換,內部使用String.format方法實現
     */
    @GetMapping(value="/entity")
    //如果接收的參數是使用參數沒有使用?有則使用@PathVariable,否則用@RequestParam
    public String getForEntityByQuestionMarkParam(@RequestParam("name") String name){
        //主要測試getEntity方法,這里測試直接傳參
        return restTemplate.getForEntity("http://hello-service/greet/{1}", String.class, name).getBody();
    }

    /**
     * getForEntity方法內部會提取map中,以占位符為key的值作為參數回填入url中
     * ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> uriVariables)
     */
    @GetMapping(value="/entity/map/{name}")
    //如果接收的參數是使用參數沒有使用?有則使用@PathVariable,否則用@RequestParam
    public String getForEntityByMap(@PathVariable("name") String name){
        //主要測試getEntity方法,這里測試map傳參
        Map<String, String> reqMap = new HashMap();
        reqMap.put("name",name);
        return restTemplate.getForEntity("http://hello-service/greet/{name}", String.class,reqMap).getBody();
    }

    /**
     * ResponseEntity<T> getForObject(URI url, Class<T> responseType)
     */
    @GetMapping("/entity/uri")
    public String getForEntityByURI(){
        //使用uri進行傳參並訪問
        UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://hello-service/greet/{name}").build().expand("laozhang").encode();
        URI uri = uriComponents.toUri();
        return restTemplate.getForEntity(uri, String.class).getBody();

    }
    /**
     * T getForObject(String url, Class<T> responseType)
     */
    @GetMapping("/object")
    public User getForObjectWithNoParam(){
        //相比getForEntity方法,獲取對象可以省去調用getBody
        return restTemplate.getForObject("http://hello-service/user", User.class);
    }

    /**
     * T getForObject(String url, Class<T> responseType, Map<String, ?> uriVariables)
     */
    @GetMapping("/object/map")
    public User getForObjectByMap(){
        //使用map傳參
        Map<String, String> paramMap = new HashMap<>();
        paramMap.put("name","hellxz");
        return restTemplate.getForObject("http://hello-service/user", User.class, paramMap);
    }

    /**
     * T getForObject(String url, Class<T> responseType, Object... uriVariables)
     */
    @GetMapping("/object/param/{name}")
    public User getForObjectByParam(@PathVariable String name){
        return restTemplate.getForObject("http://hello-service/user/{name}",User.class, name);
    }

    /**
     * T getForObject(URI url, Class<T> responseType)
     */
    @GetMapping("/object/uri/{name}")
    public User getForObjectByURI(@PathVariable String name){
        UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://hello-service/user/{name}")
                                                                                .build().expand(name).encode();
        URI uri = uriComponents.toUri();
        return restTemplate.getForObject(uri,User.class);
    }
}

先啟動注冊中心,然后通過訪問消費者對外提供的接口進行測試,這些都是本人實際操作過的了,這里就不寫測試了

Post請求

post請求和get請求都有*ForEntity*ForObject方法,其中參數列表有些不同,除了這兩個方法外,還有一個postForLocation方法,其中postForLocation以post請求提交資源,並返回新資源的URI

postForEntity:此方法有三種重載形式,分別為:

  • postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
  • postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
  • postForEntity(URI url, Object request, Class<T> responseType)
注意:此方法返回的是一個包裝對象ResponseEntity<T>其中TresponseType傳入類型,想拿到返回類型需要使用這個包裝類對象的getBody()方法

postForObject:此方法也有三種重載形式,這點與postForEntity方法相同:

  • postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
  • postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
  • postForObject(URI url, Object request, Class<T> responseType)
注意:此方法返回的對象類型為responseType傳入類型

postForLocation:此方法中同樣有三種重載形式,分別為:

  • postForLocation(String url, Object request, Object... uriVariables)
  • postForLocation(String url, Object request, Map<String, ?> uriVariables)
  • postForLocation(URI url, Object request)
注意:此方法返回的是新資源的URI,相比getForEntitygetForObjectpostForEntitypostForObject方法不同的是這個方法中無需指定返回類型,因為返回類型就是URI,通過Object... uriVariablesMap<String, ?> uriVariables進行傳參依舊需要占位符,參看postForEntity部分代碼

按照之前的方式,我們分別在提供服務者和消費者的項目中分別創建PostRequestController
如下服務者PostRequestController代碼如下:

package com.shunneng.springcloudhelloworld;

import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;


/**
 * @Author : Hellxz
 * @Description:
 * @Date : 2018/4/18 10:21
 */
@RestController
public class PostRequestController {

    private Logger logger = Logger.getLogger(PostRequestController.class);

    /**
     * 接收一個對象再返回回去,postForEntity/postForObject方法通用
     */
    @PostMapping("/user")
    public User returnUserByPost(@RequestBody User user){
        logger.info("/use接口 "+user);
        if(user == null) return new User("這是一個空對象","","");
        return user;
    }

    /**
     * 測試PostForEntity方法的參數,可以直接看輸出判斷結果了
     */
    @PostMapping("/user/{str}")
    public User returnUserByPost(@PathVariable String str, @RequestBody User user){
        logger.info("/user/someparam 接口傳參 name:"+str +" "+user);
        if(user == null) return new User("這是一個空對象","","");
        return user;
    }

    /**
     * 為postForLocation方法返回URI
     */
    @PostMapping("/location")
    public URI returnURI(@RequestBody User user){
        //這里模擬一個url,真實資源位置不一定是這里
        UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://hello-service/location")
                                                                                                .build().expand(user).encode();
        URI toUri = uriComponents.toUri();
        //這里不知道是什么問題,明明生成uri了,返回之后好像並沒有被獲取到
        logger.info("/location uri:"+toUri);
        return toUri;
    }

}


消費端PostRequestController代碼:

package com.cnblogs.hellxz;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;


/**
 * @Author : Hellxz
 * @Description: Ribbon消費者post請求controller
 * @Date : 2018/4/18 9:47
 */
@RestController
public class PostRequestController {

    private Logger logger = Logger.getLogger(PostRequestController.class);

    @Autowired
    private RestTemplate restTemplate;

    /**
     * ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType)
     * 其中參數url不多說,Object request如果是不是一個HttpEntity對象,會自動轉換為HttpEntity對象,視作完整的body來處理;
     * 如果是HttpEntity對象,那么會被直接當做body處理並且包含header內容。
     * 以下對於重寫的方法就不多說了,使用方法大體同getForEntity,如果僅是簡單post對象,那么使用不帶Object...variables或Map variables的方法即可。
     * postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
     * postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
     *
     * 這里詳細說下我遇到的坑:
     * 1、其他幾個重載方法的最后邊的Object...variables和Map variables都是對之前的url進行操作的,
     *     也就是說,在post請求的url中使用占位符進行傳參,而如果在url中沒有使用占位符,那么這些最后傳的參數是無效的!
     * 2、方法中Object request這個對象如果和服務提供者的接收參數類型相同,那么服務提供者僅需使用@RequestBody接收參數即可。
     * 3、如果二者都使用了,這就比較有趣了,需要一邊通過@PathVariable注解接收uri中的參數,一邊還需要@RequestBody接收對象或RequestParam按字段接收參數!
     * 4、如果報錯了,請仔細看看我上邊寫的三條,並注意服務提供者的參數接收注解的使用等。
     */
    @PostMapping("/entity")
    public User postForEntity(){
        User user = new User("hellxz1","1","678912345");
        ResponseEntity<User> entity = restTemplate.postForEntity("http://hello-service/user/{str}", user, User.class, "測試參數");
        User body = entity.getBody(); //所有restTemplate.*ForEntity方法都是包裝類,body為返回類型對象
        return body;
    }

    /**
     * 使用URI傳參,測試結果會顯示在服務提供者的終端中
     * ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType)
     */
    @PostMapping("/entity/uri")
    public User postForEntityByURI(){
        User user = new User("老張","1","678912345");
        //這里只是將url轉成URI,並沒有添加參數
        UriComponents uriComponents = UriComponentsBuilder.fromUriString("http://hello-service/user")
                                                                                .build().encode();
        URI toUri = uriComponents.toUri();
        //使用user傳參
        User object = restTemplate.postForObject(toUri, user, User.class);
        return object;
    }

    /**
     * 這里測試postForObject方法,需要注意的參數如上述方法的描述,區別只是不需要getBody了,這里就不再累述了
     * postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
     * postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
     */
    @PostMapping("/object")
    public User postForObject(){
        User user = new User("hellxz2","1","123654987");
        //這里url傳1是為了調用服務者項目中的一個接口
        User responseBody = restTemplate.postForObject("http://hello-service/user/1", user, User.class);
        return responseBody;
    }

    /**
     * post請求還有一種:postForLocation,這里也同樣有三種重載,除了無需指定返回類型外,用法相同,返回類型均為URI,也就不累述了
     * postForLocation(String url, Object request, Object... uriVariables)
     * postForLocation(String url, Object request, Map<String, ?> uriVariables)
     * postForLocation(URI url, Object request)
     */
    @PostMapping("/location")
    public URI postForLocation(){
        User user = new User("hellxz3","1","987654321");
        URI uri = restTemplate.postForLocation("http://hello-service/location", user);
        //不知道為什么返回來是空,這個方法僅供參考吧,如果知道是什么情況,我會回來改的
        logger.info("/location uri:"+uri);
        return uri;
    }

}

Put請求&&Delete請求

put請求相對於get和post請求方法來的更為簡單,其中無需指定put請求的返回類型,當然也沒有返回值,也是三種重載,和之前寫的基本一致,這里就不想多說了,delete請求和put請求都是沒有返回值的,這里再特地重復寫也沒什么意思,這里先分別列出這兩個請求的方法,代碼寫在一個類中了

put請求方法如下:

  • put(String url, Object request, Object... uriVariables)
  • put(String url, Object request, Map<String, ?> uriVariables)
  • put(URI url, Object request)

delete請求方法如下:

  • delete(String url, Object... uriVariables)
  • delete(String url, Map<String, ?> uriVariables)
  • delete(URI url)

在提供服務者項目中添加PutAndDeleteRequestController,代碼如下

package com.cnblogs.hellxz;

import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.*;


/**
 * @Author : Hellxz
 * @Description: 服務提供者 put&delete請求controller
 * @Date : 2018/4/19 14:11
 */
@RestController
public class PutAndDeleteRequestController {

    private Logger logger = Logger.getLogger(PutAndDeleteRequestController.class);

    @PutMapping("/put")
    public void put(@RequestBody User user){
        logger.info("/put "+user);
    }

    @DeleteMapping("/delete/{id}")
    public void delete(@PathVariable Long id){
        logger.info("/delete id:"+id);
    }
}

在提供服務者項目中添加PutAndDeleteRequestController,代碼如下

package com.cnblogs.hellxz;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

/**
 * @Author : Hellxz
 * @Description: put請求、delete請求,重載的參數與上述demo基本相同,不予列出
 * @Date : 2018/4/19 13:43
 */
@RestController
public class PutRequestController {

    private Logger logger = Logger.getLogger(PostRequestController.class);
    @Autowired
    private RestTemplate restTemplate;

    /**
     * put請求示例,一般put請求多用作修改
     */
    @PutMapping("/put")
    public void put(@RequestBody User user){
        restTemplate.put("http://hello-service/put",user);
    }

    /**
     * delete請求示例
     */
    @DeleteMapping("/del/{id}")
    public void delete(@PathVariable Long id){
        restTemplate.delete("http://hello-service/delete/{1}", id);
    }
}

結語

這篇博文使用markdown寫成,第一次寫不知道如何將代碼塊中加入序號以及折疊代碼功能,這可能不是一篇好文章,但是寫這篇博文寫了快兩天,有什么好的建議歡迎評論交流,啥也不說了,如果本文對你有幫助,請幫忙點個推薦,加個關注吧!

聲明:本博客無需許可也可以轉載,但煩請注明出處


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM