通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质

WenJun.Zuo ITer

前言

FeignClient的实现原理网上一搜一大把,此处我就不详细再说明,比如:Feign原理 (图解) - 疯狂创客圈 - 博客园 (cnblogs.com) ,而且关于FeignClient的使用技巧我之前文章《feignclient各种使用技巧说明 》已经讲过,此处仅说一下核心步骤:

  1. 启动时:@EnableFeignClients注解–>@Import(FeignClientsRegistrar.class)–>FeignClientsRegistrar.registerBeanDefinitions–>org.springframework.cloud.netflix.feign.FeignClientsRegistrar#registerFeignClients–>扫描有添加了@FeignClient注解的接口类注解BEAN元信息列表【即:AnnotatedBeanDefinition】–>org.springframework.cloud.netflix.feign.FeignClientsRegistrar#registerFeignClient–>构建一个FeignClientFactoryBean的BeanDefinitionBuilder,并将type等相关信息设置给FeignClientFactoryBean,–>BeanDefinitionReaderUtils.registerBeanDefinition【即注册成FactoryBean】;

  2. 实际注入FeignClient接口类依赖时:根据FeignClient接口类class找到FeignClientFactoryBean对象实例–>org.springframework.cloud.netflix.feign.FeignClientFactoryBean#getObject–>org.springframework.cloud.netflix.feign.FeignClientFactoryBean#feign【得到Feign.Builder】–>targeter = get(context, Targeter.class);–>targeter.target–>feign.target(target)–>feign.Feign.Builder#build–>feign.ReflectiveFeign#newInstance–>handler = factory.create(target, methodToHandler)【得到InvocationHandler】

  3. 执行时:(feign.hystrix.HystrixInvocationHandler【feign.hystrix.enabled=true时】 OR feign.ReflectiveFeign.FeignInvocationHandler#)#invoke –>dispatch.get(method).invoke(args);【得到代理方法SynchronousMethodHandler并执行该方法】–>Client#execute【Client的实现类,其中:LoadBalancerFeignClient 是使用ribbon组件时默认实现的】

上面核心步骤其实也还是很多,我这里一句概括核心:将@FeignClient标注的接口类通过FeignClientFactoryBean生成代理类(InvocationHandler,注意有多种实现子类),再执行InvocationHandler.invoke方法,间接执行内部的MethodHandler(SynchronousMethodHandler实现类之一)invoke方法,最后由实际的Client来完成远程URL请求及响应结果转换;其中最重要也是复杂的是InvocationHandler的实现类、MethodHandler的实现类;

FeignClient的扩展点非常多,比如:FeignClientsConfiguration 类中所有默认配置均可以自行替换自定义的实现类,若需单个FeignClient生效,则可通过@FeignClient注解的configuration属性指明对应这个FeignClient的特有配置类(如:MyFeignClientConfiguration)【注意自定义的配置类此处不能使用@Configuration注解,否则将导致全局生效,不加@Configuration注解时,则会由对应的contextId的FeignClientContext单独创建】

那么说了这么多,为了大家能够理解FeignClient的核心实现原理,同时因为我项目中也要实现类似的功能(目的让开发人员对复杂部份透明,调用远程BEAN的方法就像调本地一样,即RPC的初衷),我(梦在旅途 www.zuowenjun.cn)通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质,代码全部贴出来了,大家应该一看就懂,不懂复制到DEMO项目中DEBUG起来就也就明白了。实际运行的结果符合预期;

1. 定义注解

1
2
3
4
5
6
7
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DemoClient {
//定义相关的注解属性
}

2. 定义标注了@DemoClient注解接口对应的真实代理类FactoryBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class DemoClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

private Class<?> type;

private ApplicationContext context;

//可添加其它属性字段(同时记得至少添加setter方法)

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.context = applicationContext;
}

@Override
public Object getObject() throws Exception {
return Proxy.newProxyInstance(RemoteTestClientFactoryBean.class.getClassLoader(), new Class<?>[]{this.type}, 自定义代理对象处理类【需继承自InvocationHandler,也可以直接采用lambda表达式】,这里也是真正的执行业务逻辑的核心);
}


