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.

Introduction

Spring logo

Historique

  • 0.9 - Première version !
  • 1.0 - Optimisations diverses
  • 2.0 - Simplification des configurations XML, support de Groovy, de JPA
  • 2.5 - Configuration par annotations, support de Java 6, auto-détection des composants
  • 3.0 - Spring expression language, support des bases de données intégrées, validation des model, support de REST
  • 3.1 - Profils de beans, abstraction de cache
  • 4.0 - Support de Java 8, apparition de Spring Boot
  • 4.2 - Améliorations du Code Container et de la couche d'accès aux données
  • 4.3 - Améliorations du cache et des modules webs
  • 5.0 - Spring WebFlux, support de Kotlin

Les différents projets

L'inversion de contrôle (IoC)

L'injection de dépendances (DI)

  • Réduction au maximum des dépendances entre les composants logiciels
  • Maintenance et évolutions plus maitrisées
  • Utilisation possible d'implémentations différentes d'un composant sans tout modifier.
  • Plus de gestion manuelle du cycle de vie des composants.
  • Patterns de développement comme le singleton utilisables très facilement.
  • Code plus facile à tester (mocks), à lire et à réutiliser.
  • Ajout d'une dépendance à un framework potentiellement lourd ?
  • Perte de contrôle ?

Spring Core

Beans
Core
Context
SpEL
Core et Beans sont les modules qui implémentent l'inversion de contrôle et l'injection de dépendences.
Context est une sorte de registre permettant de construire, stocker et injecter les différents composants de votre application.
SpEL ou Spring Expression Language permet de consulter et manipuler le graph d'objets à l'exécution.

Les Beans

  • Un bean doit être décrit dans une configuration de bean à fournir au conteneur Spring.
  • Lorsqu'il y a des dépendances entre des beans, Spring lie ces derniers entre eux par injection.
  • Les déclarations de beans permettent aux beans d'être instantiés, stockés, gérés et détruits par le conteneur Spring via l'ApplicationContext.

Les configurations de Beans

  • Eléments à définir
  • La classe
  • Le nom
  • Le scope
  • Les arguments du constructeur et les autres propriétés
  • L'initialisation/destruction
//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;
  }
}

L'injection de beans

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

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

Simplifier et automatiser

//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 {

}

Cas particuliers (1/2)

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

Cas particuliers (2/2)

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

Les fichiers .properties

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

Spring Boot

  • Démarrer très rapidement le développement de votre application (notamment les applications web) sans être ralenti par des éléments de configuration
  • Profiter de librairies tierces directement disponibles mais facilement désactivables ou remplaçables par l'ajout explicite de dépendances.
  • Externaliser la configuration dans des fichiers de configurations (.properties ou YAML).
  • Avoir des fonctionnalités supplémentaires comme des serveurs intégrés, du monitoring, des vérifications au niveau de la santé et de la sécurité de votre application...

La gestion des dépendances

<?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>

L'application

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

}

Spring Batch SB

Avantages/Inconvénients

  • Fonctionnement en chunk
  • Gestion des transactions
  • Possibilités d'arrêt, de redémarrage
  • Possibilités d'ignorer des enregistrements ou de réessayer en cas d'erreur
  • Flexibilité
  • Testabilité
  • Monitoring
  • Vocabulaire des batchs à maîtriser
  • Nécessité de connaître l'ensemble des possibilités du framework pour l'utiliser efficacement
  • Complexité relative

Principaux concepts

Job

  • Le Job est lancé par le JobLauncher
  • Les Jobs sont stockés dans le JobRepository
  • Lorsqu'un Job est lancé, une nouvelle JobInstance est créée pour chaque ensemble unique de JobParameters
  • Chaque JobInstance possède au moins une JobExecution correspondante, et potentiellement plusieurs en cas de redémarrage du Job
  • Chaque JobExecution possède un statut, éventuellement un code de sortie et un message de sortie
@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...
}

Step

  • Une Step peut représenter l'exécution d'une Tasklet
  • Une Step peut aussi être composée d'un ItemReader, éventuellement d'un ItemProcessor et d'un ItemWriter
  • Lorsqu'une Step est lancée, une nouvelle StepExecution est créée
  • Chaque StepExecution possède un statut, éventuellement un code de sortie et un message de sortie
@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...
}

Données d'exécution

Step orientée Chunk

Reader (1/2)

//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();
}

Reader (2/2)

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

Processor

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

Writer (1/2)

//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();
  }

Writer (2/2)

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

Tasklet

//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();
}

Listener

  • StepExecutionListener
  • ChunkListener
  • ItemReadListener
  • ItemProcessListener
  • ItemWriteListener
  • SkipListener
  • Intervenir avant ou après l'exécution d'une Step
  • Intervenir avant ou après chaque Chunk
  • Intervenir avant ou après une lecture, ou lors d'une erreur de lecture
  • Intervenir avant ou après un traitement, ou lors d'une erreur de traitement
  • Intervenir avant ou après une écriture, ou lors d'une erreur d'écriture
  • Intervenir lorsqu'un élément a été ignoré

StepExecutionListener

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

ChunkListener

//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é
  }
}

Item*Listener

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

SkipListener

//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.
  }
}

Flot d'exécution

  • Flot d'exécution séquentiel
  • Flot d'exécution conditionnel
  • Flot d'exécution parallèle

Flot d'exécution séquentiel

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

Flot d'exécution conditionnel

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

Flot d'exécution parallèle

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

Gestion des erreurs

  • Step Chunk
  • Lorsqu'une exception est levée dans l'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).
  • Les Chunks précédemment traités restent inchangés (les éléments enregistrés en BDD ou écrits dans un fichier sont conservés).
  • La Step est interrompue, passe en statut FAILED tout comme le Batch.
  • Si le Batch est par la suite relancé, la Step reprendra au début du Chunk précédemment en erreur.
  • Step Tasklet
  • Lorsqu'une exception est levée dans une Tasklet, le traitement est interrompu de manière classique
  • Aucun rollback n'est effectué (sauf mécanismes internes au niveau de la BDD)
  • Si le Batch est par la suite relancé, la Step reprendra au début de la Tasklet, ce qui peut impliquer que des éléments déjà traités le soient de nouveau.

Réessayer le traitement d'un élément en erreur

@Bean
public Step step1() {
  return this.stepBuilderFactory.get("step1")
    .<Vehicule, Vehicule>chunk(2)
    .reader(itemReader())
    .writer(itemWriter())
    .faultTolerant()
    .retryLimit(3)
    .retry(SQLException.class)
    .build();
}

Ignorer des éléments problématiques

@Bean
public Step step1() {
  return this.stepBuilderFactory.get("step1")
    .<Vehicule, Vehicule>chunk(10)
    .reader(flatFileItemReader())
    .writer(itemWriter())
    .faultTolerant()
    .skipLimit(10)
    .skip(FlatFileParseException.class)
    .build();
}