暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Spring Cloud Feign初探秘

爱动漫的程序员 2020-05-28
380

增加 Spring Cloud Feign 依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

整合 @EnableFeignClients

@SpringBootApplication // 标准 Spring Boot 应用
@EnableDiscoveryClient // 激活服务发现客户端
@EnableScheduling
@EnableFeignClients
public class SpringCloudClientApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SpringCloudClientApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

整合@EnableFeignClients

@SpringBootApplication // 标准 Spring Boot 应用
@EnableDiscoveryClient // 激活服务发现客户端
@EnableScheduling
@EnableFeignClients
public class SpringCloudClientApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(SpringCloudClientApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

整合 @FeignClient

@FeignClient(name = "spring-cloud-server-application")
public interface SayingService {

    @GetMapping("/say")
    String say(@RequestParam("message") String message);

}

注入SayingService

    @Autowired
    private SayingService sayingService;

调用SayingService

    @GetMapping("/feign/say")
    public String feignSay(@RequestParam String message) {
        return sayingService.say(message);
    }

实现自定义 RestClient(模拟 @FeignClient)从而对比体会Fegin的实现逻辑

 1@SpringBootApplication // 标准 Spring Boot 应用
2@EnableDiscoveryClient // 激活服务发现客户端
3@EnableScheduling
4@EnableFeignClients(clients = SayingService.class) // 引入 FeignClient
5@EnableRestClient(clients = SayingRestService.class) // 引入 @RestClient
6@EnableBinding(SimpleMessageService.class) // 激活并引入 SimpleMessageService
7public class SpringCloudClientApplication {
8
9    public static void main(String[] args) {
10        new SpringApplicationBuilder(SpringCloudClientApplication.class)
11                .web(WebApplicationType.SERVLET)
12                .listeners(new HttpRemoteAppEventListener())
13                .run(args);
14    }
15}

首先,通过启动类的注解@EnableFeignClients

1@Retention(RetentionPolicy.RUNTIME)
2@Target(ElementType.TYPE)
3@Documented
4@Import(FeignClientsRegistrar.class)
5public @interface EnableFeignClients {
6   ...
7}

上标记@Import(FeignClientsRegistrar.class)
,这时通过进入FeignClientsRegistrar
类进行观察,发现该类实现了ImportBeanDefinitionRegistrar
接口,EnvironmentAware
接口,ResourceLoaderAware
接口和BeanClassLoaderAware
接口.
其中ImportBeanDefinitionRegistrar
的主要功能是在启动的时候向BeanDefinitionRegistry
中注册FeignClient
接口所标注的类的代理类.
EnvironmentAware
接口提供了setEnvironment
方法

1@Override
2    public void setEnvironment(Environment environment) {
3        this.environment = environment;
4    }

该方法,将spring容器中的Environment
对象注册到当前实体类中,该对象主要保存了spring容器所扫描到的所有的配置信息.其中就包括了FeignClient
所使用的的服务提供方的信息.
BeanClassLoaderAware
接口主要提供了setBeanClassLoader
方法.该方法主要提供了当前的BeanClassLoader
的信息

1@Override
2    public void setBeanClassLoader(ClassLoader classLoader) {
3
4    }

通过观察接口的实现和代码的实现不难理解,该类主要的的就是将FeignClient
注解所标记的接口与对应的服务的提供方建立联系.在springcloud中,主要是与注册中心获得对应的联系,并通过负载均衡算法将该请求负载到对应的服务提供方.

代码实现

通过模仿@EnableFeignClients
注解的方式开启自定义实现Bean加载类的加载.

 1@Retention(RetentionPolicy.RUNTIME)
2@Target(ElementType.TYPE)
3@Documented
4@Import(RestClientsRegistrar.class)
5public @interface EnableRestClient {
6
7    /**
8     * 指定 @RestClient 接口
9     * @return
10     */

11    Class<?>[] clients() default {};
12}

通过@Enable
注解的特性,建立对应的RestClientsRegistrar

1public class RestClientsRegistrar implements ImportBeanDefinitionRegistrar,
2        BeanFactoryAwareEnvironmentAware 
{...}

自定义RestClient
注解代码:

 1@Target(ElementType.TYPE)
2@Retention(RetentionPolicy.RUNTIME)
3@Documented
4public @interface RestClient {
5
6    /**
7     * REST 服务应用名称
8     * @return
9     */

10    String name();
11}

自定义注解修饰类代码:

 1@RestClient(name = "${saying.rest.service.name}")
2public interface SayingRestService {
3
4    @GetMapping("/say")
5    String say(@RequestParam("message") String message)// 请求参数和方法参数同名
6
7    public static void main(String[] args) throws Exception {
8        Method method = SayingRestService.class.getMethod("say", String.class);
9        Parameter parameter = method.getParameters()[0];
10        System.out.println(parameter.getName());
11
12        parameter.isNamePresent();
13
14        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
15
16        Stream.of(nameDiscoverer.getParameterNames(method)).forEach(System.out::println);
17    }
18}

通过实现与FeignClientsRegistrar
类相似的接口从而达到将自定义RestClient
所标注的接口进行代理之后和对应的服务提供方建立联系.主要核心方法为:

 1@Override
2    public void registerBeanDefinitions(AnnotationMetadata metadata,
3                                        BeanDefinitionRegistry registry)
 
{
4 ClassLoader classLoader = metadata.getClass().getClassLoader();
5
6        Map<String, Object> attributes =
7                metadata.getAnnotationAttributes(EnableRestClient.class.getName());
8
9        // attributes -> { clients :  SayingRestService}
10        Class<?>[] clientClasses = (Class<?>[]) attributes.get("clients");
11        // 接口类对象数组
12        // 筛选所有接口
13        Stream.of(clientClasses)
14                .filter(Class::isInterface) // 仅选择接口
15                .filter(interfaceClass ->
16                        findAnnotation(interfaceClass, RestClient.class) != null// 仅选择标注 @RestClient
17                .forEach(restClientClass -> {
18                    // 获取 @RestClient 元信息
19                    RestClient restClient = findAnnotation(restClientClass, RestClient.class);
20                    // 获取 应用名称(处理占位符)
21                    String serviceName = environment.resolvePlaceholders(restClient.name());
22
23                    // RestTemplate -> serviceName/uri?param=...
24
25                    // @RestClient 接口编程 JDK 动态代理
26                    Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{restClientClass},
27                            new RequestMappingMethodInvocationHandler(serviceName, beanFactory));
28
29                    // 将 @RestClient 接口代理实现注册为 Bean(@Autowired)
30                    // BeanDefinitionRegistry registry
31
32                    String beanName = "RestClient." + serviceName;
33                    // 实现方略二:SingletonBeanRegistry
34                    if (registry instanceof SingletonBeanRegistry) {
35                        SingletonBeanRegistry singletonBeanRegistry = (SingletonBeanRegistry) registry;
36                        singletonBeanRegistry.registerSingleton(beanName, proxy);
37                    }
38//                    registerBeanByFactoryBean(serviceName,proxy,restClientClass,registry);
39                });
40}

主要代码逻辑为:通过启动类标注的EnableRestClient
注解,拿到所需要的代理类信息.通过筛选,拿出只被RestClient
注解标注的接口.之后拿到RestClient
注解上所标注的信息.并通过environment
环境变量信息,拿到配置文件中所对应的服务提供方的application.name
.并通过当前的接口信息,类加载器,服务提供方信息和beanFactory
通过 JDK 动态代理 创建代理类RequestMappingMethodInvocationHandler
.

 1public class RequestMappingMethodInvocationHandler implements InvocationHandler {
2
3    private final ParameterNameDiscoverer parameterNameDiscoverer
4            = new DefaultParameterNameDiscoverer();
5
6    private final String serviceName;
7
8    private final BeanFactory beanFactory;
9
10    public RequestMappingMethodInvocationHandler(String serviceName,
11                                                 BeanFactory beanFactory)
 
{
12        this.serviceName = serviceName;
13        this.beanFactory = beanFactory;
14    }
15
16    @Override
17    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
18
19        // 过滤 @RequestMapping 方法
20        GetMapping getMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
21        if (getMapping != null) {
22            // 得到 URI
23            String[] uri = getMapping.value();
24            // http://${serviceName}/${uri}
25            StringBuilder urlBuilder = new StringBuilder("http://").append(serviceName).append("/").append(uri[0]);
26            // 获取方法参数数量
27            int count = method.getParameterCount();
28            // 方法参数是有顺序
29            // FIXME
30//            String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
31            // 方法参数类型集合
32            Class<?>[] paramTypes = method.getParameterTypes();
33            Annotation[][] annotations = method.getParameterAnnotations();
34            StringBuilder queryStringBuilder = new StringBuilder();
35            for (int i = 0; i < count; i++) {
36                Annotation[] paramAnnotations = annotations[i];
37                Class<?> paramType = paramTypes[i];
38                RequestParam requestParam = (RequestParam) paramAnnotations[0];
39                if (requestParam != null) {
40                    String paramName = "";
41//                            paramNames[i];
42                    // HTTP 请求参数
43                    String requestParamName = StringUtils.hasText(requestParam.value()) ? requestParam.value() :
44                            paramName;
45                    String requestParamValue = String.class.equals(paramType)
46                            ? (String) args[i] : String.valueOf(args[i]);
47                    // uri?name=value&n2=v2&n3=v3
48                    queryStringBuilder.append("&")
49                            .append(requestParamName).append("=").append(requestParamValue);
50                }
51            }
52
53            String queryString = queryStringBuilder.toString();
54            if (StringUtils.hasText(queryString)) {
55                urlBuilder.append("?").append(queryString);
56            }
57
58            // http://${serviceName}/${uri}?${queryString}
59            String url = urlBuilder.toString();
60
61            // 获取 RestTemplate , Bean 名称为“loadBalancedRestTemplate”
62            // 获得 BeanFactory
63            RestTemplate restTemplate = beanFactory.getBean("loadBalancedRestTemplate", RestTemplate.class);
64
65            return restTemplate.getForObject(url, method.getReturnType());
66
67        }
68        return null;
69    }
70}

