在 Spring 运行时注册 bean(原型)

IT小君   2021-10-25T02:56:28

只需要社区评估的东西。以下是一段代码,它是一个创建特定类型实例的简单工厂。该方法将在上下文中将 bean 注册为原型并返回实例。这是我第一次在运行时配置 bean。您能否评估并提供反馈?先感谢您。

package au.com.flexcontacts.flexoperations;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.AbstractApplicationContext;

import au.com.flexcontacts.exceptions.SyncClassCreactionError;

/**
 * @author khushroo.mistry
 * Class purpose: Simple Factory to create an 
 * instance of SynchroniseContactsService and register it in the Spring IoC.
 */
public final class FLEXSyncFactory implements ApplicationContextAware {

    private static AbstractApplicationContext context;


    /**
     * @param username
     * @param password
     * @param syncType
     * @return the correct service class
     * @throws SyncClassCreactionError
     * The method registers the classes dynamically into the Spring IoC
     */
    public final SynchroniseContactsService createSyncService(String username, String password, SyncType syncType) throws SyncClassCreactionError {

        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) context.getBeanFactory();

        try {

            //Register the bean in the IoC
            BeanDefinition bdb = new GenericBeanDefinition();
            bdb.setBeanClassName(syncType.getClassName());
            bdb.setScope("prototype");
            ConstructorArgumentValues constructor = bdb.getConstructorArgumentValues();
            constructor.addIndexedArgumentValue(0, username);
            constructor.addIndexedArgumentValue(1, password);
            beanFactory.registerBeanDefinition(syncType.getInstanceName(), bdb);

            //Return instance of bean
            return (SynchroniseContactsService) beanFactory.getBean(syncType.getInstanceName());
        } catch (Exception e) {
            e.printStackTrace();
            throw new SyncClassCreactionError("Error: Illegal Handler");
        }

    }

    public void setApplicationContext(ApplicationContext applicationContext)
    throws BeansException {
        context = (AbstractApplicationContext) applicationContext;

    }

}

FLEX Sync 工厂已在 IoC 容器中配置为单例。所以要创建一个新的同步管理器,我执行以下操作:

flexSyncFactory.createSyncService(userName, password, SyncType.FULL);

我正在使用 Spring 3.1。请查看并提供您宝贵的反馈。

亲切的问候。

点击广告,支持我们为你提供更好的服务
评论(3)
IT小君

这纯粹是我的观点,不是专家的观点:

Spring 提供了两种自定义修改应用程序上下文的机制 - 使用BeanFactoryPostProcessor允许修改现有 bean 定义或添加新 bean 定义,以及BeanPostProcessors允许修改 bean 实例(将它们包装在代理等周围)。

Spring 没有提供任何其他本地方式来在运行时动态添加 bean 定义或 bean 实例,但就像您通过获取底层 bean 工厂实例并添加 bean 定义所做的那样,是一种方法。它有效,但存在风险:

  • 如果用新类型覆盖现有 bean 名称会发生​​什么,如何处理已注入该 bean 的位置。此外,如果现有 bean 名称被完全不同的类型覆盖会发生什么!

  • 这个新注册的 bean 不会有任何自动装配的字段,也不会被注入到其他 bean 中——所以本质上 bean 工厂只是充当保存 bean 的注册表,而不是真正的依赖注入功能!

  • 如果refresh()在应用程序上下文中调用a ,则支持 bean 工厂将被覆盖并创建一个新工厂,因此任何直接针对 bean 工厂注册的 bean 实例都将丢失。

如果目标纯粹是创建由 Spring 自动装配的 bean,我会选择类似@Configurable 的东西如果上述风险是可以接受的,那么您的方法也应该有效。

2021-10-25T02:56:29   回复
IT小君

这对我有用:http : //random-thoughts-vortex.blogspot.com/2009/03/create-dynamically-spring-beans.html

声明一个专用的 Spring 上下文 bean,它将实现 ApplicationContextAware 和 BeanFactoryPostProcessor 接口:

  public class MyContextWrapper implements ApplicationContextAware,
             BeanFactoryPostProcessor {

   private ApplicationContext appContext;
   private ConfigurableListableBeanFactory factory;

   public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
              throws BeansException {
   this.factory = factory;
   }
   public void setApplicationContext(ApplicationContext c)
            throws BeansException {
   this.appContext = c;   
   }

   //setters and getters

}

通过在 XML 配置文件中声明 bean,让 spring 将此 bean 加载到它的上下文中:

<bean id="appContext" class="my.package.MyContextWrapper">
</bean>

现在这个 bean 可以通过引用它被加载到应用程序的任何其他 bean 中:

<bean id="myBeanFactory" class="my.package.MyBeanFactory">
 <property name="springContext" ref="appContext">
 </property>
</bean>

使用 GenericBeanDefinition 加载 bean 定义:

BeanDefinitionRegistry registry = ((BeanDefinitionRegistry )factory);

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(MyBeanClass.class);
beanDefinition.setLazyInit(false);
beanDefinition.setAbstract(false);
beanDefinition.setAutowireCandidate(true);
beanDefinition.setScope("session");

registry.registerBeanDefinition("dynamicBean",beanDefinition);

Bean 在会话范围内创建,并将存储在用户会话中。属性 auto wire 候选者告诉 spring 是否应该由 Spring 自动处理 bean 的依赖项,例如 setter 或 getter 或构造函数参数。属性 lazy init 告诉 Spring 是否应该在需要时实例化这个 bean。

要获取 Spring bean 的句柄,请使用 Spring 应用程序上下文,如下所示:

Object bean= 
 getApplicationContext().getBean("dynamicBean");
 if(bean instanceof MyBeanClass){
 MyBeanClass myBean = (MyBeanClass) bean;

   // do with the bean what ever you have to do.
 } 
2021-10-25T02:56:29   回复
IT小君

您的解决方案看起来不错。我相信我们也可以通过创建一个实现 BeanNameAware 和 FactoryBean 接口的 bean 来实现,然后我们在创建上下文之前设置值。

xxxxBean.beansByName.put("synTable", synTable);
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
assert externalDataSource == context.getBean("synTable");

这是Bean的实现

public class xxxxBean implements BeanNameAware, FactoryBean {

    public static Map<String, Object> beans = new HashMap<String, Object>();

    private String beanName;

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }

    @Override
    public Object getObject() {
        return beans.get(beanName);
    }

    @Override
    public Class<?> getObjectType() {
        return beans.get(beanName).getClass();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

}
2021-10-25T02:56:29   回复