@Override
public Class<?> getObjectType() {
return this.type;
}

@Override
public boolean isSingleton() {
return true;
}

@Override
public void afterPropertiesSet() throws Exception {

}

/**
* 设置当前工厂要生成的BEAN Type,由BeanWrapper动态赋值,内部支持根据class String直接转换为Class对象
*
* @param type
*/
public void setType(Class<?> type) {

this.type = type;
}
}

3. 定义扫描标注了@DemoClient注解接口并自动注册为如上第2步定义的FactoryBean的Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class DemoClientsRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

private Environment environment;

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

@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
ClassPathScanningCandidateComponentProvider scanner=getScanner();
scanner.addIncludeFilter(new AnnotationTypeFilter(RemoteTestClient.class));
Set<BeanDefinition> candidateComponents=scanner.findCandidateComponents(ClassUtils.getPackageName(Application.class));
if (CollectionUtils.isEmpty(candidateComponents)){
return;
}

for (BeanDefinition candidateComponent : candidateComponents) {
if (candidateComponent instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;
AnnotationMetadata metadata = beanDefinition.getMetadata();
Assert.isTrue(metadata.isInterface(),
"@DemoClient can only be specified on an interface");

registerDemoClient(beanDefinitionRegistry,metadata);
}
}

}

private void registerDemoClient(BeanDefinitionRegistry registry,AnnotationMetadata annotationMetadata){
String className = annotationMetadata.getClassName();
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(DemoClientFactoryBean.class);
//这里将类名传给class<?>属性,看似类型不匹配,其实赋值时会由BeanWrapper进行自动转换
definition.addPropertyValue("type",className);
//这里还可以赋值更多属性,具体依据DemoClientFactoryBean的属性定义
registry.registerBeanDefinition(className + "DemoClient",definition.getBeanDefinition());
}

protected ClassPathScanningCandidateComponentProvider getScanner() {
return new ClassPathScanningCandidateComponentProvider(false, this.environment) {

@Override
protected boolean isCandidateComponent(
AnnotatedBeanDefinition beanDefinition) {
//必需是独立的 且 是接口 才是符合扫描条件的【可以更严格的过滤判断】
if (beanDefinition.getMetadata().isIndependent() && beanDefinition.getMetadata().isInterface()) {
if (beanDefinition.getMetadata().isInterface()
&& beanDefinition.getMetadata()
.getInterfaceNames().length == 1
&& Annotation.class.getName().equals(beanDefinition
.getMetadata().getInterfaceNames()[0])) {
try {
Class<?> target = ClassUtils.forName(
beanDefinition.getMetadata().getClassName(),
RemoteTestClientsRegistrar.class.getClassLoader());
return !target.isAnnotation();
}
catch (Exception ex) {
this.logger.error(
"Could not load target class: "
+ beanDefinition.getMetadata().getClassName(),
ex);

}
}
return true;
}
return false;

}
};
}


}

4.定义配置启动扫描并注册代理Bean的注解

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

}

5.最后将第4步定义的注解@EnableDemoClient添加到Spring Applcation入口类上即可

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDemoClient
public class Application {

public static void main(String[] args){
SpringApplication.run(Application.class,args);
}
}

实际用法示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@DemoClient
public interface Demo1Client{
String getRemoteResult(Long id);
}

@Service
public class Demo1Service{
@Autowired
private Demo1Client demo1Client;//此处实际注入的是DemoClientFactoryBean.getObject方法返回的InvocationHandler的代理类实例

public String doMany(Long id){
return demo1Client.getRemoteResult(id);//实际调用的是:InvocationHandler的代理类实例的invoke方法
}
}
  • 标题: 通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质
  • 作者: WenJun.Zuo
  • 创建于 : 2024-02-29 14:36:00
  • 更新于 : 2024-02-29 14:36:00
  • 链接: https://www.zuowenjun.cn//2024/02/29/feign-client-mock-demo/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论