openfeign介紹
在微服務(wù)設(shè)計(jì)里,,服務(wù)之間的調(diào)用是很正常的,,通常我們使用httpClient來(lái)實(shí)現(xiàn)對(duì)遠(yuǎn)程資源的調(diào)用,,而這種方法需要知識(shí)服務(wù)的地址,,業(yè)務(wù)接口地址等,,而且需要等他開(kāi)發(fā)完成后你才可以去調(diào)用它,,這對(duì)于集成開(kāi)發(fā)來(lái)說(shuō),不是什么好事 ,,產(chǎn)生了A業(yè)務(wù)與B業(yè)務(wù)的強(qiáng)依賴性,,那么我們?nèi)绾芜M(jìn)行解耦呢,答案就是openfeign框架,,它與是springcloudy里的一部分,。
springcloud的服務(wù)消費(fèi)者指的就是服務(wù)間的調(diào)用,實(shí)現(xiàn)的方式有兩種:一種就是上一章講的restTemplate+ribbon,,另一種就是本章要講的feign,,feign默認(rèn)集成了ribbon,所以feign也默認(rèn)實(shí)現(xiàn)了負(fù)載均衡,。
服務(wù)發(fā)現(xiàn)/注冊(cè)里的服務(wù)名
通過(guò)服務(wù)名來(lái)進(jìn)行請(qǐng)求的發(fā)送要比配置域名發(fā)http更直觀,,并且你不需要知道它的域名和端口,這也是各個(gè)微服務(wù)之前直觀調(diào)用的一種方式,,而且A服務(wù)可以不依賴于B服務(wù),,只要知道接口簽名即可。
graph TD
B(服務(wù)b)-->C(eureka注冊(cè)中心)
D-->|在服務(wù)a中建立client服務(wù)名為服務(wù)b|E(openfeign服務(wù)端)
A(服務(wù)a)-->|配置某個(gè)服務(wù)中心的服務(wù)名稱|D(調(diào)用服務(wù)b的某個(gè)接口)
D-->C
添加包引用
'org.springframework.cloud:spring-cloud-starter-openfeign'
添加配置bootstrap.yml
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 10000
2 定義profile相關(guān)配置
//默認(rèn)的一些文件路徑的配置
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
}
定義服務(wù)接口
定義偽方法,,就是服務(wù)里的方法,,你要知識(shí)方法參數(shù)和它的返回值,實(shí)現(xiàn)不用管,,只在單元測(cè)試?yán)颩OCK就可以.
package test.lind.javaLindDay.feignClientDemo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.annotation.Profile;
import org.springframework.web.bind.annotation.GetMapping;
/**
* 模擬其他服務(wù).
*/
@Profile("!integTest")
@FeignClient(name = "serviceName",primary=false)
public interface MockClient {
@GetMapping(path = "/balanceSheet/{clientCode}")
String balanceSheet(String clientCode);
}
Profile的作用
profile就是環(huán)境變量,,你在類(lèi)上通過(guò)ActiveProfile去激活它,在使用它時(shí),,有過(guò)Profile注解來(lái)使用上,,上面代碼中MockClient對(duì)象不能在integTest環(huán)境下使用。
添加MOCK實(shí)現(xiàn),它是自動(dòng)注入的,,所以聲明@Bean注解
它是為了在單元測(cè)試環(huán)境下使用client,,而又不希望與外部 網(wǎng)絡(luò)資源通訊,所以需要mock一下本地資源去實(shí)現(xiàn)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;
}
}
添加單元測(cè)試,,注意在單元測(cè)試上一定要指定它的環(huán)境變量
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環(huán)境
@ActiveProfiles("integTest")
public class JavaLindDayApplicationTests {
@Autowired
MockClient mockClient;
@Test
public void testMockClient() {
assertEquals(mockClient.balanceSheet("OK"), "OK");
}
}
運(yùn)行測(cè)試后,MockClient將會(huì)被注入,,它將使用Mock實(shí)現(xiàn)類(lèi),,因?yàn)橹挥蠱ock實(shí)現(xiàn)類(lèi)的Profile是指向integtest環(huán)境的。
有了openfeign,,以后開(kāi)發(fā)服務(wù)對(duì)服務(wù)調(diào)用就可以解耦了,!
- 需要先安裝插件,,默認(rèn)是不能發(fā)送文件流的
<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>
- 添加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標(biāo)準(zhǔn)的編碼,也可以這樣實(shí)現(xiàn)
@FeignClient(name = "file-upload-service", configuration = FileUploadServiceClient.MultipartSupportConfig.class)
public interface FileUploadServiceClient extends IFileUploadServiceClient {
public class MultipartSupportConfig {
@Bean
public Encoder feignFormEncoder() {
return new SpringFormEncoder();
}
}
}
- 添加注解
// 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);
|