Step Scope e Late Binding com SpEL no Spring Batch
Objective
A maioria dos beans do Spring é construída uma única vez, na inicialização
da aplicação, a partir de valores conhecidos em tempo de configuração. Um
job batch quebra essa suposição de propósito: o nome do arquivo de entrada,
um intervalo de datas, ou qualquer outro parâmetro de execução só é
conhecido quando o job de fato começa — às vezes só quando um step
específico dentro dele começa. O bean scope StepScope do Spring Batch
adia a instanciação de um bean até o step correspondente começar, e,
combinado com a Spring Expression Language (SpEL), esse instante adiado
também é o momento em que as propriedades de um bean podem ser preenchidas
a partir do contexto de execução daquele step — sem nome de arquivo
codificado na configuração, sem encanamento customizado para repassar
parâmetros de execução até um reader ou writer.
Use Cases
- Configurar um step de leitura de arquivo (
FlatFileItemReader, umTaskletcustomizado) cujo caminho do arquivo de entrada é fornecido como um parâmetro de job na hora da execução, em vez de embutido na configuração do Spring. - Compartilhar um valor calculado por um step anterior (escrito no contexto de execução do step ou do job) com o reader, writer ou tasklet de um step posterior, sem introduzir um canal separado para repassá-lo.
- Rodar a mesma definição de step para datas, regiões ou execuções de batch diferentes, parametrizando na hora da execução em vez de duplicar a configuração para cada variante.
Deep Dive
StepScope: um bean scope customizado atrelado ao ciclo de vida do step
O Spring suporta bean scopes customizados e conectáveis desde a versão 2 —
os scopes embutidos singleton/prototype/request/session ganham
companhia de qualquer implementação registrada via CustomScopeConfigurer.
O Spring Batch fornece um desses scopes, o StepScope, cujo propósito
inteiro é ligar o ciclo de vida de um bean a um step específico: um bean
step-scoped só é instanciado quando o step correspondente de fato começa,
não quando o container Spring ao redor sobe.
java@Bean
@StepScope
public FlatFileItemReader<Foo> flatFileItemReader(
@Value("#{jobParameters['input.file.name']}") String name) {
return new FlatFileItemReaderBuilder<Foo>()
.name("flatFileItemReader")
.resource(new FileSystemResource(name))
.build();
}StepScope não é registrado por padrão — a documentação de referência
atual do Spring Batch é explícita ao dizer que ele deve ser adicionado por
uma (e somente uma) de três vias: @EnableBatchProcessing, uma definição
explícita de bean StepScope, ou o namespace XML legado batch. O
equivalente da era XML do livro registra o mesmo scope como um bean:
plaintext<bean class="org.springframework.batch.core.scope.StepScope"/>
e marca um bean como step-scoped com um atributo scope simples:
plaintext<bean id="decompressTasklet" class="com.manning.sbia.ch01.batch.DecompressTasklet" scope="step"> <property name="inputResource" value="#{jobParameters['inputResource']}" /> <property name="targetDirectory" value="#{jobParameters['targetDirectory']}" /> <property name="targetFile" value="#{jobParameters['targetFile']}" /> </bean>
As duas formas fazem a mesma coisa: nada em decompressTasklet/
flatFileItemReader pode ser resolvido até o step ao qual pertence de fato
começar, porque seus valores de propriedade são expressões SpEL, não
literais.
Late binding com SpEL: três contextos que um bean step-scoped pode ler
A Spring Expression Language (SpEL), introduzida no Spring 3, é uma
linguagem de expressão geral utilizável em qualquer lugar do portfólio
Spring — não algo que o Spring Batch inventou, apenas algo em que ele se
apoia bastante aqui. As propriedades de um bean step-scoped podem
referenciar qualquer um dos três contextos via placeholders #{...}:
| Contexto | Descrição |
|---|---|
jobParameters |
Parâmetros fornecidos quando o job foi executado |
jobExecutionContext |
O contexto de execução compartilhado do job atual |
stepExecutionContext |
O próprio contexto de execução do step atual |
jobParameters é um map, indexado por chave: #{jobParameters['inputResource']}
resolve o parâmetro inputResource exatamente como foi passado na
execução. Os outros dois funcionam da mesma forma contra quaisquer pares
chave/valor que um step anterior (ou o próprio job) tenha escolhido
registrar — o que é como um step repassa um valor calculado para outro
posterior sem um canal customizado.
Por que isso importa: parametrizar sem codificar valores fixos
O ganho concreto, segundo o próprio estudo de caso do livro: um step de
importação de produtos precisa saber qual arquivo ler, mas esse arquivo
muda a cada execução. Sem step scope e SpEL, o nome do arquivo seria
codificado por ambiente (quebrando assim que o arquivo mudasse) ou repassado
manualmente. Com step scope, os parâmetros do job launcher fluem direto
para a configuração do reader no momento em que o step começa — a
configuração expressa "ler de qualquer arquivo que quem chama especificar",
não "ler de /data/import.csv".
Trade-offs
- Step scope só adia a instanciação para o início do step — não faz um bean se reinstanciar por item ou por chunk. Ele resolve "eu não sei esse valor até o step começar", não "eu preciso de uma instância nova por unidade de trabalho"; confundir os dois leva a recorrer a step scope quando um mecanismo diferente (como um reset de proxy escopado entre steps, ou gerenciamento de estado explícito) é o que de fato é necessário.
- Uma expressão SpEL referenciando um parâmetro de job que nunca foi
fornecido na execução falha no início do step, não em tempo de
configuração. Todo o benefício do late binding — adiar a resolução para
runtime — também é seu custo: um typo numa chave de parâmetro de job
(
#{jobParameters['inputResourc']}) compila sem problema e só aparece quando o step de fato tenta rodar. StepScopedeve ser registrado exatamente uma vez, por exatamente um de três mecanismos. A documentação de referência atual é explícita ao dizer que misturar vias (ex.: um beanStepScopeexplícito e@EnableBatchProcessing) não é o uso pretendido — escolha um e seja consistente, em vez de assumir que registrá-lo duas vezes é só redundante.- Book vs. today: o estilo principal de configuração migrou de XML
scope="step"para@StepScope+@Beanem configuração Java, seguindo a mesma mudança documentada em outros pontos deste workflow (<batch:job-repository>→@EnableJdbcJobRepository, vocabulário XML de job →JobBuilder/StepBuilder). A forma XML do livro (scope="step"num<bean>,<property value="#{jobParameters['x']}"/>) ainda funciona conceitualmente — as expressões SpEL e os três contextos disponíveis (jobParameters/jobExecutionContext/stepExecutionContext) estão inalterados — mas o próprio namespace XML de batch está depreciado desde o Spring Batch 6.0, com remoção planejada para a 7.0, a mesma migração já observada para o vocabulário job/step/chunk nos outros conceitos de Spring Batch deste workflow.