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