Spring工程通过注解实现动态声明Bean

Easter79
• 阅读 780

此前在一个旧的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);
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
spring注解
随着越来越多地使用Springboot敏捷开发,更多地使用注解配置Spring,而不是Spring的applicationContext.xml文件。Configuration注解:Spring解析为配置类,相当于spring配置文件Bean注解:容器注册Bean组件,默认id为方法名@Configurat
Easter79 Easter79
3年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Stella981 Stella981
3年前
SpringBoot自定义序列化的使用方式
场景及需求:项目接入了SpringBoot开发,现在需求是服务端接口返回的字段如果为空,那么自动转为空字符串。例如:\    {        "id":1,        "name":null    },    {        "id":2,        "name":"x
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k