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

    极限就是为了超越而存在的
  • 相关阅读:
    Leetcode: Longest Increasing Subsequence
    Leetcode: Bulls and Cows
    Leetcode: Serialize and Deserialize Binary Tree
    undefined reference to symbol '_ZNK11GenICam_3_016GenericException17GetSourceFileNameEv'
    ranlib 作用
    live555运行时报错:StreamParser internal error ( 86451 + 64000 > 150000)
    qt 免注册下载
    modsign: could't get uefi db list
    ubuntu安装 opencv-3.4.3
    xl2tpd[26104]: Maximum retries exceeded for tunnel 33925. Closing
  • 原文地址:https://www.cnblogs.com/liboware/p/15119128.html
Copyright © 2020-2023  润新知