Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.
For the best experience please use the latest Chrome, Safari or Firefox browser.
Beans
ApplicationContext
.Beans
//Soit la classe suivante que l'on faire gérer par Spring
public class BeanExemple{
private String msg;
private String nom;
public void getNom(){return nom;}
public void setNom(String n){nom = n;}
public BeanExemple(String m){msg=m;}
public void init() {}
public void cleanup() {}
}
<!-- applicationContext.xml -->
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "..." ... >
<bean name="beanExemple" class="com.myproject.BeanExemple"
scope="prototype" init-method="init" destroy-method="cleanup">
<constructor-arg value = "Hello "/><!-- OU -->
<constructor-arg index = "0" value = "Hello "/><!-- OU -->
<constructor-arg type = "java.lang.String" value = "Hello "/>
<property name = "nom" value = "World"/>
</bean>
</beans>
@Configuration
public class SpringConfig {
@Bean(initMethod = "init", destroyMethod = "cleanup")
@Scope("prototype")
public BeanExemple beanExemple(){
BeanExemple be = new BeanExemple("Hello ");
be.setNom("World");
return be;
}
}
public class BeanExemple{
private AutreBeanEx autreBean;
public void getAutreBean(){ return autreBean; }
public void setAutreBean(String b){
autreBean = b;
}
}
<!-- applicationContext.xml -->
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "..." ... >
<bean name="autreBean" class="com.myproject.AutreBeanEx"/>
<bean name="beanExemple" class="com.myproject.BeanExemple">
<property name="autreBean" ref="autreBean"/>
<bean/>
</beans>
@Configuration
public class SpringConfig {
@Bean
public BeanExemple beanExemple(){
BeanExemple b = new BeanExemple();
b.setAutreBean(autreBeanEx());
}
@Bean
public AutreBeanEx autreBeanEx(){
return new AutreBeanEx();
}
}
ApplicationContext
ApplicationContext
pour qu'ils puissent être gérés par le conteneur.
package com.myproject;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext ctx = null;
//Configuration Java
ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//OU Configuration XML
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BeanExemple be = ctx.getBean(BeanExemple.class);
//be peut être utilisé dans ce main et les beans déclarés
//dans la configuration ont été intégrés dans le conteneur
}
}
//Annotation des beans à gérer par Spring avec @Component
//(seulement pour les singletons)
@Component
public class BeanExemple{
//Injection d'un bean avec @Autowired, l'injection se fait par
//type. Attention, si deux beans existent du même type,
//cette injection échouera !
@Autowired
private AutreBeanEx autreBean;
}
@Component
public class AutreBeanEx{
}
@Configuration
//L'annotation @ComponentScan permet de dire à Spring
//de chercher les beans dans toutes les classes de ce
//ce package.
@ComponentScan(basePackages = "com.myproject")
public class SpringConfig {
}
@Service
, @Controller
, @Repository
sont des spécialisations de
@Component
et permettent à ces beans particuliers d'être traités de manière optimale par Spring.
@Component
public class BeanExemple{
//Utiliser @Resource pour injecter explicitement par nom
@Resource(name = "autreBean1")
private AutreBeanEx autreBean1;
//Ici @Autowired fonctionne car @Primary a été mis
//sur le bean principal
@Autowired
private AutreBeanEx autreBean;
}
public class AutreBeanEx{
private String nom;
private AutreBeanEx(String n){nom = n;}
}
@Configuration
@ComponentScan(basePackages = "com.myproject")
public class SpringConfig {
@Bean(name = "autreBean1")
public AutreBeanEx autreBean1(){
return new AutreBeanEx("Hello");
}
@Bean(name = "autreBean")
//L'annotation @Primary permet de lever le doute
//lorsque plusieurs beans ont le même type.
@Primary
public AutreBeanEx autreBean(){
return new AutreBeanEx("World");
}
}
@Qualifier
peut être utilisé pour mettre des sortes de tags sur les beans afin de rendre plus souple l'injection automatique.
public class AutreBeanEx{
@PostConstruct //remplace init-method
public void init() {
// Instructions à exécuter après l'instanciation
}
@PreDestroy //remplace destroy-method
public void cleanup() {
// Instructions à exécuter avant la destruction
}
}
@Configuration
public class SpringConfig {
@Bean
@Scope("prototype")
public AutreBeanEx autreBean(){
return new AutreBeanEx();
}
}
.properties
.properties
comme nous l'avons vu dans des cours précédents. Vous pouvez récupérer ces propriétés qui sont stockées dans le conteneur Spring, soit comme paramètres de construction de beans, ou directement dans votre application.
#app.properties
myproject.name=Worldz
myproject.message=Hello
<beans>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:app.properties"/>
</bean>
<!-- OU -->
<context:property-placeholder location="classpath:app.properties"/>
<bean id="beanExemple" class="com.myproject.BeanExemple">
<property name="nom" value="${myproject.nom}"/>
<property name="message" value="${myproject.message}"/>
</bean>
</beans>
@Configuration
@PropertySource("classpath:app.properties")
public class SpringConfig {
@Value("${myproject.nom}")
private String nom;
@Value("${myproject.message}")
private String message;
@Bean(name = "beanExemple")
public BeanExemple beanExemple(){
return new BeanExemple(message, nom);
}
}
application.properties
(comme par exemple l'URL de connexion à la BDD), Spring récupère automatiquement ces valeurs pour paramétrer la création des beans nécessaires (à la connexion à la base de données par exemple).
.properties
ou YAML
).maven
et Gradle
. Exemple avec Maven :
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1.RELEASE</version>
<relativePath/>
</parent>
<groupId>com.myproject.java</groupId>
<artifactId>javaproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>javaproject</name>
<description>TP sur Spring</description>
<dependencies>
<!-- Ajout de dépendances starter en fonction des besoins-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- Dépendance à MySQL => désactivation de la BDD intégrée et configuration optimisée pour MySQL-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Autres dépendances déclarées de manière classique-->
</dependencies>
</project>
package com.example.myapplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//équivalent à @Configuration @EnableAutoConfiguration @ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@Configuration
@EnableBatchProcessing
public class HelloWorldBatch {
@Autowired
public JobBuilderFactory jobBuilderFactory;
@Bean
public Job helloWorldBatch(Step hwStep, Step importFileStep) {
return jobBuilderFactory.get("helloWorldBatch")
.incrementer(new RunIdIncrementer())
.flow(hwStep)
.next(importFileStep)
.end()
.build();
}
// Définition des steps...
}
@Configuration
@EnableBatchProcessing
public class HelloWorldBatch {
//Définitions précédentes...
@Bean
public Step hwStep() {
return stepBuilderFactory.get("hwStep").tasklet(myTasklet()).build();
}
@Bean
public Step importFileStep(ItemWriter<MyObject> writer) {
return stepBuilderFactory.get("importFile")
.<MyObject, MyObject> chunk(CHUNK_SIZE)
.reader(reader(null))
.processor(processor())
.writer(writer)
.build();
}
// Définition des reader/processor/writer...
}
spring.batch.initialize-schema
dans le fichier application.properties
ou directement lors du lancement de l'application.
//Interface ItemReader fournie par Spring Batch
public interface ItemReader<T> {
T read() throws Exception, UnexpectedInputException, ParseException;
}
//DTO Véhicule
public class Vehicule {
private String immat;
private String marque;
private String modele;
private Integer km;
//Getters/Setters
}
Fichier test.csv immat;marque;modele;kilometrage
AB-123-CD;Peugeot;208;7462
EL-645-AA;Renault;Zoé;4621
DE-679-KK;Citroën;C4;39651
@Bean
public FlatFileItemReader<Vehicule> myCSVReader() {
return new FlatFileItemReaderBuilder<Vehicule>()
.name("myCSVReader").linesToSkip(1)
.resource(new ClassPathResource("test.csv"))
.delimited().delimiter(";")
.names("immat", "marque", "modele", "km")
.fieldSetMapper(new BeanWrapperFieldSetMapper<>() {{
setTargetType(Vehicule.class);
}})
.build();
}
@Entity
public class Vehicule {
@Column(name = "immatriculation")
private String immat;
private String marque;
private String modele;
@Column(name = "kilometrage")
private Integer km;
//Getters/Setters
}
@Autowired
public EntityManagerFactory entityManagerFactory;
@Bean
public JpaPagingItemReader<Vehicule> myJpaReader() {
return new JpaPagingItemReaderBuilder<Vehicule>()
.name("myJpaReader")
.entityManagerFactory(entityManagerFactory)
.pageSize(10)
.queryString("from Vehicule v where km = 0")
.build();
}
//Interface ItemProcessor de Spring Batch
public interface ItemProcessor<I, O> {
O process(I item) throws Exception;
}
class VehiculeProcessor implements ItemProcessor<Vehicule, Vehicule> {
@Override
Vehicule process(Vehicule item) throws Exception {
if(item.getModele() == null){
//Retourner null permet de filtrer le résultat et de ne pas l'envoyer à l'ItemWriter
return null;
}
item.setMarque(item.getMarque() != null ? item.getMarque().toUpperCase() : "N/A";
item.setDenomination(item.getMarque() + " " + item.getModele());
return item;
}
}
process
(comme des requêtes BDD par exemple).
Préférer dans ce cas des opérations ensemblistes (cf. Tasklet).
//Interface ItemWriter de Spring Batch
public interface ItemWriter<T> {
void write(List<? extends T> items)
throws Exception;
}
@Autowired
public EntityManagerFactory entityManagerFactory;
@Bean
public JpaItemWriter<Vehicule> writerJPA(){
return new JpaItemWriterBuilder<Vehicule>().entityManagerFactory(entityManagerFactory).build();
}
@Bean
public JdbcBatchItemWriter<Vehicule> writerJDBC(DataSource dataSource){
return new JdbcBatchItemWriterBuilder<Vehicule>()
.itemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>())
.sql("INSERT INTO vehicule (immatriculation, marque, modele, kilometrage) "
+ "VALUES (:immat, :marque, :modele, :km)")
.dataSource(dataSource)
.build();
}
@Bean
public ItemWriter<Vehicule> fileWriter() {
BeanWrapperFieldExtractor<Vehicule> bwfe = new BeanWrapperFieldExtractor<Vehicule>();
bwfe.setNames(new String[] {"immat", "marque", "modele", "km"});
DelimitedLineAggregator<Vehicule> agg = new DelimitedLineAggregator<>();
agg.setFieldExtractor(bwfe);
return new FlatFileItemWriterBuilder<>()
.name("csvWriter")
.lineAggregator(agg)
.resource(new FileSystemResource("test.csv"))
.delimited().delimiter(";")
.build();
}
//Interface Tasklet fournie par Spring Batch
public interface Tasklet {
RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throws Exception;
}
Une Tasklet contient une seule méthode execute
qui sera répétée tant que la méthode
ne renvoie pas RepeatStatus.FINISHED
. Il est en effet possible de renvoyer RepeatStatus.CONTINUABLE
afin d'exécuter de nouveau la Tasklet.
public class PurgeVehiculeTasklet implements Tasklet {
@Autowired
private VehiculeRepository vehiculeRepository;
@Override
public RepeatStatus execute(StepContribution contribution,
ChunkContext chunkContext) throws Exception {
vehiculeRepository.deleteByImmatNull();
return RepeatStatus.FINISHED;
}
}
@Bean
public Tasklet stepVehicule(){
return new PurgeVehiculeTasklet();
}
@Bean
public Step stepVehicule(Tasklet stepVehicule) {
return stepBuilderFactory.get("stepVehicule").tasklet(stepVehicule).build();
}
StepExecutionListener
ChunkListener
ItemReadListener
ItemProcessListener
ItemWriteListener
SkipListener
StepExecutionListener
beforeStep
et afterStep
permettant l'exécution d'instructions avant le lancement de la Step, ou une fois cette dernière terminée.
//Interface fournie par Spring Batch
public interface StepExecutionListener extends StepListener {
void beforeStep(StepExecution stepExecution);
ExitStatus afterStep(StepExecution stepExecution);
}
public class VehiculeStepListener implements StepExecutionListener {
@Override
public void beforeStep(StepExecution sExec) throws Exception {
//Avant l'exécution de la Step
}
@Override
public ExitStatus afterStep(StepExecution sExec) throws Exception {
//Une fois la Step exécutée
}
}
Il est aussi possible d'utiliser le annotations @BeforeStep
et @AfterStep
sur des méthodes implémentées dans un Reader/Processor/Writer ou une Tasklet
public class VehiculeTasklet implements Tasklet {
@BeforeStep
public void beforeStep(StepExecution sExec) throws Exception {
//Avant l'exécution de la Step
}
@AfterStep
public ExitStatus afterStep(StepExecution sExec) throws Exception {
//Une fois la Step exécutée
}
}
ExitStatus
dans la
méthode afterStep
afin de modifier le flot d'exécution de la Step.
ChunkListener
beforeChunk
et afterChunk
permettant l'exécution d'instructions avant le traitement d'un Chunk, ou une fois ce dernier terminé (possible donc au niveau d'un ItemReader
ou d'un ItemWriter).
//Interface fournie par Spring Batch
public interface ChunkListener extends StepListener {
void beforeChunk();
void afterChunk();
}
public class VehiculeChunkListener implements ChunkListener {
@Override
public void beforeChunk() throws Exception {
//Avant l'exécution du Chunk
}
@Override
public void afterChunk() throws Exception {
//Une fois le chunk terminé
}
}
Il est aussi possible d'utiliser le annotations @BeforeChunk
et @AfterChunk
sur des méthodes implémentées dans un Reader/Writer.
public class VehiculeItemReader implements ItemReader<Vehicule> {
Vehicule read() {...}
@BeforeChunk
public void beforeChunk() throws Exception {
//Avant l'exécution du chunk
}
@AfterChunk
public void afterChunk() throws Exception {
//Une fois le chunk terminé
}
}
afterChunk
n'est pas appelée lorsqu'une erreur est levée pendant le traitement du Chunk.
Item*Listener
before*
, after*
et on*Error
avec *
= Read
, Write
, ou Process
.
//Interfaces fournies par Spring Batch
public interface ItemReadListener<T> extends StepListener {
void beforeRead();
void afterRead(T item);
void onReadError(Exception ex);
}
public interface ItemProcessListener<T, S> extends StepListener {
void beforeProcess(T item);
void afterProcess(T item, S result);
void onProcessError(T item, Exception ex);
}
public interface ItemWriteListener<S> extends StepListener {
void beforeWrite(List<? extends S> items);
void afterWrite(List<? extends S> items);
void onWriteError(Exception ex, List<? extends S> items);
}
Il est aussi possible d'utiliser le annotations @Before*
, @After*
, @On*Error
sur des méthodes implémentées dans un Reader/Processor/Writer.
public class VIP implements ItemProcessor<Vehicule, Vehicule> {
Vehicule process(Vehicule v) {...}
@BeforeProcess
public void beforeProcess(Vehicule item) throws Exception {
//Avant le traitement de l'élément
}
@AfterProcess
public void afterProcess(Vehicule v, Vehicule r) throws Exception {
//Une fois l'élément traité
}
@OnProcessError
public void onProcessError(Exception ex) throws Exception {
//Lors d'une exception levée lors du traitement
}
}
ItemWriteListener
et ItemReadListener
SkipListener
onSkipInRead
, onSkipInProcess
, onSkipInWrite
permettant de traiter les éléments ignorés dans le batch
//Interfaces fournies par Spring Batch
public interface SkipListener<T, S> extends StepListener {
void onSkipInRead(Throwable ex);
void onSkipInProcess(T item, Throwable ex);
void onSkipInWrite(S item, Throwable ex);
}
public class VSL implements SkipListener<Vehicule, Vehicule> {
@Override
public void onSkipInRead(Throwable ex) {
}
@Override
public void onSkipInProcess(Vehicule v, Throwable ex) {
}
@Override
public void onSkipInWrite(Vehicule v, Throwable ex) {
}
}
Il est aussi possible d'utiliser le annotations @OnSkipInRead
, @OnSkipInProcess
, @OnSkipInWrite
sur des méthodes implémentées dans un Reader/Processor/Writer.
public class VIP implements ItemProcessor<Vehicule, Vehicule> {
Vehicule process(Vehicule v) {...}
@OnSkipInProcess
public void onSkipInProcess(Vehicule v, Throwable ex) {
//Si une exception configurée pour ignorer l'élément
//est levée, cette méthode est exécutée avec en paramètre
//l'élément qui a posé problème et l'exception qui a été levée.
}
}
@Autowired
public JobBuilderFactory jobBuilderFactory;
//Avec stepA, stepB et stepC trois Steps déclarées en @Bean
@Bean
public Job myJob(Step stepA, Step stepB, Step stepC) {
return jobBuilderFactory.get("myJob")
.incrementer(new RunIdIncrementer())
.flow(stepA).next(stepB).next(stepC)
.end().build();
}
stepB
ne sera exécutée que si la Step stepA
s'exécute correctement (c'est-à-dire que l'ExitStatus
est COMPLETED
). Idem pour la Step stepC
avec la Step stepB
ExitStatus
d'une Step
, on peut décider d'exécuter telle ou telle Step.
@Autowired
public JobBuilderFactory jobBuilderFactory;
//Avec stepA, stepB et stepC trois steps déclarées en @Bean
@Bean
public Job myJob(Step stepA, Step stepB, Step stepC) {
return jobBuilderFactory.get("myJob")
.incrementer(new RunIdIncrementer())
.start(stepA).on("COMPLETED").to(stepB)
.from(stepA).on("WARNING").to(stepC)
.end().build();
}
stepB
ne sera exécutée que si la Step stepA
s'exécute correctement (c'est-à-dire que l'ExitStatus
est COMPLETED
). Si l'ExitStatus
de la Step stepA
est WARNING
, c'est la Step stepC
qui sera exécutée. C'est grâce aux StepListeners qu'il est
possible de modifier l'ExitStatus
d'un Step.
@Autowired
public JobBuilderFactory jobBuilderFactory;
//Avec stepA, stepB, stepC et stepD quatre steps déclarées en @Bean
@Bean
public Job importUserJob(Step stepImportCSV, Step stepGetMissingCoordinates) {
SimpleFlow flow1 = new FlowBuilder<SimpleFlow>("flow1").start(stepB).build();
SimpleFlow flow2 = new FlowBuilder<SimpleFlow>("flow2").start(stepC).build();
return jobBuilderFactory.get("parallel_job").incrementer(new RunIdIncrementer())
.flow(stepA)
.split(new SimpleAsyncTaskExecutor("parallel_flow")).add(flow1,flow2)
.next(stepD).end()
.build();
}
stepB
et stepC
sont exécutées en parallèle. Il est
aussi possible de paralléliser les traitements à l'intérieur d'une même Step (on parle de Steps Multi-threaded).
ExitStatus
des Steps afin
de modifier le flot d'exécution de manière dynamique. Comment le Batch se comporte-t-il en cas d'erreur ? Par défaut toute exception non
gérée dans une Step entraîne un arrêt de la Step qui passe en statut FAILED
tout comme le Batch.
ItemReader
, l'ItemProcessor
ou l'ItemWriter
,
le Chunk qui était en cours de traitement est rollbacké (nombre d'enregistrements défini au niveau de la configuration de la Step).FAILED
tout comme le Batch.Tasklet
, le traitement est interrompu de manière classique@Bean
public Step step1() {
return this.stepBuilderFactory.get("step1")
.<Vehicule, Vehicule>chunk(2)
.reader(itemReader())
.writer(itemWriter())
.faultTolerant()
.retryLimit(3)
.retry(SQLException.class)
.build();
}
@Bean
public Step step1() {
return this.stepBuilderFactory.get("step1")
.<Vehicule, Vehicule>chunk(10)
.reader(flatFileItemReader())
.writer(itemWriter())
.faultTolerant()
.skipLimit(10)
.skip(FlatFileParseException.class)
.build();
}