深入浅出SpringCloud原理及实战「Netflix系列之Fegin」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(上)

Posted 洛神灬殇

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出SpringCloud原理及实战「Netflix系列之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;

@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("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();

接口使用示例:
@Service
public 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调用别人的接口,都可以使用这个工具来简化调用与解析的操作。

以上是关于深入浅出SpringCloud原理及实战「Netflix系列之Fegin」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(上)的主要内容,如果未能解决你的问题,请参考以下文章

深入浅出SpringCloud原理及实战「Netflix系列之Fegin」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(下)

深入浅出SpringCloud原理及实战「Netflix系列之Hystrix」针对于限流熔断组件Hystrix的超时机制的原理和实现分析

深入浅出SpringCloud原理及实战「Netflix系列之Hystrix」针对于限流熔断组件Hystrix的基本参数和实现原理介绍分析

深入浅出Dubbo3原理及实战「SpringCloud-Alibaba系列」基于Nacos作为注册中心进行发布SpringCloud-alibaba生态的RPC接口实战

深入浅出SpringCloud原理及实战「Netflix系列之Ribbon」针对于负载均衡组件Ribbon的基本参数和实现原理介绍分析

深入浅出SpringCloud原理及实战「SpringCloud-Gateway系列」微服务API网关服务的Gateway全流程开发实践指南(入门篇)