質問

Is it possible to access beans defined outside of the step scope? For example, if I define a strategy "strategyA" and pass it in the job parameters I would like the @Value to resolve to the strategyA bean. Is this possible? I am currently working round the problem by getting the bean manually from the applicationContext.

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

    MyTasklet myTasklet= new yTasklet();

    myTasklet.setStrategy(myCustomStrategy);

    return myTasklet;
}

I would like to have the ability to add more strategies without having to modify the code.

役に立ちましたか?

解決

The sort answer is yes. This is more general spring/design pattern issue rater then Spring Batch.
The Spring Batch tricky parts are the configuration and understanding scope of bean creation.
Let’s assume all your Strategies implement Strategy interface that looks like:

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

Every strategy should implements Strategy and use @Component annotation to allow automatic discovery of new Strategy. Make sure all new strategy will placed under the correct package so component scan will find them.
For example:

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

The above are singletons and will be created on the application context initialization.
This stage is too early to use @Value("#{jobParameters['strategy']}") as JobParameter wasn't created yet.

So I suggest a locator bean that will be used later when myTasklet is created (Step Scope).

StrategyLocator class:

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

}

Configuration will look like:

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

To initialize StrategyLocator we need to make sure all strategy were already created. So the best approach would be to use ApplicationListener on ContextRefreshedEvent event (warning in this example strategy names start with lower case letter, changing this is easy...).

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

}

The tasklet will hold a field of type String that will be injected with Strategy enum String using @Value and will be resolved using the locator using a "before step" Listener.

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

To attach the listener to the taskelt you need to set it in your step configuration:

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

他のヒント

jobParameters is holding just a String object and not the real object (and I think is not a good pratice store a bean definition into parameters).
I'll move in this way:

@Bean
@StepScope    
class MyStategyHolder {
  private MyCustomClass myStrategy;
  // Add get/set

  @BeforeJob
  void beforeJob(JobExecution jobExecution) {
    myStrategy = (Bind the right strategy using job parameter value);
  }
}

and register MyStategyHolder as listener.
In your tasklet use @Value("#{MyStategyHolder.myStrategy}") or access MyStategyHolder instance and perform a getMyStrategy().

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top