手写springcloud|服务调用 feign

手写springcloud|服务调用 feign

github示例代码

github对应代码仓库地址:https://github.com/huajiexiewenfeng/spring-cloud-project-learn

REST框架对比

REST框架使用场景请求映射注解请求参数
Feign客户端声明@ReqeustLine@Param
Spring Cloud Open Feign客户端声明@RequestMapping@RequestParam
JAX-RS客户端、服务端声明@Path@*Param
Spring Web MVC服务端声明@RequestMapping@RequestParam

Spring Cloud Open Feign 利用Feign高扩展性,使用标准Spring Web MVC 来声明客户端 Java接口

  • Feign
    • 注解扩展性
    • HTTP请求处理
    • Rest请求元信息解析
  • Spring Cloud Open Feign
    • 提供Spring Web MVC 注解处理
    • 提供Feign自动装配

Spring Cloud Open Feign 是通过Java接口的方式来声明 REST 服务提供者的请求元信息,通过调用Java接口的方式来实现HTTP/REST通讯

实现细节猜想

  • java接口(以及方法)与Rest 提供者
  • @FeignClient注解所指定的应用(服务)名称可能用到了服务发现,一个服务可以对用多个服务实例(HOST:PORT)
  • @EnableFeignClients注解是如何感知或者加载标注@FeignClient的配置类(Bean)
  • Feign请求和响应的内容是如何序列化和反序列化对用的POJO的

Spring Cloud Open Feign 实现细节

实现策略:Enable 模块驱动

具体实现:org.springframework.context.annotation.ImportBeanDefinitionRegistrar

主要工作:

  • 注册默认配置
  • 注册所有标注@FeignClient配置类

注册默认配置

org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerDefaultConfiguration

注册所有标注@FeignClient配置类

org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerFeignClients

通过ClassPathScanningCandidateComponentProvider扫描指定的basePackages集合中的类是否标注了@FeignClient,如果有的话作为AnnotatedBeanDefinition返回,其中包含@FeignClient属性元数据,来至于AnnotationMetadata,再重新注册FeignClientFactoryBean·的BeanDefinition

@EnableFeignClient->@FeignClient元信息->标注接口定义的FeignClientFactoryBean->形成被标注接口的代理对应

Spring Cloud Open Feign 大致流程

  1. Spring Web MVC 注解元信息解析
  2. 通过@FeignClient所生产代理对象的方法调用实现HTTP调用
  3. 通过SpringDecoder实现Response与接口返回类型的反序列化
  4. 负载均衡
    Spring Cloud 替换了Client实现-LoadBalanceFeignClient
  5. 重试
    Spring Cloud 在外部包装Feign接口
  6. 熔断
    feign.hystrix.HystrixFeign

调用的过程

  1. 通过接口上面@FeignClient注解获取服务名称
  2. 再通过注册中心获取服务器地址
  3. 发起rest远程调用
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yvjqrVGL-1571101316984)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571051021308.png)]

手写实现步骤

该版本集成了 负载均衡 loadbalanced ,在服务调用的阶段集成负载均衡。

  1. @RestClient注解 代替@FeignClient
  2. @EnableRestClients注解用来驱动加载标注@RestClient的配置类
  3. 当客户端调用该接口(标有@RestClient注解)的方法时,调用的是动态代理的类,将服务名称,调用的方法名称传入到动态代理类中做后续相应的处理,接口本身并不需要实现类。
  4. 动态代理中,封装完整URL,在服务调用阶段使用loadBalanceRestTemplate进行远程访问,再拦截到LoadBalancedRequestInterceptor负载均衡的逻辑中
  5. 最后调用服务端的对应方法,接收返回信息

1.@RestClient

可以类比为spring cloud open feign中的@FeignClient

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RestClient {
    /**
     * REST 服务应用名称
     * @return
     */
    String name();
}

2.@EnableRestClients注解驱动

这一块可以参考 Spring boot 编程思想|@Enable 模块驱动

