
增加 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 BeanFactoryAware, EnvironmentAware {...}
自定义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
代码面纱的解密.