代理类主要包含的代码逻辑解析

通过当前被代理对象所执行的方法,获取到GetMapping
注解所标注的信息.通过GetMapping
注解标注的信息拿到对应的访问方法路径信息,即我们所谓的URI
,通过当前被代理类执行的方法,获取到RequestParam
注解所标注的信息.最后通过StringBuilder
拼接的方式,进行调取链路和参数的拼接,最后通过RestTemplate
远程调用对应的服务提供方.

注意

通过代码逻辑的反复测试和经过小马哥的精细化测试,得出结论,RequestParam
所标注的信息虽然通过javac -g
观察方法的签名可以看到对应所标注参数的参数名称,但是由于JDK的bug所导致,RequestParam
修饰的方法变量所在的类如果是接口,则无法拿到字段名称.笔者功力有限,就不一一阐述说明.当RequestParam
修饰的方法所在的类为实体类的时候,才可以拿到对应字段的信息.

这也解释了为什么FeignClient
需要强校验RequestParam
注解的value属性的意义所在.

对应调试代码
 1 public static void main(String[] args) throws Exception {
2        Method method = SayingRestService.class.getMethod("say", String.class);
3        Parameter parameter = method.getParameters()[0];
4        System.out.println(parameter.getName());
5
6        parameter.isNamePresent();
7
8        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
9
10        Stream.of(nameDiscoverer.getParameterNames(method)).forEach(System.out::println);
11    }

