首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot基础篇Bean之动态注册

SpringBoot基础篇Bean之动态注册

作者头像
一灰灰blog
发布2019-05-26 15:56:11
1.6K0
发布2019-05-26 15:56:11
举报
文章被收录于专栏:小灰灰小灰灰

I. 手动注册Bean方式

1. 核心实现类

以前也写过关于动态注册Bean的博文,如 180804-Spring之动态注册bean

我们的实现方式和上面也没什么区别,依然是借助BeanDefinition来创建Bean定义并注册到BeanFactory中,具体实现的核心代码如下

public class ManualRegistBeanUtil {

     /**
     * 主动向Spring容器中注册bean
     *
     * @param applicationContext Spring容器
     * @param name               BeanName
     * @param clazz              注册的bean的类性
     * @param args               构造方法的必要参数,顺序和类型要求和clazz中定义的一致
     * @param <T>
     * @return 返回注册到容器中的bean对象
     */
    public static <T> T registerBean(ConfigurableApplicationContext applicationContext, String name, Class<T> clazz,
            Object... args) {
        if(applicationContext.containsBean(name)) {
            Object bean = applicationContext.getBean(name);
            if (bean.getClass().isAssignableFrom(clazz)) {
                return (T) bean;
            } else {
                throw new RuntimeException("BeanName 重复 " + name);
            }
        }


        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        for (Object arg : args) {
            beanDefinitionBuilder.addConstructorArgValue(arg);
        }
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();

        BeanDefinitionRegistry beanFactory = (BeanDefinitionRegistry) applicationContext.getBeanFactory();
        beanFactory.registerBeanDefinition(name, beanDefinition);
        return applicationContext.getBean(name, clazz);
    }
}

上面唯一的方法中,接收四个参数,源码中也有说明,稍微需要注意下的是Spring容器中不允许出现同名的Bean

2. 测试用例

动态创建Bean,并不是塞入容器之中就完结了,塞进去之后,是为了后续的使用,自然而然的就会有下面几种情形

a. 无其他Bean依赖

即不依赖其他的Bean, 单纯的供其他地方使用,这种情况下,主要需要测试的就是别人可以通过什么方式来使用它

@Slf4j
public class ManualBean {

    private int id;

    public ManualBean() {
        Random random = new Random();
        id = random.nextInt(100);
    }

    public String print(String msg) {
        return "[ManualBean] print : " + msg + " id: " + id;
    }
}
b. 依赖其他Bean

和前面一个不同,这个Bean内部需要注入其他的Bean,因此我们主动注册Bean时,能否将依赖的Bean也注入进去呢?

定义一个测试Bean

@Slf4j
public class ManualDIBean {

    private int id;

    @Autowired
    private OriginBean originBean;

    private String name;

    public ManualDIBean(String name) {
        Random random = new Random();
        this.id = random.nextInt(100);
        this.name = name;
    }

    public String print(String msg) {
        String o = originBean.print(" call by ManualDIBean! ");
        return "[ManualDIBean] print: " + msg + " id: " + id + " name: " + name + " originBean print:" + o;
    }
}

其依赖的普通Bean定义如下

@Slf4j
@Component
public class OriginBean {

    private LocalDateTime time;

    public OriginBean() {
        time = LocalDateTime.now();
    }

    public String print(String msg) {
        return "[OriginBean] print msg: " + msg + ", time: " + time;
    }
}
c. 普通Bean依赖主动注册的Bean

这个其实就是使用case了,主动注册的Bean也是被人使用的,那可以怎么使用呢?传统的Autowired可否?

@Slf4j
@Component
public class AnoOriginBean {
    // 希望可以注入 主动注册的Bean
    @Autowired
    private ManualBean manualBean;

    public AnoOriginBean() {
        System.out.println("AnoOriginBean init: " + System.currentTimeMillis());
    }

    public String print() {
        return "[AnoOriginBean] print!!! manualBean == null ? " + (manualBean == null);
    }
}
d. Bean注册实现

