乐趣区

关于springboot:springboot项目运行过程中动态注入bean

[toc]

1. 指标

绝大部分的场景中,咱们的 bean(通常是借助几个注解如:@Service、@Component、@Controller 等)是在我的项目启动过程中,spring通过其依赖注入的相干伎俩帮咱们主动注入的。有时候咱们的 bean 须要在特定的业务场景中动静拆卸,在我的项目运行之前,无奈给出其相干的代码,咱们心愿在须要的时候,通过反射等伎俩,构建出一个对象,而后动静注入到 spring 容器中,交由其托管,后续就能够从容器中获取、应用。
原文地址

2. 外围api

一个 spring 利用能够通过 BeanDefinitionRegistry 类中的 registerBeanDefinition 办法动静注入bean

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition

2.1 BeanDefinition

BeanDefinition describes a bean instance. It has setter methods that can be used to programmatically set the Spring specific characteristics to a bean, for example, BeanDefinition #setScope(String scope) can be used to set a scope other than default singleton.

2.2 GenericBeanDefinition

This is the commonly used BeanDefinition implementation.

It allows specifying the bean class, bean characteristics plus constructor argument values and property values.

3.demo 环境

  • Spring Context 4.3.4.RELEASE: Spring Context.
  • JDK 1.8
  • Maven 3.3.9

4.demo

咱们次要通过如下几种形式实现:

  • GenericBeanDefinition
  • BeanDefinitionBuilder
  • BeanFactoryPostProcessor
  • BeanDefinitionRegistryPostProcessor

4.1 Dynamic Bean Registration With GenericBeanDefinition

GenericBeanDefinition is a one-stop-shop for standard bean definition purposes. Like any bean definition, it allows for specifying a class plus optionally constructor argument values and property values. Additionally, deriving from a parent bean definition can be flexibly configured through the“parentName”property.

In general, use this GenericBeanDefinition class for the purpose of registering user-visible bean definitions (which a post-processor might operate on, potentially even reconfiguring the parent name). Use RootBeanDefinition / ChildBeanDefinition where parent/child relationships happen to be pre-determined.


Creating Example Bean class

public class MyBean {
  private Date date;
  public void doSomething () {System.out.println("from my bean, date:" + date);
  }
  public void setDate (Date date) {this.date = date;}
}

Registering the above, created bean dynamically using GenericBeanDefinition.

public class GenericBeanDefinitionExample {public static void main (String[] args) {
      DefaultListableBeanFactory context =
                new DefaultListableBeanFactory();
      GenericBeanDefinition gbd = new GenericBeanDefinition();
      gbd.setBeanClass(MyBean.class);
      MutablePropertyValues mpv = new MutablePropertyValues();
      mpv.add("date", new Date());
      //alternatively we can use:
      // gbd.getPropertyValues().addPropertyValue("date", new Date());
      gbd.setPropertyValues(mpv);
      context.registerBeanDefinition("myBeanName", gbd);
      MyBean bean = context.getBean(MyBean.class);
      bean.doSomething();}
}

Outout

from my bean, date: Wed Jult 23 12:20:58 EDT 2019

4.2 Dynamic Bean Registration With BeanDefinitionBuilder

Programmatic means of constructing BeanDefinitions using the builder pattern. Intended primarily for use when implementing Spring 2.0 NamespaceHandlers. The only difference here is, BeanDefinitionBuilder uses Builder Pattern.


Creating another bean class

public class MyBean {
  private String str;
  public void setStr (String str) {this.str = str;}
  public void doSomething () {System.out.println("from MyBean" + str);
  }
}

Example to register the bean dynamically using BeanDefinitionBuilder.

public class BeanDefinitionBuilderExample {public static void main (String[] args) {
      DefaultListableBeanFactory beanFactory =
                new DefaultListableBeanFactory();
      BeanDefinitionBuilder b =
                BeanDefinitionBuilder.rootBeanDefinition(MyBean.class)
                                     .addPropertyValue("str", "myStringValue");
      beanFactory.registerBeanDefinition("myBean", b.getBeanDefinition());
      MyBean bean = beanFactory.getBean(MyBean.class);
      bean.doSomething();}
}

Output

from MyBean myStringValue

Injecting other bean references using BeanDefinitionBuilder

Creating Bean 1

public class Bean1 {
  private Bean2 otherBean;
  public void setOtherBean (Bean2 otherBean) {this.otherBean = otherBean;}
  public void doSomething () {otherBean.doSomething();
  }
}

Creating Bean2

public class Bean2 {public void doSomething () {System.out.println("from other bean");
  }
}

Seting the Bean2 in Bean1

