Spring - 在运行时注册作用域 Bean

15

我正在开发一个基于Spring的应用程序,它注册了一个自定义范围“任务”。其想法是当启动一个新任务时,Spring应提供任务范围的对象。

任务在运行时实例化。它以Properties对象的形式提供一些配置。我想将该对象注册到ApplicationContext中,但在任务范围内,以便该范围内的所有bean都可以引用特定任务的配置。

下面是大致的代码思路:

public class MyTask extends SourceTask {
    @Override
    public void start(Map<String, String> props) {
        context = ContextProvider.getApplicationContext();
        // Initialize the scope
        ConnectorTaskScope scope = context.getBean(ConnectorTaskScope.class);
        scope.startNewTask();

        // TODO register the props object in the context

        // get an object which requires the properties and work with it
        context.getBean(SomeScopedBean.class);        
    }
}

我不知道如何在适当的范围内注册一个Bean到ApplicationContext中。

谢谢。

更新:

为了更好地解释问题,这里有更多的代码。 SomeScopedBean应该使用它提供的配置做一些操作,看起来像这样:

public class SomeScopedBean {
    @Autowire
    public SomeScopedBean (Properties configuration) {
        // do some work with the configuration 
    }
}

应用程序的想法是它应该有多个使用不同配置运行的MyTask实例,每个任务都有自己的范围。在每个任务的范围内,应初始化1个使用该任务配置的SomeScopedBean实例。

Translated content:

The idea of the application is that it should have multiple instances of MyTask running with different configurations, and each task has its own scope. Within the scope of each task, there should be one instance of SomeScopedBean initialized with the task's configuration.

public class MyApplication {
    public static void main (String[] args) {
        // ...
        Properties config1 = loadConfiguration1();
        Properties config2 = loadConfiguration2();
        MyTask task1 = new MyTask();
        MyTask task2 = new MyTask();
        task1.start(config1);
        task2.start(config2);
        // ...
    }
}

2
http://memorynotfound.com/spring-custom-scope-creating-and-implementing-threadscope/ - StanislavL
1
@StanislavL,我已经实现了自定义作用域,并且它运行良好。问题是如何在运行时注册一个有作用域的bean。 - artemb
你尝试过使用 scope=prototype 吗?它会在注入时创建一个类的新实例。 - Downhillski
使用任务工厂代替任务Bean怎么样? - Mike Wojtyna
1
我不是专家,但这种方法似乎有点不太对。作用域用于分离bean实例,但看起来你想要分离bean定义。通常的工作流程是:创建bean定义,并让作用域决定是否创建新实例或使用现有实例。 - chimmi
显示剩余5条评论
2个回答

5

如果我理解您的最后一条评论:

我希望在每个作用域(即每个 MyTask 中)内各有 1 个 SomeScopedBean 实例,但每个实例都配置了不同的配置属性(这些属性由部署框架在实例化每个 Task 时提供)。

特别是 within each MyTask,如果限定于 MyTask,您可以采取以下措施:

  • SomeScopedBean 定义为原型 bean。
  • 创建一个工厂 @Configuration,该工厂将使用提供的属性配置实例化 SomeScopedBean

首先是配置:

@Configuration
public class SomeScopedBeanFactoryConfiguration {

    @Bean
    @Scope(BeanDefinition.SCOPE_PROTOTYPE)
    public SomeScopedBean create(Properties configuration) {
        return new SomeScopedBean(configuration);
    }

}

然后将SomeScopedBeanFactoryConfiguration自动装配到MyTask中,并创建SomeScopedBean

public class MyTask extends SourceTask {

    @Autowired
    private SomeScopedBeanFactoryConfiguration  someScopedBeanFactoryConfiguration;

    @Override
    public void start(Map<String, String> props) {
        SomeScopedBean scopedBean = someScopedBeanFactoryConfiguration.create(props);    
    }
}

注意:如果必须在具有任务/线程范围的多个bean中注入SomeScopedBean,则可以将其范围更改为线程范围之一,例如:
    @Bean
    @Scope("thread")
    public SomeScopedBean create(Properties configuration) {
        return new SomeScopedBean(configuration);
    }

这看起来很有前途。我想知道在同一(“任务”)范围内的其他bean是否可以需要SomeScopedBean并提供正确配置的实例。我会尝试一下。 - artemb
如果您将SomeScopedBean的范围设置为您的task范围而不是prototype范围,则应该可以。 - Nicolas Labrot
是的!它完全按照我的意愿工作了!设置有点不直观,但是它按照我想要的方式工作了!谢谢。 - artemb

2
AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();

MyTask instance = new MyTask();
beanFactory.autowireBean(instance);
beanFactory.initializeBean(instance, MyTask.class.getCanonicalName());

//for singleton I used
((ConfigurableListableBeanFactory)beanFactory).registerSingleton(MyTask.class.getCanonicalName(), instance);

在您的情况下,我建议注册MyTask代理的单例。代理可以将所有作用域相关实例(例如在Map或ThreadLocal存储中)保存,并在调用时将逻辑委托给Map中的正确实例。
更新: 实际上,您自动装配的不是MyTask bean,而是代理。代理包装了所有MyTask方法。代理与MyTask具有相同的接口。假设您调用ProxyMyTask.do()方法。代理拦截调用,以某种方式获取范围(例如,在Tread Scope示例中获取当前线程),并从Map(或对于Thread Scope,从ThreadLocal存储)中获取正确的MyTask实例。最后调用找到的MyTask实例的do()方法。
更新2: 请参见示例http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html。您可以轻松地包装一个接口。确定范围并返回适当实例的逻辑应该在方法中。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    return method.invoke(target, args);
}

好的,如果MyTask将所有范围相关实例存储在一个映射中,当调用MyTask方法时,它如何知道它是在哪个作用域内被调用的? - artemb
谢谢!在这种情况下,MyTaskProxy - 这是我开发的还是由Spring提供的? - artemb
再次更新 - StanislavL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接