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

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

前言

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方法
    }
}
hmoban主题是根据ripro二开的主题,极致后台体验,无插件,集成会员系统
自学咖网 » 通过实现仿照FeignClient框架原理的示例来看清FeignClient的本质