代码中`registerBeanByFactoryBean`方法浅析:

本实例主要包含了两种方式实现了类的注册:
1.通过BeanDefinitionBuilder
实现BeanDefinition
的构建,之后通过BeanDefinitionBuilder
BeanDefinition
构建所需要的信息通过addConstructorArgValue
传递进去,类似于

1        /**
2         *  <bean class="User">
3         *          <constructor-arg>${}</construtor-arg>
4         *      </bean>
5         */


的实现方式.
registerBeanByFactoryBean
代码展示:

 1    private static void registerBeanByFactoryBean(String serviceName,
2                                                  Object proxy, Class<?> restClientClass, BeanDefinitionRegistry registry)
 
{
3
4        String beanName = "RestClient." + serviceName;
5
6        BeanDefinitionBuilder beanDefinitionBuilder =
7                BeanDefinitionBuilder.genericBeanDefinition(RestClientClassFactoryBean.class);
8
9        /**
10         *  <bean class="User">
11         *          <constructor-arg>${}</construtor-arg>
12         *      </bean>
13         */

14        // 增加第一个构造器参数引用 : proxy
15        beanDefinitionBuilder.addConstructorArgValue(proxy);
16        // 增加第二个构造器参数引用 : restClientClass
17        beanDefinitionBuilder.addConstructorArgValue(restClientClass);
18
19        BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
20        registry.registerBeanDefinition(beanName, beanDefinition);
21
22    }
23    private static class RestClientClassFactoryBean implements FactoryBean {
24
25        private final Object proxy;
26
27        private final Class<?> restClientClass;
28
29        private RestClientClassFactoryBean(Object proxy, Class<?> restClientClass) {
30            this.proxy = proxy;
31            this.restClientClass = restClientClass;
32        }
33
34        @Nullable
35        @Override
36        public Object getObject() throws Exception {
37            return proxy;
38        }
39
40        @Nullable
41        @Override
42        public Class<?> getObjectType() {
43            return restClientClass;
44        }
45    }

2.通过Spring的高级APISingletonBeanRegistry
实现BeanDefinition
的注册.
代码展示:

1                    // 实现方略二:SingletonBeanRegistry
2                    if (registry instanceof SingletonBeanRegistry) {
3                        SingletonBeanRegistry singletonBeanRegistry = (SingletonBeanRegistry) registry;
4                        singletonBeanRegistry.registerSingleton(beanName, proxy);
5                    }

总结

笔者通过模仿EnableFeignClients
注解的方式开始,完成了一套类似于FeignClient
的调用逻辑,读者可以自行比对两者的实现差异,从而达到对FeignClient
代码面纱的解密.


文章转载自爱动漫的程序员,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论