此前在一个旧的spring项目中实现了手动配置接入Eureka,同时生成了一个Feign的客户端。刚好现在有一个新的微服务API需要接入。于是想实现在springboot中通过@FeignClient自动声明客户端bean的功能,过程也不是很复杂,在此分享一下过程
一、实现@FeignClient注解
@FeignClient是 spring-cloud-starter-openfeign 项目中用于springboot自动配置声明Feign客户端的注解,并不属于Feign原有的注解,所以这里我要自己去写一个@FeignClient自己用
1@Target(value = ElementType.TYPE) 2@Retention(value = RetentionPolicy.RUNTIME) 3@Documented 4public @interface FeignClient{ 5 String value(); 6}
由于构造客户端时我只需要用到一个url,所以不需要额外的属性,有一个value足矣。有其他需要的朋友可以自己添加需要的注解属性。下面是一个Feign客户端,使用了自定义的FeignClient注解
1package my.feign.client; 2 3import my.annotation.FeignClient; 4import my.app.model.* 5 6import feign.Headers; 7import feign.RequestLine; 8 9@FeignClient("http://unionpay-api") 10@Headers({ "Content-Type: application/json", "Accept: application/json;charset=UTF-8" }) 11public interface UnionpayService { 12 @RequestLine("POST /pos/transaction/pay") 13 PayResult pay(PayRequest request); 14}
二、动态注册Bean
工程启动时我需要扫描指定包内的代码,查找所有@FeignClient注解的类,数量也是不定的,所以这里我们需要动态的声明bean方式。关键就是 BeanDefinitionRegistryPostProcessor 这个spring接口。这个接口扩展自BeanFactoryPostProcessor,专门用于动态注册Bean,spring官方解释是:允许在正常的BeanFactoryPostProcessor检测开始_之前_注册更多的自定义bean。
在这里我只利用它优先于其他Bean声明检测前执行这一特性,否则在其他类使用@Autowire注入我们的客户端时会报异常。
本例我们使用BeanFactory来注册一个单例Bean,所以使用postProcessBeanFactory方法
1@Configuration 2public class FeignConfig implements BeanDefinitionRegistryPostProcessor { 3 4 // 类中还有若干方法用于构建EurekaClient和RibbonClient,由于本文主题不是如何构建Feign客户端,在此不一一列出,有需要的可以邮件找我要 mailto: kit.li@qq.com 5 6 @Override 7 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { 8 9 } 10 11 @Override 12 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 13 ApplicationInfoManager applicationInfoManager = this.applicationInfoManager(); 14 Provider<EurekaClient> provider = () -> this.eurekaClient(applicationInfoManager); 15 ServerListUpdater serverListUpdater = new EurekaNotificationServerListUpdater(provider); 16 17 // 扫描指定package下所有带有FeignClient注解的类,自已实现的方法,找我或百度都有 18 Set<Class> classSet = ClassUtil.getClass4Annotation("my.feign.client", FeignClient.class); 19 classSet.forEach(cls -> { 20 // 读取注解的value,得到服务的url,截取其中的VipAddress 21 FeignClient anno = (FeignClient) cls.getAnnotation(FeignClient.class); 22 String url = anno.value(); 23 String vip = url.substring(url.indexOf("://") + 3); 24 25 // 创建Feign客户端实例 26 IClientConfig clientConfig = this.ribbonClientConfig(cls.getName(),vip); 27 ServerList<Server> serverList = (ServerList<Server>) this.ribbonServerList(clientConfig, provider); 28 ServerListFilter<Server> serverListFilter = this.ribbonServerListFilter(clientConfig); 29 ILoadBalancer loadBalancer = new DynamicServerListLoadBalancer<>(clientConfig, new RandomRule(), 30 new NIWSDiscoveryPing(), serverList, serverListFilter, serverListUpdater); 31 Client client = RibbonClient.builder().lbClientFactory(name -> 32 LBClient.create(loadBalancer, ClientFactory.getNamedConfig(name))).build(); 33 Object service = buildServiceClient(cls, client, url); 34 35 // 注册成单例 36 beanFactory.registerSingleton(cls.getName(), service); 37 }); 38 } 39} 40
三、结语
至此已经成功实现了Bean的动态生成,适用于一些想通过注解动态声明bean的场合。例子中因为需要需要其他方式创建bean实例,所以用了postProcessBeanFactory方法。也如以使用BeanDefinition方式注册bean。例如:
1Class<?> cls = MyService.class; 2BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(cls); 3GenericBeanDefinition definition = (GenericBeanDefinition) builder.getRawBeanDefinition(); 4definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE); 5definition.getPropertyValues().add("name","unionpay"); 6 7registry.registerBeanDefinition("myService", definition);