前面定义了两个需要手动注册的bean,所以就需要选择一个合适的地方来处理主动注册的逻辑,我们把这段逻辑放在AutoConfig中,用于测试演示

@Configuration
public class BeanRegisterAutoConf {

    public BeanRegisterAutoConf(ApplicationContext applicationContext) {
        System.out.println("BeanRegisterAutoConf init: " + System.currentTimeMillis());
        registerManualBean((ConfigurableApplicationContext) applicationContext);
    }

    /**
     * 手动注册自定义地bean
     * @param applicationContext
     */
    private void registerManualBean(ConfigurableApplicationContext applicationContext) {
        // 主动注册一个没什么依赖的Bean
        ManualBean manualBean = ManualRegistBeanUtil.registerBean(applicationContext, "manualBean", ManualBean.class);
        manualBean.print("test print manualBean");

        // manualDIBean 内部,依赖由Spring容器创建的OriginBean
        ManualDIBean manualDIBean = ManualRegistBeanUtil.registerBean(applicationContext, "manualDIBean",
                ManualDIBean.class, "依赖OriginBean的自定义Bean");
        manualDIBean.print("test print manualDIBean");
    }
}

3. 实测演示

前面的测试case都准备好了,接着就需要实际的跑一下看看效果了,选择Rest服务来演示,创建一个简单的Controller

@RestController
public class ShowController {

    @Autowired
    private ManualBean manualBean;
    @Autowired
    private ManualDIBean manualDIBean;
    @Autowired
    private AnoOriginBean anoOriginBean;

    public ShowController() {
        System.out.println("ShowController init: " + System.currentTimeMillis());
    }

    @GetMapping(path = "show")
    public String show(String msg) {
        Map<String, String> result = new HashMap<>(8);
        result.put("manualBean", manualBean == null ? "null" : manualBean.print(msg));
        result.put("manualDIBean", manualDIBean == null ? "null" : manualDIBean.print(msg));
        result.put("anoOriginBean",anoOriginBean.print());
        return JSONObject.toJSONString(result);
    }
}

上面就使用了三个Bean,两个主动注册的外加一个依赖了主动注册Bean的anoOriginBean (其实Controller本身也是一个使用主动注册Bean的Bean)

先预测一下结果:

  • 如果 manualBean, manualDIBean 为空,表示不能直接通过 @Autowired 注解的方式引入手动注册的Bean;此时会抛npe
  • 如果没有npe,且 AnoOriginBean内部依赖的manualBean也不是null,则表示直接用@Autowired来注入没啥毛病(是否绝对呢?)
  • manualDIBean 内部依赖了originBean,也是通过注解方式注入,如果正常返回,表示手动注册的也可以这么引用其他的Bean;否则不行
手动注册演示
手动注册演示

执行结果如上图,简单来说,就是手动注册的Bean,和我们一般使用的Bean也没什么两样,原来可以怎么用,现在依然可以这么用

II. BeanDefinitionRegistryPostProcessor扩展方式

前面这种手动注入的方式有个不好的地方就是主动注册的这个逻辑,感觉写在什么地方都不太优雅,在Spring项目的源码中通过实现BeanDefinitionRegistryPostProcessor扩展方式接口的方式比较多,比如org.springframework.cloud.autoconfigure.RefreshAutoConfiguration

依葫芦画瓢实现一个

1. 实现类