public class InjectingOtherBeans {public static void main (String[] args) {
      DefaultListableBeanFactory context =
                new DefaultListableBeanFactory();
      //define and register MyOtherBean
      GenericBeanDefinition beanOtherDef = new GenericBeanDefinition();
      beanOtherDef.setBeanClass(Bean2.class);
      context.registerBeanDefinition("other", beanOtherDef);
      //definine and register myBean
      GenericBeanDefinition beanDef = new GenericBeanDefinition();
      beanDef.setBeanClass(Bean1.class);
      MutablePropertyValues mpv = new MutablePropertyValues();
      mpv.addPropertyValue("otherBean", context.getBean("other"));
      beanDef.setPropertyValues(mpv);
      context.registerBeanDefinition("myBean", beanDef);
      //using MyBean instance
      MyBean bean = context.getBean(MyBean.class);
      bean.doSomething();}
}

Output

from other bean

4.3 Dynamic Bean Registration With BeanFactoryPostProcessor

A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances. Allows for custom modification of an application context’s bean definitions, adapting the bean property values of the context’s underlying bean factory. Application contexts can auto-detect BeanFactoryPostProcessor beans in their bean definitions and apply them before any other beans get created.


Creating config

@Configuration
public class MyConfig {
  @Bean
  MyConfigBean myConfigBean () {return new MyConfigBean();
  }
}

BeanFactoryPostProcessor allows client code to customize bean definitions. The method BeanFactoryPostProcessor.postProcessBeanFactory is called by the Spring startup process just after all bean definitions have been loaded, but no beans have been instantiated yet.

public class MyConfigBean implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory)
            throws BeansException {GenericBeanDefinition bd = new GenericBeanDefinition();
      bd.setBeanClass(MyBean.class);
      bd.getPropertyValues().add("strProp", "my string property");
      ((DefaultListableBeanFactory) beanFactory)
                .registerBeanDefinition("myBeanName", bd);
  }
}

Creating another Bean for Demo

public class MyBean {
  private String strProp;
  public void setStrProp (String strProp) {this.strProp = strProp;}
  public void doSomething () {System.out.println("from MyBean:" + strProp);
  }
}

Main class for BeanFactoryPostProcessor Example.

public class BeanFactoryPostProcessorExample {public static void main (String[] args) {
      AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MyConfig.class);
      MyBean bean = context.getBean(MyBean.class);
      bean.doSomething();}
}

Output

from MyBean:  my string property
WARNING: @Bean method MyConfig.myConfigBean is non-static and returns an object assignable to Spring's BeanFactoryPostProcessor interface. This will result in a failure to process annotations such as @Autowired, @Resource and @PostConstruct within the method's declaring @Configuration class. Add the 'static' modifier to this method to avoid these container lifecycle issues; see @Bean javadoc for complete details.

4.4 Dynamic Bean Registration With BeanDefinitionRegistryPostProcessor

Extension to the standard BeanFactoryPostProcessor SPI, allowing for the registration of further bean definitions before regular BeanFactoryPostProcessor detection kicks in. In particular, BeanDefinitionRegistryPostProcessor may register further bean definitions which in turn define BeanFactoryPostProcessor instances.


Creating another config class.

@Configuration
public class MyConfig {
  @Bean
  MyConfigBean myConfigBean () {return new MyConfigBean();
  }
}

This is a sub-interface of BeanFactoryPostProcessor (last example). It allows for the registration of bean definitions. It’s method postProcessBeanDefinitionRegistry is called before BeanFactoryPostProcessor#postProcessBeanFactory. This interface is more focused on the BeanDefinition registration rather than general-purpose BeanFactoryPostProcessor.


Creating implementation class for BeanDefinitionRegistryPostProcessor.

public class MyConfigBean implements BeanDefinitionRegistryPostProcessor {
  @Override
  public void postProcessBeanDefinitionRegistry (BeanDefinitionRegistry registry)
            throws BeansException {GenericBeanDefinition bd = new GenericBeanDefinition();
      bd.setBeanClass(MyBean.class);
      bd.getPropertyValues().add("strProp", "my string property");
      registry.registerBeanDefinition("myBeanName", bd);
  }
  @Override
  public void postProcessBeanFactory (ConfigurableListableBeanFactory beanFactory)
            throws BeansException {//no op}
}

Creating a brand new Bean class

public class MyBean {
  private String strProp;
  public void setStrProp (String strProp) {this.strProp = strProp;}
  public void doSomething () {System.out.println("from MyBean:" + strProp);
  }
}

Main class for BeanDefinitionRegistryPostProcessor Example.

public class BeanDefinitionRegistryPostProcessorExample {public static void main (String[] args) {
      AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(MyConfig.class);
      MyBean bean = (MyBean) context.getBean("myBeanName");
      bean.doSomething();}
}

Output

from MyBean:  my string property
WARNING: Cannot enhance @Configuration bean definition 'beanDefinitionRegistryPostProcessorExample.MyConfig' since its singleton instance has been created too early. The typical cause is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor return type: Consider declaring such methods as 'static'.

4.5 运行时卸载 bean

To remove or unregister the bean from spring context.

beanRegistry.removeBeanDefinition("bean")

To delete/clear the singleton bean from context.

beanRegistry.destroySingleton("bean")
退出移动版