通過實現仿照FeignClient框架原理的示例來看清FeignClient的本質
- 2021 年 11 月 14 日
- 筆記
- feignclient, JAVA
前言
FeignClient的實現原理網上一搜一大把,此處我就不詳細再說明,比如:Feign原理 (圖解) – 瘋狂創客圈 – 部落格園 (cnblogs.com),而且關於FeignClient的使用技巧我之前文章《feignclient各種使用技巧說明》已經講過,此處僅說一下核心步驟:
啟動時:@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】;
實際注入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】
執行時:(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. 定義註解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DemoClient {
//定義相關的註解屬性
}
2. 定義標註了@DemoClient註解介面對應的真實代理類FactoryBean
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
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的註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DemoClientsRegistrar.class)
public @interface EnableDemoClient {
}
5.最後將第4步定義的註解@EnableDemoClient添加到Spring Applcation入口類上即可
@SpringBootApplication
@EnableDemoClient
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class,args);
}
}
實際用法示例如下:
@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方法
}
}