在 Spring Batch 中访问 Step Scope 之外的 Bean

Ash*_*ell 3 spring spring-batch

是否可以访问在步骤范围之外定义的 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;
}
Run Code Online (Sandbox Code Playgroud)

我希望能够在无需修改代码的情况下添加更多策略。

Hai*_*man 5

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

interface Strategy {
    int execute(int a, int b); 
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

所以我推荐一个定位器 bean,稍后创建 myTasklet 时会用到它(Step Scope)。

策略定位器类:

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;
    }

}
Run Code Online (Sandbox Code Playgroud)

配置将如下所示:

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

要初始化 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);        
    }

}
Run Code Online (Sandbox Code Playgroud)

tasklet 将持有一个 String 类型的字段,该字段将使用 @Value 注入 Strategy enum String,并将使用定位器使用“before step”侦听器进行解析。

    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;
           }
    }
Run Code Online (Sandbox Code Playgroud)

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

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