【问题标题】:Accessing Beans outside of the Step Scope in Spring Batch在 Spring Batch 中访问 Step 范围之外的 Bean
【发布时间】:2014-05-09 09:16:28
【问题描述】:

是否可以访问在步骤范围之外定义的 bean?例如,如果我定义一个策略“strategyA”并将其传递到作业参数中,我希望 @Value 解析为 strategyA bean。这可能吗?我目前正在通过从 applicationContext 手动获取 bean 来解决这个问题。

@Bean
@StepScope
public Tasklet myTasklet(
        @Value("#{jobParameters['strategy']}") MyCustomClass myCustomStrategy)

    MyTasklet myTasklet= new yTasklet();

    myTasklet.setStrategy(myCustomStrategy);

    return myTasklet;
}

我希望能够添加更多策略而无需修改代码。

【问题讨论】:

    标签: spring spring-batch


    【解决方案1】:

    排序答案是肯定的。这是比 Spring Batch 更通用的 spring/设计模式问题评估器。
    Spring Batch 的棘手部分是 bean 创建的配置和理解范围。
    让我们假设你所有的 Strategies 都实现了如下所示的 Strategy 接口:

    interface Strategy {
        int execute(int a, int b); 
    };
    

    每个策略都应该实现策略并使用@Component 注解来允许自动发现新的策略。确保所有新策略都放在正确的包下,以便组件扫描可以找到它们。
    例如:

    @Component
    public class StrategyA implements Strategy {
        @Override
        public int execute(int a, int b) {
            return a+b;
        }
    }
    

    以上是单例,将在应用程序上下文初始化时创建。
    这个阶段使用 @Value("#{jobParameters['strategy']}") 为时尚早,因为 JobParameter 尚未创建。

    所以我建议使用一个定位器 bean,稍后在创建 myTasklet 时使用它(Step Scope)。

    StrategyLocator 类:

    public class StrategyLocator {
        private Map<String, ? extends Strategy> strategyMap;
    
        public Strategy lookup(String strategy) {
            return strategyMap.get(strategy);
        }
    
        public void setStrategyMap(Map<String, ? extends Strategy> strategyMap) {
            this.strategyMap = strategyMap;
        }
    
    }
    

    配置如下:

    @Bean
    @StepScope
    public MyTaskelt myTasklet () {
          MyTaskelt myTasklet = new MyTaskelt();
          //set the strategyLocator
          myTasklet.setStrategyLocator(strategyLocator());
          return myTasklet;
    }
    @Bean 
    protected StrategyLocator strategyLocator(){
        return  = new StrategyLocator();    
    }    
    

    要初始化 StrategyLocator,我们需要确保所有策略都已创建。所以最好的方法是在 ContextRefreshedEvent 事件上使用 ApplicationListener (在这个例子中警告策略名称以小写字母开头,改变这个很容易......)。

    @Component
    public class PlugableStrategyMapper implements ApplicationListener<ContextRefreshedEvent> {
        @Autowired
        private StrategyLocator strategyLocator;
        @Override
        public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
            ApplicationContext applicationContext = contextRefreshedEvent.getApplicationContext();
            Map<String, Strategy> beansOfTypeStrategy = applicationContext.getBeansOfType(Strategy.class);
            strategyLocator.setStrategyMap(beansOfTypeStrategy);        
        }
    
    }
    

    tasklet 将保存一个 String 类型的字段,该字段将使用 @Value 注入 Strategy enum String ,并将使用“前一步”侦听器使用定位器解析。

        public class MyTaskelt implements Tasklet,StepExecutionListener {
            @Value("#{jobParameters['strategy']}")
            private String strategyName;
            private Strategy strategy;
            private StrategyLocator strategyLocator;
    
            @BeforeStep
            public void beforeStep(StepExecution stepExecution) {
                strategy = strategyLocator.lookup(strategyName);        
            }
            @Override
            public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                int executeStrategyResult = strategy.execute(1, 2); 
            }
               public void setStrategyLocator(StrategyLocator strategyLocator) {
                   this.strategyLocator = strategyLocator;
               }
        }
    

    要将侦听器附加到 taskelt,您需要在步骤配置中进行设置:

    @Bean
    protected Step myTaskletstep() throws MalformedURLException {
         return steps.get("myTaskletstep")
        .transactionManager(transactionManager())
        .tasklet(deleteFileTaskelt())
        .listener(deleteFileTaskelt())
        .build();
     }
    

    【讨论】:

    • 感谢 Haim 提供了如此全面且解释清楚的示例。我担心我的问题没有得到很好的解释。如果可能的话,我希望避免对每个策略的显式依赖。我希望能够提供对 bean 的引用(使用从策略作业参数派生的名称)。就我而言,删除这种耦合更为重要,因此我认为我将不得不使用我的解决方法,即使用 applicationContext.getBean(strategyFromJobParams)。
    • 您能详细说明一下吗?正如你所说的那样,Spring 无论如何都会为每个 Strategy 创建一个实例。 applicationContext.getBean(strategyFromJobParams)。是一个引用所有 bean 并将其耦合到 Spring API 的大地图。我的解决方案将 applicationContext 替换为特定 bean 的有限映射的定位器, enun 用作对 Strategy bean 的引用。
    • 我希望能够添加更多策略而无需修改代码(将自动装配策略 C 添加到代码和地图中)。这在以前使用 XML 配置是可能的,我认为有一种方法可以使用 JavaConfig 而不必使用 applicationContext.getBean。这不是什么大不了的事,我只是认为这是可能的
    • Ash McConnell 看到我的更改,我添加了支持表单可插入状态。
    【解决方案2】:

    jobParameters 只持有一个 String 对象,而不是真正的对象(我认为将 bean 定义存储到参数中不是一个好习惯)。
    我会这样移动:

    @Bean
    @StepScope    
    class MyStategyHolder {
      private MyCustomClass myStrategy;
      // Add get/set
    
      @BeforeJob
      void beforeJob(JobExecution jobExecution) {
        myStrategy = (Bind the right strategy using job parameter value);
      }
    }
    

    并将MyStategyHolder注册为监听器。
    在你的 tasklet 中使用@Value("#{MyStategyHolder.myStrategy}") 或访问MyStategyHolder 实例并执行getMyStrategy()

    【讨论】:

    • 感谢您的回答!我可能会感到困惑,但是您的代码与我的原始答案有相同的问题,如果不转到应用程序上下文并使用作业参数选择正确的 bean,就无法绑定策略。例如,#{jobParameter['strategy']} 应该尝试查找名称为 strategyA 的 bean(因为它使用 # 而不是 $)。这似乎适用于 XML 配置,但是当我们转移到 JavaConfig 时它已经停止工作
    • 对不起,但老实说我从未使用过 JavaConfig。您可以尝试将myStrategyHolder 连接到 tasklet 并使用 getter() 访问策略
    • 策略是返回单调对象还是原型
    • 您的选择;取决于您的要求
    • 策略回报是单身
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    • 2012-05-26
    • 1970-01-01
    • 1970-01-01
    • 2013-07-10
    • 1970-01-01
    相关资源
    最近更新 更多