通過實現仿照FeignClient框架原理的示例來看清FeignClient的本質

夢在旅途發表於2021-11-14

前言

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. 定義註解

@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方法
    }
}

相關文章