【SpringCloud技術專題】「原生態Fegin」打開Fegin之RPC技術的開端,你會使用原生態的Fegin嗎?(上)
- 2021 年 8 月 9 日
- 筆記
- SpringCloud-技術專區-系列專題
前提介紹
-
Feign是SpringCloud中服務消費端的調用框架,通常與ribbon,hystrix等組合使用。
-
由於遺留原因,某些項目中,整個系統並不是SpringCloud項目,甚至不是Spring項目,而使用者關注的重點僅僅是簡化http調用程式碼的編寫。
-
如果採用httpclient或者okhttp這樣相對較重的框架,對初學者來說編碼量與學習曲線都會是一個挑戰,而使用spring中RestTemplate,又沒有配置化的解決方案,由此想到是否可以脫離Spring cloud,獨立使用Feign。
內容簡介
Feign使得 Java HTTP 客戶端編寫更方便。Feign 靈感來源於Retrofit、JAXRS-2.0和WebSocket。Feign最初是為了降低統一綁定Denominator到HTTP API的複雜度,不區分是否支援Restful。Feign旨在通過最少的資源和程式碼來實現和HTTP API的連接。通過可訂製的解碼器和錯誤處理,可以編寫任意的HTTP API。
maven依賴
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-core</artifactId>
<version>8.18.0</version>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-jackson</artifactId>
<version>8.18.0</version>
</dependency>
<dependency>
<groupId>io.github.lukehutch</groupId>
<artifactId>fast-classpath-scanner</artifactId>
<version>2.18.1</version>
</dependency>
<dependency>
<groupId>com.netflix.feign</groupId>
<artifactId>feign-jackson</artifactId>
<version>8.18.0</version>
</dependency>
定義配置類
RemoteService service = Feign.builder()
.options(new Options(1000, 3500))
.retryer(new Retryer.Default(5000, 5000, 3))
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.target(RemoteService.class, "//127.0.0.1:8085");
- options方法指定連接超時時長及響應超時時長
- retryer方法指定重試策略
- target方法綁定介面與服務端地址。
- 返回類型為綁定的介面類型。
自定義介面
隨機定義一個遠程調用的服務介面,並且聲明相關的介面參數和請求地址。
通過@RequestLine指定HTTP協議及URL地址
public class User{
String userName;
}
public interface RemoteService {
@Headers({"Content-Type: application/json","Accept: application/json"})
@RequestLine("POST /users/list")
User getOwner(User user);
@RequestLine("POST /users/list2")
@Headers({
"Content-Type: application/json",
"Accept: application/json",
"request-token: {requestToken}",
"UserId: {userId}",
"UserName: {userName}"
})
public User getOwner(@RequestBody User user,
@Param("requestToken") String requestToken,
@Param("userId") Long userId,
@Param("userName") String userName);
}
服務提供者
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping(value="users")
public class UserController {
@RequestMapping(value="/list",method={RequestMethod.GET,RequestMethod.POST,RequestMethod.PUT})
@ResponseBody
public User list(@RequestBody User user) throws InterruptedException{
System.out.println(user.getUsername());
user.setId(100L);
user.setUsername(user.getUsername().toUpperCase());
return user;
}
}
調用
與調用本地方法相同的方式調用feign包裝的介面,直接獲取遠程服務提供的返回值。
String result = service.getOwner(new User("scott"));
原生Feign的兩個問題
-
原生Feign只能一次解析一個介面,生成對應的請求代理對象,如果一個包里有多個調用介面就要多次解析非常麻煩。
-
Feign生成的調用代理只是一個普通對象,該如何註冊到Spring中,以便於我們可以使用@Autowired隨時注入。
解決方案:
-
針對多次解析的問題,可以通過指定掃描包路徑,然後對包中的類依次解析。
-
實現BeanFactoryPostProcessor介面,擴展Spring容器功能。
定義一個註解類
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface FeignApi {
/**
* 調用的服務地址
* @return
*/
String serviceUrl();
}
生成Feign代理並註冊到Spring實現類:
import feign.Feign;
import feign.Request;
import feign.Retryer;
import feign.jackson.JacksonDecoder;
import feign.jackson.JacksonEncoder;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class FeignClientRegister implements BeanFactoryPostProcessor{
//掃描的介面路徑
private String scanPath="com.xxx.api";
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
List<String> classes = scan(scanPath);
if(classes==null){
return ;
}
System.out.println(classes);
Feign.Builder builder = getFeignBuilder();
if(classes.size()>0){
for (String claz : classes) {
Class<?> targetClass = null;
try {
targetClass = Class.forName(claz);
String url=targetClass.getAnnotation(FeignApi.class).serviceUrl();
if(url.indexOf("//")!=0){
url="//"+url;
}
Object target = builder.target(targetClass, url);
beanFactory.registerSingleton(targetClass.getName(), target);
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
public Feign.Builder getFeignBuilder(){
Feign.Builder builder = Feign.builder()
.encoder(new JacksonEncoder())
.decoder(new JacksonDecoder())
.options(new Request.Options(1000, 3500))
.retryer(new Retryer.Default(5000, 5000, 3));
return builder;
}
public List<String> scan(String path){
ScanResult result = new FastClasspathScanner(path).matchClassesWithAnnotation(FeignApi.class, (Class<?> aClass) -> {
}).scan();
if(result!=null){
return result.getNamesOfAllInterfaceClasses();
}
return null;
}
}
調用介面編寫示例:
import com.xiaokong.core.base.Result;
import com.xiaokong.domain.DO.DeptRoom;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import com.xiaokong.register.FeignApi;
import java.util.List;
@FeignApi(serviceUrl = "//localhost:8085")
public interface RoomApi {
@Headers({"Content-Type: application/json","Accept: application/json"})
@RequestLine("GET /room/selectById?id={id}")
Result<DeptRoom> selectById(@Param(value="id") String id);
@Headers({"Content-Type: application/json","Accept: application/json"})
@RequestLine("GET /room/test")
Result<List<DeptRoom>> selectList();
}
介面使用示例:
@Service
public class ServiceImpl{
//將介面注入要使用的bean中直接調用即可
@Autowired
private RoomApi roomApi;
@Test
public void demo(){
Result<DeptRoom> result = roomApi.selectById("1");
System.out.println(result);
}
}
注意事項:
-
如果介面返回的是一個複雜的嵌套對象,那麼一定要明確的指定泛型,因為Feign在解析複雜對象的時候,需要通過反射獲取介面返回對象內部的泛型類型才能正確使用Jackson解析。如果不明確的指明類型,Jackson會將json對象轉換成一個LinkedHashMap類型。
-
如果你使用的是的Spring,又需要通過http調用別人的介面,都可以使用這個工具來簡化調用與解析的操作。