写点什么

【SpringCloud 技术专题】「原生态 Fegin」打开 Fegin 之 RPC 技术的开端,你会使用原生态的 Fegin 吗?(上)

发布于: 刚刚
【SpringCloud技术专题】「原生态Fegin」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(上)

前提介绍


  • 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, "http://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 的两个问题

  1. 原生 Feign 只能一次解析一个接口,生成对应的请求代理对象,如果一个包里有多个调用接口就要多次解析非常麻烦。

  2. Feign 生成的调用代理只是一个普通对象,该如何注册到 Spring 中,以便于我们可以使用 @Autowired 随时注入。

解决方案:

  1. 针对多次解析的问题,可以通过指定扫描包路径,然后对包中的类依次解析。

  2. 实现 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;
@Componentpublic 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("http://")!=0){ url="http://"+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 = "http://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();}
复制代码
接口使用示例:
@Servicepublic class ServiceImpl{    //将接口注入要使用的bean中直接调用即可    @Autowired    private RoomApi roomApi;    @Test    public void demo(){        Result<DeptRoom> result = roomApi.selectById("1");        System.out.println(result);    }}
复制代码

注意事项:

  1. 如果接口返回的是一个复杂的嵌套对象,那么一定要明确的指定泛型,因为 Feign 在解析复杂对象的时候,需要通过反射获取接口返回对象内部的泛型类型才能正确使用 Jackson 解析。如果不明确的指明类型,Jackson 会将 json 对象转换成一个 LinkedHashMap 类型。

  2. 如果你使用的是的 Spring,又需要通过 http 调用别人的接口,都可以使用这个工具来简化调用与解析的操作。

发布于: 刚刚阅读数: 2
用户头像

🏆2021年InfoQ写作平台-签约作者 🏆 2020.03.25 加入

👑【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 我们始于迷惘,终于更高水平的迷惘

评论

发布
暂无评论
【SpringCloud技术专题】「原生态Fegin」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(上)