模拟FeignClient实现接口代理注册配置

前言

在上一篇博文中,介绍了Spring Cloud如何为FeignClient生成代理类,并配置成Bean。该博文将参照FeignClient,实现接口代理并注册配置成Bean。

代码目录层次介绍

代码整体目录层次如下,参照FeignClient注册体系实现,即通过EnableDefinition引入DefinitionBeanRegister,从而开启标记DefinitionAnno接口的扫描。DefinitionClientFactoryBean,用于收集DefinitionAnno注解的相关信息,并通过动态代理,基于Ribbon完成微服务调用。
在这里插入图片描述

二、代码实现细节

1、DefinitionBeanRegister

通过EnableDefinition注解中的Import,引入DefinitionBeanRegister,基于该类完成特定接口的扫描动作。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(DefinitionBeanRegister.class)
public @interface EnableDefinition {
}

该Register工作内容主要分为如下几项

1、扫描特定路径下,标记有DefinitionAnno注解的接口。
2、收集DefinitionAnno注解,并配置成DefinitionClientFactoryBean。
public class DefinitionBeanRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware {

    protected ResourceLoader resourceLoader;

    protected Environment environment;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = this.getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        scanner.addIncludeFilter(new AnnotationTypeFilter(DefinitionAnno.class));
        Set<BeanDefinition> definitions = scanner.findCandidateComponents("com.quelongjiang");

        for (BeanDefinition definition : definitions) {
            if (definition instanceof AnnotatedBeanDefinition) {
                AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) definition;

                AnnotationMetadata metadata = beanDefinition.getMetadata();
                Assert.isTrue(metadata.isInterface(), "@DefinitionAnno can only be specifyed on an interface");

                Map<String, Object> attributes = metadata.getAnnotationAttributes(DefinitionAnno.class.getCanonicalName());
                this.registerDefinitionClient(registry, metadata, attributes);
            }
        }
    }

    protected void registerDefinitionClient(BeanDefinitionRegistry registry, AnnotationMetadata metadata, Map<String, Object> attributes) {
        String className = metadata.getClassName();
        BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(DefinitionClientFactoryBean.class);

        definitionBuilder.addPropertyValue("serverName", attributes.get("serverName"));
        definitionBuilder.addPropertyValue("requestUrl", attributes.get("requestUrl"));
        definitionBuilder.addPropertyValue("requestMethod", attributes.get("requestMethod"));
        definitionBuilder.addPropertyValue("type", className);
        definitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
        beanDefinition.setPrimary(true);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, metadata.getClassName(), new String[] {attributes.get("serverName") + "_Client"});
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }

    protected ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                boolean isCandidate = false;
                if (beanDefinition.getMetadata().isIndependent()) {
                    if (!beanDefinition.getMetadata().isAnnotation()) {
                        isCandidate = true;
                    }
                }
                return isCandidate;
            }
        };
    }

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

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
}

2、DefinitionClientFactoryBean

该类,通过RequestInvocationHandler内部类,实现接口的动态代理。代理类的主要动作,就是通过Ribbon实现请求发送处理。

public class DefinitionClientFactoryBean implements FactoryBean<Object>, InitializingBean, ApplicationContextAware {

    private String serverName;

    private String requestUrl;

    private HttpMethod requestMethod;

    private Class<?> type;

    protected ApplicationContext atc;

    public String getServerName() {
        return serverName;
    }

    public void setServerName(String serverName) {
        this.serverName = serverName;
    }

    public String getRequestUrl() {
        return requestUrl;
    }

    public void setRequestUrl(String requestUrl) {
        this.requestUrl = requestUrl;
    }

    public HttpMethod getRequestMethod() {
        return requestMethod;
    }

    public void setRequestMethod(HttpMethod requestMethod) {
        this.requestMethod = requestMethod;
    }

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    @Override
    public Object getObject() throws Exception {
        RestTemplate restTemplate = this.atc.getBean("loadBalanced", RestTemplate.class);

        return Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class<?>[] {type}, new RequestInvocationHandler(restTemplate));
    }

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

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.hasText(this.serverName, "serviceName must be set");
    }

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

    class RequestInvocationHandler implements InvocationHandler {

        protected RestTemplate restTemplate;

        public RequestInvocationHandler(RestTemplate restTemplate) {
            this.restTemplate = restTemplate;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            String url = this.getRequestUrl(method);
            HttpMethod executeMethod = this.getRequestMethod(method);
            if (executeMethod == HttpMethod.GET) {
                return this.restTemplate.getForObject(url, Map.class);
            }
            else {
                return this.restTemplate.postForObject(url, new HashMap<>(), String.class);
            }
        }

        protected String getRequestUrl(Method method) {
            if (!method.isAnnotationPresent(RequestInfoAnno.class)) {
                return String.format("http://%s/%s", serverName, requestUrl);
            }
            else {
                String methodRequest = method.getAnnotation(RequestInfoAnno.class).requestUrl();
                methodRequest = methodRequest.startsWith("/") ? methodRequest : "/" + methodRequest;
                return String.format("http://%s/%s%s", serverName, requestUrl, methodRequest);
            }
        }

        protected HttpMethod getRequestMethod(Method method) {
            return method.isAnnotationPresent(RequestInfoAnno.class) ? method.getAnnotation(RequestInfoAnno.class).requestMethod() : requestMethod;
        }
    }
}

3、LoadBalanceRestTemplateConfiguration

通过LoadBalanced,配置RestTemplate。由于对接的服务,需要认证才能访问,此处为该RestTemplate添加了一个BasicAuthenticationInterceptor拦截器。

@Configuration
@EnableDefinition
public class LoadBalanceRestTemplateConfiguration {

    @Bean("loadBalanced")
    @LoadBalanced
    public RestTemplate restTemplate() {
        RestTemplate restTemplate = new RestTemplate();

        restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor("que.longjiang", "123456"));
        return restTemplate;
    }
}

4、EurekaServerRequestClient

此处,定义一个使用DefinitionAnno标记的接口,用于后续测试代码使用。

@DefinitionAnno(serverName = "quelongjiang-cloud-server", requestMethod = HttpMethod.GET)
public interface EurekaServerRequestClient {

    @RequestInfoAnno(requestUrl = "eureka/apps", requestMethod = HttpMethod.GET)
    Object showEurekaInfoes();

    @RequestInfoAnno(requestUrl = "eureka/apps/delta", requestMethod = HttpMethod.GET)
    Map<String, Object> showEurekaDelta();
}

5、DefinitionRequestController

一个测试类

@RestController
@RequestMapping("quelongjiang/definitionRequestController")
public class DefinitionRequestController {

    @Autowired
    protected EurekaServerRequestClient requestClient;

    @GetMapping("eureka-infoes")
    public Object showEurekaInfoes() {
        return this.requestClient.showEurekaInfoes();
    }

    @GetMapping("eureka-delta")
    public Object showEurekaDelta() {
        return this.requestClient.showEurekaDelta();
    }
}

启动相关服务,调用请求。
在这里插入图片描述
在这里插入图片描述

总结

1、通过ClassPathScanningCandidateComponentProvider,获取注解标记类。
2、收集BeanDefinitionBuilder.genericBeanDefinition收集注解等相关信息,用于后续的注册。
3、通过动态代理,实现接口具体动作。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要做个有钱人2020

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值