1. 程式人生 > 實用技巧 >springboot~feign模擬multipart/form-data

springboot~feign模擬multipart/form-data

openfeign介紹

在微服務設計裡,服務之間的呼叫是很正常的,通常我們使用httpClient來實現對遠端資源的呼叫,而這種方法需要知識服務的地址,業務介面地址等,而且需要等他開發完成後你才可以去呼叫它,這對於整合開發來說,不是什麼好事 ,產生了A業務與B業務的強依賴性,那麼我們如何進行解耦呢,答案就是openfeign框架,它與是springcloudy裡的一部分。

    springcloud的服務消費者指的就是服務間的呼叫,實現的方式有兩種:一種就是上一章講的restTemplate+ribbon,另一種就是本章要講的feign,feign預設集成了ribbon,所以feign也預設實現了負載均衡。

服務發現/註冊裡的服務名

通過服務名來進行請求的傳送要比配置域名發http更直觀,並且你不需要知道它的域名和埠,這也是各個微服務之前直觀呼叫的一種方式,而且A服務可以不依賴於B服務,只要知道介面簽名即可。

graph TD
B(服務b)-->C(eureka註冊中心)
D-->|在服務a中建立client服務名為服務b|E(openfeign服務端)
A(服務a)-->|配置某個服務中心的服務名稱|D(呼叫服務b的某個介面)
D-->C

新增包引用

'org.springframework.cloud:spring-cloud-starter-openfeign'

新增配置bootstrap.yml

feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 10000

2 定義profile相關配置

//預設的一些檔案路徑的配置
sourceSets {
    integTest {
        java.srcDir file('src/test/java')
        resources.srcDir file('src/test/resources')
    }
}

task integTest(type: Test) {
    testClassesDirs = sourceSets.test.output.classesDirs
    classpath = sourceSets.test.runtimeClasspath
}

定義服務介面

定義偽方法,就是服務裡的方法,你要知識方法引數和它的返回值,實現不用管,只在單元測試裡MOCK就可以.

package test.lind.javaLindDay.feignClientDemo;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * 模擬其他服務.
 */
@Profile("!integTest")
@FeignClient(name = "serviceName",primary=false)
public interface MockClient {
  @GetMapping(path = "/balanceSheet/{clientCode}")
  String balanceSheet(String clientCode);
}

Profile的作用

profile就是環境變數,你在類上通過ActiveProfile去啟用它,在使用它時,有過Profile註解來使用上,上面程式碼中MockClient物件不能在integTest環境下使用。

新增MOCK實現,它是自動注入的,所以宣告@Bean註解

它是為了在單元測試環境下使用client,而又不希望與外部 網路資源通訊,所以需要mock一下本地資源去實現client.

package test.lind.javaLindDay;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import test.lind.javaLindDay.feignClientDemo.MockClient;

@Configuration
@Profile("integTest")
public class MockClientTest {
  @Bean
  @Primary
  public MockClient mockClient() {
    MockClient client = mock(MockClient.class);
    when(client.balanceSheet(
        anyString()))
        .thenReturn("OK");
    return client;
  }
}

新增單元測試,注意在單元測試上一定要指定它的環境變數

package test.lind.javaLindDay;

import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import test.lind.javaLindDay.feignClientDemo.MockClient;

@RunWith(SpringRunner.class)
@SpringBootTest
//指定profile環境
@ActiveProfiles("integTest")
public class JavaLindDayApplicationTests {

  @Autowired
  MockClient mockClient;

  @Test
  public void testMockClient() {
    assertEquals(mockClient.balanceSheet("OK"), "OK");
  }
}

執行測試後,MockClient將會被注入,它將使用Mock實現類,因為只有Mock實現類的Profile是指向integtest環境的。
有了openfeign,以後開發服務對服務呼叫就可以解耦了!

feignClient傳送multipart/form-data請求

  1. 需要先安裝外掛,預設是不能傳送檔案流的
<dependencies>
    ...
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form</artifactId>
        <version>3.3.0</version>
    </dependency>
    <dependency>
        <groupId>io.github.openfeign.form</groupId>
        <artifactId>feign-form-spring</artifactId>
        <version>3.3.0</version>
    </dependency>
    ...
</dependencies>
  1. 新增bean
@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class)
public interface FileUploadServiceClient extends IFileUploadServiceClient {

    public class MultipartSupportConfig {

        @Autowired
        private ObjectFactory<HttpMessageConverters> messageConverters;

        @Bean
        public Encoder feignFormEncoder() {
            return new SpringFormEncoder(new SpringEncoder(messageConverters));
        }
    }
}

如果不需要Spring標準的編碼,也可以這樣實現

@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class)
public interface FileUploadServiceClient extends IFileUploadServiceClient {

    public class MultipartSupportConfig {

        @Bean
        public Encoder feignFormEncoder() {
            return new SpringFormEncoder();
        }
    }
}
  1. 添加註解
    // File parameter
    @RequestLine("POST /send_photo")
    @Headers("Content-Type: multipart/form-data")
    void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo);

    // byte[] parameter
    @RequestLine("POST /send_photo")
    @Headers("Content-Type: multipart/form-data")
    void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") byte[] photo);

    // FormData parameter
    @RequestLine("POST /send_photo")
    @Headers("Content-Type: multipart/form-data")
    void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") FormData photo);