@Slf4j
@Configuration
public class AutoBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 注册Bean定义,容器根据定义返回bean

        //构造bean定义
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
                .genericBeanDefinition(AutoBean.class);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        //注册bean定义
        registry.registerBeanDefinition("autoBean", beanDefinition);


        // AutoDIBean 的注入方式
        beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(AutoDIBean.class);
        beanDefinitionBuilder.addConstructorArgValue("自动注入依赖Bean");
        beanDefinition = beanDefinitionBuilder.getBeanDefinition();
        registry.registerBeanDefinition("autoDiBean", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        // 注册Bean实例,使用supply接口, 可以创建一个实例,并主动注入一些依赖的Bean;当这个实例对象是通过动态代理这种框架生成时,就比较有用了

        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(AutoFacDIBean.class, () -> {
            AutoFacDIBean autoFacDIBean = new AutoFacDIBean("autoFac");
            autoFacDIBean.setAutoBean(factory.getBean("autoBean", AutoBean.class));
            autoFacDIBean.setOriginBean(factory.getBean("originBean", OriginBean.class));
            return autoFacDIBean;
        });
        BeanDefinition beanDefinition = builder.getRawBeanDefinition();
        ((DefaultListableBeanFactory) factory).registerBeanDefinition("autoFacDIBean", beanDefinition);
    }
}

接口的实现中,Bean的注册方式和前面的其实是一样的,这个接口提供了两个方法,通常实现第一个方法来做Bean的注册;两者从根本上也没太大的区别,上面只是给出了一种使用演示

2. 测试用例

测试的思路基本上和前面一样,定义了三个需要我们注册的Bean,一个没有外部依赖的AutoBean

public class AutoBean {

    public String print() {
        return "[AutoBean] " + System.currentTimeMillis();
    }
}

一个依赖外部Bean的AutoDIBean

public class AutoDIBean {

    private String name;

    @Autowired
    private OriginBean originBean;

    public AutoDIBean(String name) {
        this.name = name;
    }

    public String print() {
        return "[AutoDIBean] " + name + " originBean == null ? " + (originBean == null);
    }

}

一个用于主动创建和设置依赖的AutoFacDIBean (用于前面的实现类中的第二个方法的注册方式)

public class AutoFacDIBean {
    private String name;

    @Setter
    private OriginBean originBean;

    @Setter
    private AutoBean autoBean;

    public AutoFacDIBean(String name) {
        this.name = name;
    }

    public String print() {
        return "[AutoDIBean] " + name + " originBean == null ? " + (originBean == null) + " | autoBean==null ? " +
                (autoBean == null);
    }

}

一个依赖了主动注册AutoBean的 AnoAutoOriginBean

@Component
public class AnoAutoOriginBean {
    @Autowired
    private AutoBean autoBean;

    public AnoAutoOriginBean() {
        System.out.println("AnoAutoOriginBean init: " + System.currentTimeMillis());
    }

    public String print() {
        return "[AnoAutoOriginBean] print!!! autoBean == null ? " + (autoBean == null);
    }
}

3. 实测演示

同样写一个RestApi进行演示,通过实际的演示结果发现和前面没什么太大的区别

@Autowired
private AutoBean autoBean;
@Autowired
private AutoDIBean autoDIBean;
@Autowired
private AutoFacDIBean autoFacDIBean;
@Autowired
private AnoAutoOriginBean anoAutoOriginBean;
@GetMapping(path = "auto")
public String autoShow() {
    Map<String, String> result = new HashMap<>(8);
    result.put("autoBean", autoBean == null ? "null" : autoBean.print());
    result.put("manualDIBean", autoDIBean == null ? "null" : autoDIBean.print());
    result.put("autoFacDIBean",autoFacDIBean == null ? "null" : autoFacDIBean.print());
    result.put("anoAutoOriginBean",anoAutoOriginBean.print());
    return JSONObject.toJSONString(result);
}
接口方式注册演示
接口方式注册演示
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I. 手动注册Bean方式
    • 1. 核心实现类
      • 2. 测试用例
        • a. 无其他Bean依赖
        • b. 依赖其他Bean
        • c. 普通Bean依赖主动注册的Bean
        • d. Bean注册实现
      • 3. 实测演示
      • II. BeanDefinitionRegistryPostProcessor扩展方式
        • 1. 实现类
          • 2. 测试用例
            • 3. 实测演示
            相关产品与服务
            容器服务
            腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档