天天看點

FeignClient傳輸實體類(包括GET、POST)

一、在調用其他服務時,使用實體對象作為參數。按照以下配置。

1、feign接口。

@RequestMapping(value = "/user", method = RequestMethod.POST, consumes = "application/json")
String getUserId(@RequestBody User user);
           
  • 1、consumes: 指定處理請求的送出内容類型(Content-Type),例如application/json, text/html;
  • 2、produces: 指定傳回的内容類型,僅當request請求頭中的(Accept)類型中包含該指定類型才傳回;
  • 3、或者 = MediaType.APPLICATION_JSON_VALUE
  • 4、正常來說,在FeignClient的接口中,也不需要在參數上注解@RequestBody ,隻需要在實作類上添加@RequestBody 注解即可。

 2、其他服務實作。

@RequestMapping(value = "/user", method = RequestMethod.POST)

public String getUserId(@RequestBody User user){

    if("a".equals(user.getUserName())){

        return "1001";

    }

    return "1003";

}

feign對應的服務實作上的參數@RequestBody 注解必不可少,否則接受到參數可能為null。

二、如果以上依然實作不了實體對象的傳輸,可以增加以下方法修改。以下配置是參考網上别人所寫,具體是誰我給忘記了,如果知道,請告訴我一聲,我會備注的。

1、修改調用@FeignClient 接口的pom檔案。

feign:
  httpclient:
    enabled: true
           

2、修改pom檔案。添加以下配置:

<!-- 使用Apache HttpClient替換Feign原生httpclient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>
<dependency>
    <groupId>com.netflix.feign</groupId>
    <artifactId>feign-httpclient</artifactId>
    <version>8.18.0</version>
</dependency>
           

3、為什麼要替換?主要是為了使用GET方法時也可以傳入實體作為參數。否則,如果使用實體作為參數傳值,預設會改為POST請求方式。看源碼

private synchronized OutputStream getOutputStream0() throws IOException {
  try {
      if(!this.doOutput) {
            throw new ProtocolException("cannot write to a URLConnection if doOutput=false - call setDoOutput(true)");
 } else {
      if(this.method.equals("GET")) {
           this.method = "POST";
 }
           

Feign在預設情況下使用的是JDK原生的URLConnection發送HTTP請求,沒有連接配接池,但是對每個位址會保持一個長連接配接,即利用HTTP的persistence connection 。我們可以用Apache的HTTP Client替換Feign原始的http client, 進而擷取連接配接池、逾時時間等與性能息息相關的控制能力。Spring Cloud從Brixtion.SR5版本開始支援這種替換,首先在項目中聲明Apache HTTP Client和feign-httpclient依賴。

4、看一個簡單的示例。

// portal層
@RequestMapping(value = "/teacher/teacher", method = RequestMethod.GET)
public Teacher getTeacher(){
    Student student = new Student();
    student.setStudentId(1);
    student.setStudentName("王小二");
    student.setAge(12);

    return teacherFeign.getTeacher(student);
}


// feign api層
@FeignClient(value = "feign-server")
public interface TeacherFeign {

    @RequestMapping(value = "/teacher/teacher", method = RequestMethod.GET,consumes = MediaType.APPLICATION_JSON_VALUE)
    Teacher getTeacher( Student student);
    
}

// feign-server層
@RestController
public class TeacherRest implements TeacherFeign {
    @Override
    public Teacher getTeacher(@RequestBody Student student) {
        System.out.println(student.toString());
        return teacherList().get(0);
    }
}
           

三、使用表單模式傳輸資料,相當于GET方式直接接受實體參數。方式一,建構k1=v1&k2=v2

/**
* 在舊系統中,使用了原始的表單送出,并且這也是一個微服務
*/
@RequestMapping(value = "/patients/{id}", method = RequestMethod.PUT)
public APIResult UpdatePatientInfo(@PathVariable(value = "id") String id,PatientParameter patientParameter){...}
           

是以我的feign接口如下

3-1、Feign接口

@FeignClient("xx-server")
public interface PatientLegacyAdapterFeign {

/**
 * 編輯客戶
 * @param patientId
 * @param patientRequest 編輯客戶請求資料
 * @return
 */
@RequestMapping(value = "/patients/{id}",method = RequestMethod.PUT)
Map<String,Object> editPatientInfo(@PathVariable("id")String patientId, PatientParameter patientParameter);
           

雖然這樣可以請求過去,但是參數中的值并不會去過去,patientParameter結果為null。

3-2、 分析

Content-type=application/x-www-form-urlencoded,參數上不能增加@RequestBody的注解。
           
  • 1、因為使用的表單模式送出,是以這個送出方式是application/x-www-form-urlencoded。
  • 2、采用這種類型:送出到服務端實際上是一個MultiValueMap,如果value中的對象也是一個對象,那麼在建構這個參數時就非常困難。采用key1=value1&key2=value2這種形式将所有參數拼接起來;value要進行編碼,編碼之後的對調試者不友好;value是複雜對象的情況更加糟糕,一般隻能通過程式來序列化得到參數,想手寫基本不可能。

3-3、解決方案

@FeignClient("xx-server")
public interface OldPatientFeign {

/**
 * 編輯患者
 * @param id
 * @param patientParameter
 * @return
 */
@RequestMapping(value = "/patient/{id}", method = RequestMethod.PUT)
Map<String,Object> updatePatientInfo(@PathVariable(value = "id") String id, MultiValueMap patientParameter);
}
           

因為我們知道表單送出時,服務端是一個MultiValueMap,是以我們直接使用這個對象去傳。

// 調用前使用MultiValueMap 。
LinkedMultiValueMap m = new LinkedMultiValueMap();
m.setAll( BeanUtils.describe(getPatientParameterByPatientRequest(patientRequest)));
return oldPatientFeign.updatePatientInfo(patientId,m);
           

這樣就可以完美的使用表單格式送出了。

四、Feign實作Form表單送出,方式二,參考周立大大的文章

4-1、添加依賴

<dependency>
  <groupId>io.github.openfeign.form</groupId>
  <artifactId>feign-form</artifactId>
  <version>3.2.2</version>
</dependency>

<dependency>
  <groupId>io.github.openfeign.form</groupId>
  <artifactId>feign-form-spring</artifactId>
  <version>3.2.2</version>
</dependency>
           

4-2、Feign Client示例:

@FeignClient(name = "xxx", url = "http://www.itmuch.com/", configuration = TestFeignClient.FormSupportConfig.class)
public interface TestFeignClient {
    @PostMapping(value = "/test",            
        consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},            
        produces = {MediaType.APPLICATION_JSON_UTF8_VALUE}            )    
    void post(Map<String, ?> queryParam);    

    class FormSupportConfig {
        @Autowired        
        private ObjectFactory<HttpMessageConverters> messageConverters;        
            // new一個form編碼器,實作支援form表單送出        
            @Bean        
            public Encoder feignFormEncoder() {            
                return new SpringFormEncoder(new SpringEncoder(messageConverters));        
            }        
            // 開啟Feign的日志        
            @Bean        
            public Logger.Level logger() {
                        return Logger.Level.FULL;        
            }    
    }
}
           

4-3、調用示例:

@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
  HashMap<String, String> param = Maps.newHashMap();
  param.put("username","zhangsan");
  param.put("password","pwd");  
  this.testFeignClient.post(param);  
  return new User();
}