本文用的是实现ImportBeanDefinitionRegistrar接口这种方式实现

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({RestClientsRegistrar.class})
public @interface EnableRestClients {

    /**
     * 指定@RestClient接口
     * @return
     */
    Class<?>[] clients() default {};
}
RestClientsRegistrar
  • 在spring容器启动时,将@RestClient接口代理实现注册为Bean
  • metadata获取注解相关的元信息
  • 将接口封装成动态代理Proxy.newProxyInstance()
  • 通过registrybean注册到springbeanDefinition
  • 实现BeanFactoryAware获取springBeanFactory对象,获取restTemplate对象
  • 实现EnvironmentAware获取springenvironment对象,完成占位符的功能拓展
public class RestClientsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware , EnvironmentAware {

    private BeanFactory beanFactory;

    private Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
        ClassLoader classLoader = metadata.getClass().getClassLoader();
        //将@RestClient接口代理实现注册为Bean(@Autowired)
        //@RestClient->attributes->{clients:ServiceName}
        Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableRestClients.class.getName());
        Class<?>[] clientsClasses = (Class<?>[]) attributes.get("clients");
        //筛选所有的接口
        Stream.of(clientsClasses)
                .filter(Class::isInterface)//过滤接口
                .filter(interfaceClass -> {//过滤@RestClient
//                    return interfaceClass.isAnnotationPresent(RestClient.class);
                    return AnnotationUtils.findAnnotation(interfaceClass, RestClient.class) != null;
                })
                .forEach(restClientClass -> {
                    //获取@RestClient元信息
                    RestClient restClient = AnnotationUtils.findAnnotation(restClientClass, RestClient.class);
                    //获取应用名称(处理占位符)
                    String serviceName = environment.resolvePlaceholders(restClient.name());
                    //目的-》使用restTemplate->serviceName/uri?param...
                    //@RestClient接口变成动态代理
                    Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{restClientClass},
                            new RequestMappingMethodInvocationHandler(serviceName, beanFactory));
                    //注册bean
                    String beanName = "RestClient." + serviceName;
                    if(registry instanceof SingletonBeanRegistry){
                        SingletonBeanRegistry singletonBeanRegistry = (SingletonBeanRegistry)registry;
                        singletonBeanRegistry.registerSingleton(serviceName,proxy);
                    }
//                    registryBeanDefinition(beanName, registry, restClientClass, proxy);
                });
    }

    private void registryBeanDefinition(String beanName, BeanDefinitionRegistry registry, Class restClientClass, Object proxy) {
        BeanDefinition beanDefinition = null;
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RestClientClassFactory.class);
        beanDefinitionBuilder.addConstructorArgValue(restClientClass);
        beanDefinitionBuilder.addConstructorArgValue(proxy);
        registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
    }

    static class RestClientClassFactory implements FactoryBean {

        private final Class<?> restClientClass;

        private final Object proxy;

        public RestClientClassFactory(Class<?> restClientClass, Object proxy) {
            this.restClientClass = restClientClass;
            this.proxy = proxy;
        }

        @Override
        public Object getObject() throws Exception {
            return proxy;
        }

        @Override
        public Class<?> getObjectType() {
            return restClientClass;
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

3.RequestMappingMethodInvocationHandler动态代理

  • 拼接完整的url带参数
  • beanFactory获取customLoadBalanceRestTemplate负载均衡的RestTemplate对象
  • 远程调用,被LoadBalancedRequestInterceptor拦截
public class RequestMappingMethodInvocationHandler implements InvocationHandler {

    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

    private final String serviceName;

    private final BeanFactory beanFactory;

    public RequestMappingMethodInvocationHandler(String serviceName, BeanFactory beanFactory) {
        this.serviceName = serviceName;
        this.beanFactory = beanFactory;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RequestMapping requestMapping = AnnotationUtils.findAnnotation(method, RequestMapping.class);
        if (requestMapping != null) {
            String[] uri = requestMapping.value();
            StringBuilder urlBuilder = new StringBuilder("/").append(serviceName).append("/").append(uri[0]);
            //获取方法参数的数据
            int count = method.getParameterCount();
            Class<?>[] parameterTypes = method.getParameterTypes();
            Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
            String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);

            StringBuilder queryString = new StringBuilder();

            for (int i = 0; i < count; i++) {
                Class<?> parameterType = parameterTypes[i];
                //获取方法参数上面的注解
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                RequestParam requestParam = (RequestParam)parameterAnnotations[i][0];

                if (requestParam != null) {
                    String value = requestParam.value();
                    String requestparamName = StringUtils.hasText(value) ? value : parameterNames[0];
                    String requestparamValue = String.class.equals(parameterType) ? (String) args[i] : String.valueOf(args[i]);
                    queryString.append("&").append(requestparamName).append("=").append(requestparamValue);
                }
            }

            if (StringUtils.hasText(queryString.toString())) {
                urlBuilder.append("?").append(queryString);
            }

            String url = urlBuilder.toString();
            RestTemplate restTemplate = beanFactory.getBean("customLoadBalanceRestTemplate", RestTemplate.class);

            return restTemplate.getForObject(url, method.getReturnType());
        }
        return null;
    }
}
测试类
@RestClient(name = "${saying.rest.service.name}")
public interface SayingRestService {

    @RequestMapping("say")
    public String say(@RequestParam("message") String message);

}
yml
spring:
  application:
    name: spring-cloud-client-application
  server:
    name: spring-cloud-server-application
server:
  port: 8082

saying:
  rest:
    service:
      name: ${spring.server.name}
ClientController客户端调用类
@RestController
public class ClientController {

    @Autowired
    @CustomedLoadBalance
    private RestTemplate restTemplate;

    @Autowired
    private SayingRestService sayingRestService;

    @GetMapping("/rest/say")
    public Object restSay(@RequestParam("message") String message) {
        return sayingRestService.say(message);
    }

    @Bean
    public ClientHttpRequestInterceptor interceptor() {
        return new LoadBalancedRequestInterceptor();
    }

    @Bean
    @CustomedLoadBalance
    public RestTemplate customLoadBalanceRestTemplate() {
        return new RestTemplate();
    }

    @Bean
    @Autowired
    public Object customizer(@CustomedLoadBalance Collection<RestTemplate> restTemplates, ClientHttpRequestInterceptor interceptor) {
        restTemplates.forEach(restTemplate -> {
                    //增加拦截器
                    restTemplate.setInterceptors(Arrays.asList(interceptor));
                }
        );
        return new Object();
    }

}

服务端关键部分代码

   ...
   @GetMapping("/say")
    public String say(@RequestParam("message") String message) throws InterruptedException {
        int value = random.nextInt(200);
        System.out.println("say() cost " + value + "ms");
        Thread.sleep(value);
        System.out.println("接收到消息-say:" + message);
        return "Hello," + message;
    }
    ...

测试

1.浏览器输入地址

http://127.0.0.1:8082/rest/say?message=helloword11

2.被拦截到RequestMappingMethodInvocationHandler

因为调用SayingRestService接口实际上是调用的SayingRestService的动态代理RequestMappingMethodInvocationHandler

@EnableRestClients注解驱动在注册阶段使用RequestMappingMethodInvocationHandler代替SayingRestService注册到spring中的beanDefinition
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7WseGvkL-1571101316985)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571054518992.png)]

3.拼接完整url

在这里插入图片描述

4.被拦截到LoadBalancedRequestInterceptor

因为使用beanFactory.getBean("customLoadBalanceRestTemplate", RestTemplate.class);获取到customLoadBalanceRestTemplate,而customLoadBalanceRestTemplate增加了拦截器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8VzwKJQz-1571101316986)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571054614878.png)]

5.服务端注册两个实例,选择其中一台发送消息

返回结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BD98voQP-1571101316986)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1571054670091.png)]

感谢

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值