Académique Documents
Professionnel Documents
Culture Documents
Il y a deux types d’exception les exception checked, il doivent être déclaré dans la signature
de la méthode après le mot clé throws. et les exception non checked, ce type d’exception
peuvent être levé même sans déclaration préalable dans la signature de la méthode.
InputStream est utilisé pour la lecture d’un flux sous forme binaire et Reader est utilisé pour
la lecture d’un flux sous forme text.
Un Stream est une séquence de données sont produites de manière conceptuelle une à la
fois à partir d’un source de données. Sur un Stream on peut appliquer plusieurs opérations
qui peuvent être enchaînées pour former un pipeline complexe. On peut exécuter un stream
en parallèle.
La motivation d’utilisation des stream est qu'avec les stream on programme avec un niveau
d'abstraction plus élevé. Un autre avantage est que java 8 on peut exécuter d’une façon
transparente un pipeline d’opération sur plusieurs coeur CPU.
Pour trier une liste on peut utiliser la méthode sort qui peut être paramétrée en utilisant un
objet Comparator.
Une interface fonctionnelle c’est une interface qui contient une seul méthode abstract.
Une expression lambda peut être compris comme une représentation concise d’une fonction
anonyme qui peut être transmise. Concise parce qu'on n'a pas besoin d’écrire beaucoup de
code comme pour les classes anonymes. C’est une fonction parce qu'elle n’est pas lié à une
classe particulière. Mais il est comme une méthode, elle a une liste des paramètres, un
corps, un type de retour et une liste d’exceptions. Elle est anonyme parce qu’elle n’a pas un
nom explicite. Transmise parce que une expression lambda peut être passé en argument à
une méthode ou stocké dans un variable.
Le Syntaxe d’une expression lambda peut être soit (paramètres) -> expression ou
(paramétres) -> { expressions; }
Une expression lambad peut être soit assignée à une variable ou transmise à une méthode
Une interface fonctionnel est une interface qui spécifie une seul méthode abstract.
C’est une annotation est utilisé pour indiquer qu’une interface est destinée à être une
interface fonctionnelle.
C’est quoi l’interface fonctionnel consumer ?
L’interface consumer définit une méthode abstract nommé accept qui prend un objet de type
générique et ne renvoie aucun résultat.
L’interface Function définit une méthode abstract nommé apply qui prend un objet de type
générique de type T et renvoie un autre Objet de type générique de type T.
Quel est la condition pour dire qu’une expression lambda est valide ?
on dit que une expression lambda est valide lorsque il a la même signature que la méthode
abstract de l’interface fonctionnel.
Les opération boxing et unboxing a un coût de performance. Les valeur en sortie d’un type
primitif sont essentiellement des wrappers autour de types primitifs et sont stocké dans la
Head. Par conséquent, ces valeurs utilisent plus de mémoire et nécessitent des recherches
de mémoire supplémentaires pour extraire les valeurs primitifs de départ.
Comment on peut éviter les opérations autoboxing dans les expressions lambdas ?
Pour éviter les opérations autoboxing dans les expressions lambdas, java 8 propose des
versions spécifiques des interfaces fonctionnelles pour les types primitifs.
Vue que les interfaces définis par java 8 ne lance pas d’exception, on a deux options.
Premièrement, déclaré notre propre interface fonctionnel pour lancer une exception. La
deuxième option est d'envelopper le lambda avec un bloc try/catch.
Le type d’une expression lambda est déduit à partir du context dans lequel elle est utilisé. Ce
type attendu est appelé le type cible. Il peut être soit le type d’un paramètre d’une méthode
auquel il est passé ou une variable à laquelle est affecté. Un lambda peut être associé à
plusieurs interfaces fonctionnelles si elles sont une signature de méthode abstract
compatible.
Les expressions lambda sont autorisé à capturer des variables d’instance et des variables
statiques sans restriction. Mais les variables local doivent être explicitement déclaré final ou
effectivement final.
Les variables locals ont ces restrictions pour plusieurs raisons. Premièrement, parce que les
variables local vivent sur le stack et les threads ne partagent pas leur stack. Deuxièmement,
cette méthode décourage également les modèles de programmation impératifs typique.
Une closure est une instance d’une fonction qui peut référencer des variables non-local de
cette fonction sans restriction. Par exemple un closure peut être passé comme argument à
une fonction. il peut également accéder et modifier des variables définies en dehors de son
champs d’application.
Une expression lambdas peut être passé en argument d’une méthode et peut accéder à des
variables en dehors de son portée. Mais, il ne peut pas le contenu des variables local d’une
méthodes dans laquelle est défini. Alors, on peut dire que les lambdas appliquer le closure
sur les valeurs plutôt que des variables.
Une référence de méthode est une technique qui permet de réutiliser les définition des
méthodes existantes et les transmettre comme des expressions lambdas. Il existe trois
types de référence de méthodes. Premièrement, le référence à une méthode statique. Dans
ce ca la signature de la méthode statique doit être la même que la signature de la
l’expression lambdas. deuxièmement, une référence d’une méthode à une méthode
d’instance d’un type arbitraire. l’idée est que on fait référence à une méthode pour un objet
fourni comme l’un des paramètres de l’expression lambdas. Troisièmement, une référence
d’une méthode à une méthode d’instance d’un objet existant.
Comment on peut créer un référence de constructeur ?
On peut créer une référence à un constructeur en utilisant son nom et le mot clé new. Il
fonctionne de la même manière que le référence de méthode static.
Quels sont les interfaces fonctionnelles qu’on peut utiliser avec les constructeurs de
zéro à deux paramètres ?
Avec les références de constructure on peut utiliser les interfaces suivantes : Supplier,
Function, BiFunction
L’interface Predicate définit troi méthode par défaut sotn les méthodes and, or et negat.
L’interface Function définit deux méthodes par défaut la première est la méthode andThen et
la méthode compose.
Les Stream c’est une API de java qui permet de manipuler des collections de données de
manière déclarative.ça veut dire que avec les stream on exprime une requête plutôt que de
coder une implémentation. En outre, les stream peuvent être traités en parallèle de manière
transparente
C’est un Stream ?
Un stream est une séquence d’éléments provenant d’une source qui prend en charge les
opération de traitement des données. Ces opérations peuvent être exécutées
séquentiellement ou en parallèle. Les stream sont consommés à partir d’une source
fournissant des données, telles que les collections, des matrices, des ressources d’E S.
Il y a de nombreux différences entre les collections et les stream. Premièrement, les stream
ne sont pas de structure de données qui stockent de données mais plus tôt permet de
véhiculer des élément provenant d’une source. Deuxièmement, une opération sur stream
produit une résultat, mais il ne modifie pas son source. Troisièmement, les collections ont un
taille limite, alors une les stream n’ayant pas un taille limite. Quatrièmement, on ne peut pas
consommer un flux que une seul fois. Dernièrement, les collection utilisent un itérateur
externe, alors que les stream utilisent un itérateur interne qui permet de choisir de mettre en
place le parallélisme. En plus de ça les données des collections sont calculés au préalable
alors que les données des streams sont calculés à la demande.
Quelles sont les deux catégories des opérations sur les Streams ?
Les Stream définit deux types d’opérations. Les opérations intermédiaires qui permet de
transformer un flux à un autre flux et les opérations terminales qui permet d’exécuter le
pipeline et transformer un flux à une structure de données.
La méthode filter prend comme argument un predicate et retourne un flux incluant tous les
éléments qui correspondent au predicate.
La méthode distinct permet de renvoyer un flux avec des éléments uniques selon la mise en
oeuvre des méthodes hashcode et equals.
La méthode limite permet de renvoyer un autre flux qui ne dépasse pas un taille donnée. La
taille demandé est passé en argument à la méthode limit().
La méthode map prend comme paramètre une fonction. Cette fonction est appliquée à
chaque éléments du flux et qui le transforme en un nouvelle élément.
La méthode flatMap permet de remplacer chaque valeur d’un flux par un autre flux, puis de
concaténer tous les flux générés en un seul flux.
La méthode flatMap permet de remplacer la valeur de chaque flux par un autre flux, puis
concaténer tous les flux générés en un seul flux. Alors que Map permet de remplacer une
valeur d’un flux par une autre valeur.
La méthode allMatch permet de vérifier si tous les éléments dans la liste correspond au
predicate donné. Cette méthode renvoie un boolean.
On utilise la classe Optional pour éviter les bogues liés à la vérification de null. Elle propose
des méthodes pour vérifier explicitement la présence d’une valeur ou à traiter l’absence
d’une valeur. Comme la méthode isPresent(), get et orElse.
Si on ne soucie pas de l’élément trouvé, c’est mieux qu’on utilise la méthode findAny parce
qu'il est moins contraignant lors de l’utilisation des flux parallèles
La méthode reduce prend en paramètre deux arguments. Une valeur initiale, et un objet de
type BinaryOperator pour combiner deux valeur et retourne une nouvelle valeur. Avec un
reduce on peut produire la somme, le maximum, le nombres d’éléments d’un flux.
Est ce que les opérations sur les flux sont stateless ou stateful ?
Les opérateurs comme map() et filtre() prennent chaque élément du flux d’entrée et
produisent zéro ou un résultat dans dans le flux sortant. Ces opération sont sans état. Mais
les opération comme reduce, sum, max et count doivent avoir un état interne pour accumuler
le résultat. En revanche certains opérateurs telles que sort() et distinct() nécessitent de
connaître l'historique précédent pour faire leur travail. On dit que ces opérations sont statful.
Les méthodes les plus courant qui permet de convertir un flux à un flux spécialisé sont
mapToInt, mapToLong et mapToDouble
Il existe aussi une version spécialisé pour les flux primitifs : OptionnalInt, OptionnelLong et
OptionnelDouble.
Pour générer une plage numérique java 8 présente deux méthodes statiques disponibles sur
intStream et longStream. ces méthodes sont range rangeclosed. Les deux méthodes
prennent la valeur initial de la plage comme premier paramètre et la valeur final comme
deuxième paramètre. Mais range est exclusif et rangeCloser est inclusif.
On peut créer un Stream avec plusieur méthodes. Premièrement, à partir d’une collection on
utilisant la méthode stream. Deuxièmement, à partir des valeurs on utilisant la méthode
Stream.of(). Troisièmement, à partir d’un tableau on utilisant la méthode Arrays.stram().
Quatrièmement, en utilisant les méthodes statiques de la classe Files de l’API NIO comme la
méthode lines(). Finalement, on peut utiliser des méthode spécifique comment la méthode
iterate et generate. La méthode iterate prend en paramètre une valeur initial et un expression
lambda à appliquer (unaryoperator<T>) successivement sur chaque nouvelle valeur
produite. En plus de ça on peut créer un flux vide en utilisant la méthode empty()
La méthode collect à plusieurs rôle. Premièrement, il permet de grouper une liste par une
valeur. Deuxièmement, partitionner une liste en deux groupes. Troisièmement, créer des
groupement multiniveau.
La méthode collect permet de déclencher une opération de réduction sur les élément d’un
flux.
Comment il fonctionne les collectors ?
Quels sont les différentes méthodes de réduction proposé par la classe Collectors ?
La méthode reducing est une généralisation des autres méthodes de réduction. Il prend trois
arguments :
Le premier est la valeur de départ de l’opération de réduction
Le deuxième valeur est une valeur de même type que le premier paramètre
Le troisième est est une expression lamda de type BinaryOperator qui agréger deux
valeurs en une seul valeur de même type.
Cette méthode est appelé une fonction de classification car elle est utilisé pour classer les
éléments du flux dans différentes groupes.
Pour obtenir un groupement à plusieurs niveau en utilisant un collecteur, on doit créer une
méthode Collectors.groupingBy qui accepte un deuxième argument, en plus de la fonction
de classification, qui peut être de n’importe quel type de collecteur. L’objectif de ce collector
est d'effectuer une opération de réduction supplémentaire sur les élément de flux classés
dans le même groupe
C’est une méthode qui prend deux arguments le collecteur à adapter et une fonction de
transformation et renvoie une autre collecteur.
C’est quoi le partitionnement avec java 8 ?
Quels sont les méthodes qui sont défini par l’interface Collector ?
Les méthodes définis par l’interface Collector sont Supplier<A> supplier(), BiConsumer<A,T>
accumulator(), Function<A,R> finisher(), BinaryOperator combiner(), Set<Characteristics>
characteristics(). Chacune des quatres premières méthodes retourne une fonction qui sera
invoquée par la méthode collect, alors que la cinquième méthode fournit un ensemble de
caractéristiques qui seront utilisées par la méthode collect. Les trois première méthodes sont
suffisantes pour exécuter une réduction séquentielle du flux.
La méthode supplier renvoie une fonction de type Supplier qui permet de retourner un
accumulateur vide pendant le processus de collect.
La méthode accumulator renvoie une fonction de type BiConsummer qui effectue une
opération de réduction. Lors de la traversé de l’élément n du flux, cette fonction est appliqué
avec deux arguments : l'accumulateur étant étant le résultat de réduction et le n élément du
flux.
Cette méthode est appelé après avoir traversé le flux, elle renvoie une fonction de type
Function qui sera invoqué à la fin de processus d’accumulation afin de transformer l’objet
accumulateur en résultat final.
Cette méthode renvoie une fonction de type BinaryOperateur qui permet de définir comment
les accumulateurs résultant de la réduction des différentes sous-parties du flux sont
combinées lorsques les sous-parties sont traitées en parallèle.
Le flux original est divisé récursivement en sous-flux jusqu’à ce qu’une condition définissant
si un flux doit être divisible devient faux. A ce stade, tous les sous-flux peuvent être traités en
parallèle en utilisant un algorithme de réduction séquentielle. Enfin, tous les résultat partiels
sont combinés par paire en utilisant la fonction envoyer par la méthode combiner.
Pour créer une collecte personnalisée, il y a deux façon. La première est d’implémenté
l’interface Collector. La deuxième façon est d’utiliser la méthode collect surchargée
acceptant trois paramètres (supplier, accumultor, combiner).
Dans un premier temps le flux original est divisé récursivement en sous flux jusqu’à ce
qu'une condition définissant si un flux doit être divisé devient faux. A ce stade là tous les
sous-flux peut être traités en parallèle, chacun d’entre eux utilise l’algorithme de réduction
séquentielle. Ensuite, tous les résultat partiels sont combinées par paire en utilisant la
fonction renvoie par la méthode combiner.
Quels sont les facteurs qui impacte les performances des stream ?
Quand on parle des performances des streams il faut prendre en compte plusieurs facteurs.
Premièrement, le frais de boxed et unboxed. Deuxièmement, la difficulté de diviser un flux en
morceau indépendants à exécuter en parallèle. Par exemple l’opération iterate est difficile à
diver en bloc, car le résultat n’est pas disponible au début du processus de réduction. En
plus de ça le processus de parallélisation nous oblige à partitionner le flux d’une manière
récursive et combiner le résultat de chaque sous-flux à la fin en une seul résultat.
Pour améliorer les performances des streams, il y a plusieurs façon. Les plus importants
sont : Premièrement, l’utilisation des des méthodes plus spécifiques pour éviter le coût de
autoboxing et unboxing.
Un flux parallèle est un flux qui divise ces éléments en plusieurs segments, en traitant
chaque segment avec un Thread différent.
Les flux parallèles utilisent en interne ForkJoinPool, qui a par défaut autant de threads que
de processeurs. Il est renvoyé par la méthodes Runtime.getRuntime().AvailableProcessors().
Pour modifier ce pool on utilise la propriété système
java.util.concurent.ForkJoinPool.common.paralism.
Quand on utilise les flux parallèle, il faut éviter l’utilisation des algorithmes qui modifie une
état d’un objet partagé entre plusieurs Threads.
Pour utiliser les flux efficacement il faut suivre les conseils suivants :
En cas de doute mesurez. Transformer un flux séquentiel en flux parallèle et calculer
le temps d’exécution
méfiez-vous du boxing. Les opération de boxing et unboxing peuvent poser des
problèmes de performance
Certaine opérations se comportent naturellement moins bien sur le flux parallèle que
sur un flux séquentiel. Par exemple l’opérateur limit et findFirst. Par contre il y en a
d’autre qui se comportent mieux sur le flux parallèle comme par exemple findAny.
Il faut prendre en considération le produit entre le coût approximatif de traitement
d’un seul élément à travers le pipeline de flux et le nombres des éléments à traiter.
Il faut tenir en compte la qualité de la décomposition de la structure de données sous-
jacente. Par exemple une ArrayList peut être divisé plus efficacement plus qu’une
LinkedList.
Il faut déterminer calculer le coût de l’opération terminale
Le framework fork/join permet de diviser une tâche parallélisé en tâches plus petites, puis
combiner les résultats de chaque sous tâches pour produire le résultat final.C’est une
implémentation de l’interface ExecutorService, qui distribue ces sous-tâches aux threads
disponibles dans un pool de threads appelé ForkJoinPool.
Lorsque on transmet une tâche à un ForkJoinPool, cette tâche exécuté par un Thread du
pool qui à son tour appelle la méthode de calcule de la tâche. Cette méthode vérifier si la
tâche est suffisamment petite pour être exécutée de manière séquentielle. Sinon, il divise la
tâche en deux sous-tâche et les affecte à deux RecursiveTask qui sont programmées pour
être exécuté par ForkJoinPool. Par conséquence ce processus peut être répété de manière
récursive. A la fin le résultat de chaque sous-partie est calculé séquentiellement. Ensuite, le
résultat de la tâche est calculé en combinant les résultats partiels de chaque sous-tâche
Pour implémenter le framework fotk/join avec java 7, il faut créer une classe qui implémente
l’interface RecursiveTask<R>, où R est le type de résultat produit par la tâche parallélisée.
Cette interface a une seul méthode abstract appelé compute(). Cette méthode définit la
logique de division de la tâche en sous tâches et l’algorithme pour produire le résultat d’une
seul sous-tâche lorsqu'il n’est plus possible ou pratique de diviser davantage
Le framework fork/join utilise une technique appellée work stealing. Cela signifie que les
sous-tâches sont plus ou moins réparties sur tous les Thread de ForkJonPool. Chacun de
ces thread contient d’attente liée à des tâches qui lui sont assignées, et dès qu’il termine une
tâche il prend une autre et commence à l’exécuter. Une fois la file d’attente d’une Thread est
vide alors que les autres Threads sont encore occupés, au lieu qu’il devient inactive, le
Thread choisit aléatoirement une file d’attente et vole une tâche.
Cette interface permet de définir comment un flux parallèle peut diviser les données qu’il
traverse. Cette interface définit plusieurs méthodes (tryAdvance, trySplit, estimateSize,
characteristics). La méthode tryAdvance se comporte comme un itérateur, il retourne true si
il y a d’autre élément à parcourir. La méthode trySplit est utilisé pour partitionner certain de
ces éléments en second spliterator permettant de le traiter en parallèle. La méthode
estimateSize est utilisé pour donner une estimation de nombre des éléments restés à
parcourir. La méthode characteristics retourne un int qui encode les caractéristiques de
spliterator. Les client de Spliterator peuvent utilisés ces caractéristiques pour mieux contrôler
et optimiser son utilisation.
Dans un premier temps, la méthode trySplit est invoquée sur le premier Spliterator et génère
un seconde. Ensuite, la même méthode est appelé sur ces Spliterators pour générer d’autres
Spliterators jusque la méthode trySplit renvoie null pour signaler que la structure de
données qu’il traite n’est plus divisible.
Améliorer la visibilité du code c’est la facilité avec laquelle le code peut être compris par une
autre personne.
Pour améliorer la visibilité du code en utilisant java 8 on peut utiliser trois méthodes de
refactoring :
Refactorisation des classes anonyme en expression lambda parce que les classe
anonyme est extrêmement verbeuse.
Refactorisation des expression lambda aux références de méthode
Refactorisation d’un traitement de données d’un style impératif vers l’utilisation de
l’API Stream
Quels sont les difficultés de convertir des classes anonyme à des expressions lambda
?
Convertir des classes anonyme à des expressions lambda peut rencontrer des difficultés.
Premièrement, La signification de this et supper est différente pour les classes anonymes et
les expressions lambda. Dans une classe anonyme this fait référence à la classe lui-même,
mais à l’intérieur d’une expression lambda, elle fait référence à la classe qui l’entoure.
Deuxièmement, les classes anonymes sont autorisés au shadowing des variables de la
classe englobante. Les expressions lambda ne peuvent pas. Enfin la conversion des classes
anonymes à des expressions lambda peut rendre la code ambigu dans le contexte d’une
surcharge. Parce que le type de la classe anonyme est explicite à l’instanciation, mais le
type de l’expression lambda dépend de son context. On peut résoudre l'ambiguïté en
fournissant un type explicite
La conversion du code impératif en Stream API peut être difficile, car nous devons réfléchir
aux instructions de flux de contrôle, telle que break, continue et return, et déduire les
opérations de flux correct à utiliser.
Le pattern stratégie est une solution qui permet de représenter une famille d’algorithmes et
de choisir parmi eux au moment de l’exécution. Pour implémenter ce pattern, il faut créer
une interface qui représente un algorithme et une ou plusieurs implémentation concrètes de
cette interface pour représenter plusieurs algorithmes.
Si l’interface qui représente un algorithme est une interface fonctionnelle, au lieu de déclarer
des nouveaux implémentations pour implémenter les différentes stratégies, on peut passer
directement des expressions lambdas, qui sont plus concise.
Le pattern méthode de template est une solution d’avoir la flexibilité de modifier certaines
parties d’un algorithme. Ce pattern est util lorsque on est dans une situation telle que
j’adorais utiliser cette algorithme mais j’ai besoin de changer quelques lignes pour qu’il fasse
ce que je veux.
Pour implémenter le pattern observateur on doit créer deux interface. Le premier interface
est Observer qui regroupe les différentes observateurs. Il a une seul méthode appelée notify
qui sera appelée par le sujet lorsqu'il y a un message disponible. Le deuxième interface est
Subject qui permet d’enregistrer un nouvel observateur on utilisant la méthode
saveObservateur et informer ses observateurs avec la méthode notifyObservers.
Les expression lambda permet de replacer les différentes implémentation des observables.
Le pattern chaîne de responsabilité est une solution qui permet de créer une chaîne d’objet
de traitement. Un objet de traitement peut faire un travail et transmet le résultat à un autre
objet, qui effectue ensuite un travail et le passe à un autre objet de traitement, et ainsi de
suite.
Pour implémenter ce pattern on peut utiliser des objets de traitement en tant qu'instance de
l’interface Function. Pour composer ces objets de traitements, il suffit d’utiliser la méthode
andThen.
Le pattern Factory est une solution qui permet de créer des objets sans exposer la logique
d’instanciation au client.
Comment on peut tester une méthode qui prend une fonction comme argument ?
La chose que on peut faire si une méthode prend une expression lambdas comme argument
et de tester son comportement avec différentes lambdas.
On peut tester le comportement de cette fonction en la traitant comme une instance d’une
interface fonctionnelle
Les expressions lambda peuvent rendre les stack traces moins visibles parce que les
expressions lambda n’ont pas de nom. Pour remédier à ce problème, on peut utiliser des
références de méthodes à des méthodes déclarées dans la même classe que celle où elle
est utilisée. Deuxièmement, on peut également utiliser la méthode peek. Son but est
d’exécuter une action sur chaque élément d’un flux dès qu’il est consommé.
Pourquoi les concepteurs de java ont introduit les méthodes par défaut ?
Les concepteurs de java ont introduit les méthodes par défaut pour aider à faire évoluer les
API de manière compatible.
Avec java 8 qu’il est la différence entre une classe abstraite et une interface ?
Premièrement, une classe peut hériter uniquement d’une seul classe abstraite, alors qu'elle
peut implémenter plusieur interface. Deuxièmement, une classe abstraite peut imposer un
état à travers des variables d’instance, alors qu'une interface ne peut pas avoir des variables
d’instance.
Quelles sont les règles à suivre lorsqu’une classe hérite d’une méthode avec la même
signature à partir de plusieurs endroits ?
Optional vs Null ?
Il est important de noter que l’intention de la classe Optional n’est pas de remplacer chaque
référence null. Son bute est de nous aider à définir est ce que on attends une valeur
optionnel ou non.
Il y a trois façon de créer un objet Optional. Premièrement, on peut créer un objet vide
Optional.empty(). Deuxièmement, un optional à partir d’une valeur non null Optional.of(car)
Troisièmement, à partir de null Optional.ofNullable().
Pour transformer des valeurs à partir d’un optional on peut utiliser les méthodes map et
flatMap. L’opération map applique une fonction à chaque élément d’un flux et il envoie une
résultat de type Optional. flatMap a pour effet de remplacer chaque flux généré par le
contenu de ce flux.
La classe est non sérialisable. Donc, elle n’est pas destinée à être utilisée en tant que type
de champ. Parce que l’utilisation de la cette classe dans des applications qui fonction à l’aide
des outils ou frameworks nécessitant un modèle sérialisable peut poser des problèmes. Pour
résoudre ce problème on peut utiliser une méthode permettant d’accéder à touts valeur
éventuellement manquante en retournant plutôt une Optional.
Exception vs Optional ?
Si on sait qu’un Optional peut lever une exception on peut créer une méthode utilitaire qui
encapsule et renvoie un Optional.
Une instance de cette classe est un objet immuable représentant une date sans heure de la
journée. Pour créer une instance LocalDate on utilise la méthode statique of. On peut créer
aussi une instance de cette classe à partir d’un String en utilisant la méthode parse(). Il est
également possible d’obtenir la date actuelle on utilisant la méthode now. L’instance
LocalDate fournit de nombreux méthodes pour lire ces valeur, telle que l’année, le mois et le
jours. Il est aussi possible de lire les valeurs d’une instance LocalDate en utilisant la
méthode get prenant un objet ChronoField.
Une instance de cette classe est un objet immutable représentant une heure de la journée.
Pour créer une instance LocalTime on utilise la méthode statique of (avec et sans seconde).
Il également possible de créer une instance de cette classe à partir d’un String en utilisant la
méthode parse(). L’instance LocalTime fournit de nombreux méthodes pour lire ces valeurs.
Une instance de cette classe est un objet immutable représentant une date avec l’heure.
Pour créer une instance LocalDateTime on peut utiliser la méthode of ou un LocalDate en lui
passant l’heure ou un LocalTime en lui passant la date. On peut également extraire extraire
le composant LocalTime et LocalDate à partir d’un LocalDateTime en utilisant les méthodes
toLoclaTIme et toLocalDate.
c’est une classe qui permet de représenter le nombre de secondes écoulés depuis l’époque
Unix. il est utilisé par la machine. Pour créer une instance de cette classe, on utilise la
méthode statique ofEpochSecond ou la méthode now().
La classe period permet de donner la différence entre deux LocalDate en terme de jour, mois
et année.
Il y a plusieurs façon pour modifier une date. Premièrement, la méthode with qui permet de
modifier la valeur d’un champ d’un objet temporal. il déclenche une exception
Unsuported_TemporalTypeException si le champ demandé n’est pris en charge par le
paramètre Temporal spécifique. On peut au les deux méthodes minus ou plus pour ajouter
ou soustraire une durée donnée.
Pour modifier LocalDate on utilise la méthode withAttribute. Plus précisément, les méthodes
get et with permet respectivement de lire et modifier la valeur d’un champ d’un objet
Temporal. ils lancent une exception Unsuported-TemporalException si le champs demandé
n’est pas pris en charge par le paramètre Temporal spécifié. En plus de ça on peut
manipuler LocalDate on utilisant les méthodes plusAttribute et minsAttribute.
Comment on peut effectuer des opération plus avancées sur les dates ?
On peut effectuer des opérations complexe sur les dates telle que l’ajustement d’une date au
dimanche suivant, au jour ouvrable suivant ou au dernier jour du mois en passant un
temporalAdjuster à une version surchargée de la méthode with. L’API Date et Heure fournit
de nombreux Temporal Adjuster prédéfinis pour les cas d’usage les plus courants. Pour y
accéder, on utilise les méthodes statiques de la classe TemporalAdjusters. Pour effectuer
des opérations plus personnalisé on peut créer notre propre TemporalAjuster.
C’est une interface fonctionnel. Il déclare une seul méthode appelé Temporal
adjustInto(Temporal) qui permet de convertir un objet Temporal en un autre objet Temporal.
Pour transformer une date à une chaîne de caractère on utilise la méthode format qui prend
en paramètre un objet de type DateTimeFormatter.
Pour convertir une chaîne de caractère à une date on utilise la méthode parse qui prend en
paramètre une chaîne de caractère et un objet de type DateTimeFormatter.
Un fuseau horaire est une zone de la surface de la terre dans laquelle tous les localités ont
le même temps standard. Chaque fuseau d’horaire a un identifiant et un décalage par
rapport à Greenwinch. La classe ZondId représente l’identifiant de fuseau horaire et fournit
des règles de conversion entre LocalDateTIme et instance. Pour obtenir la liste des ZondId
disponibles on fait appel à la méthode getRoles sur ZonId.
Cette classe permet de représenter une date avec un fuseau d’horaire. On peut convertir un
objet de type ZonedDateTime à un objet de type LocalDateTime ou instance en utilisant les
méthode suivantes : toLocalDateTime et toInstance.
Quel est la condition pour exploiter le parallélisme sans soucis avec les stream ?
Java 8 permet d’exploiter le parallélisme sans aucun soucis à condition qu’on adopter le
comportement sans état. ça veut dire les fonction de pipeline de traitement de flux
n’interagissent pas en lisant ou en écrivant dans une variable écrite par un autre.
Une fonctionne pure est une fonction qui ne modifie pas ni l’état de sa classe englobante, ni
l’état des autres objets et renvoie ses résultats entiers en utilisant return.
Un effet secondaire est une action qui n’est pas totalement incluse dans la fonction elle-
même. Je peux citer par exemple :
La modification d’une structure de données
Lancer une exception
Effectuer des opération E/S telles que l’écriture dans un fichier
est un objet qui ne peut pas changer d’état après l’instanciation. Ces objets sont thread-safe
car ils ne peuvent pas être modifiés.
Parce que les méthodes ne peuvent pas interférer les uns avec les autres.
La différence entre une méthode et une fonction c’est une méthode a une effet secondaire
alors que les fonctions n’ont pas d’effet secondaire (pas de structure de mutation visible pour
les appels, pas d’E/S, pas d’exception).
Une fonction ne peut pas lancer une exception. En java au lieu qu’une fonction lève une
exception il peut utiliser un objet de type Optional. Cet objet soit il retourne une résultat en
cas de succès ou indique dans sa valeur qu’il n’a pas pu effectuer l’opération en retournant
un Optional.empty().
Une fonction est référentiellement transparent si elle renvoie la même résultat lorsqu’elle est
appelé avec les même argument.
Récursion VS itération ?
Est ce que cela veut dire que que la récursivité est null ?
Bien sûr non. Les langages fonctionnelle apportent une réponse à ce problème :
L’optimisation à ma queue. L’idée de base c’est de créer une fonction récursive où l’appel
récursif est la dernière chose qu’arrive dans la fonction. Par exemple au lieu de garder un
trace du résultat résultat intermédiaire de chaque appel récursif les résultats intermédiaires
sont passés comme argument à une autre fonction. La mauvaise nouvelle est que java ne
supporte pas ce type d’optimisation. Mais l'adaptation de la récursion de queue peut être une
meilleure pratique que la récursivité classique, car elle ouvre la voie à l’optimisation du
compilateur.
Dans la programmation fonctionnelle pure la mutation est non autorisé, alors que avec la
programmation fonctionnelle non la mutation est autorisé et ne doit pas être visible pour
l'appelant de la méthode.
Une fonction d’ordre supérieurs c’est une fonction qui prend une fonction ou plusieurs en
paramètre et renvoie une fonction comme résultat.
Lorsqu’on utilise les fonctions d’ordre supérieur, il est généralement conseillé que les
arguments passés à la fonctions n’ayant pas d’effet secondaires.
Le Curryint est une technique où une fonction f de deux arguments est vue comme une
fonction g d’un argument qui retourne une fonction aussi d’un argument. La valeur envoie
par la dernière fonction est la même que la valeur retourné par la fonction d’origine. Bien sûr
ça est généralisé
Un fonction ne peut pas modifier une structure de données globales ou passé en paramètre.
Parce que l’appel deux fois est susceptible de produire des réponses différentes violant la
transparence référentielle et la capacité de comprendre la fonction comme un simple
mappage des arguments aux résultat. Donc, si on a besoin d’une nouvelle structure de
données on doit créer une nouvelle et ne pas muter une structure de données existante
Une structure de données persistantes est une structure dont les valeurs sont persistent et
isolées des changements qui se produisent ailleurs.
Comment on peut appliquer la règle pas de mutation de structure existante ?
Pour aider le compilateur à appliquer la règle pas de mutation de structure existante on peut
déclarer les champs de classe étant final et de protéger les objets pointé pas les champs
final.
les flux sont paresseux parce que un flux se comporte comme une boîte noire qui peut
générer des valeurs sur demande. Lorsqu’on applique une séquence d’opérations à un flux,
celles-ci sont simplement sauvegardées. Ces opérations sont exécutées lorsqu’une
opération terminal est appelée.
Un flux parallèle est un flux qui divise ces éléments en plusieurs segments, en traitant
chaque segment avec un thread différent.
En appelant la méthode parallel() qui permet de diviser le flux en plusieurs parties. chaque
partie est traité par un thread. Ce mécanisme utilise en interne le pattern fork/join. Par défaut
il utilise autant de thread que de processeur.
La grande nouveauté de java 10 est l’utilisation du mot clé va qui permet de rendre la
déclaration des variables plus lisible. La deuxième nouveauté c’est que les API collection
sont enrichis avec des méthodes statique pour permettre la copies d’une collection
existantes.
La première nouveauté de java 11 c’est la possibilité d’utiliser le mot clé var avec les
expression
Premièrement une classe abstract peut avoir un constructeur, alors qu’une interface ne peut
pas avoir un constructeur. deuxièmement, tous les membres d’une interface sont public
static final. Alors que les classe abstract peut contenir des membres public protected private
static final private. Troisièmement, tous les méthodes d'interface sont public.
La méthode equals permet de comparer deux objets et de déterminer l’égalité entre ces
deux objets.
La méthode hashCode est utilisé par les collection pour optimiser leur données et les classer
selon la valeur de hashCode retourné.
Quels sont les contraints qu’il faut respecter lors du la redéfinition des méthodes
equals et hashCode ?
La redéfinition des méthodes equals et hashCode doit respecter les règles suivantes :
Symétrique : ça veut dire que pour deux références a et b si a.equals(b) alors
b.equals(a).
Réflexivité : ça veut dire que pour tout objet non null a.equls(a) doit toujours renvoyer
true.
Transitivité : ça veut dire que si a.equals(b) et b.equls(c) alors a.equls(c).
Consistance : ça veut dire que si deux objet sont égaux on invoquant la méthode
equals alors leurs méthode hashCode doit envoyer la même valeur pour les deux
objets.
Pour tout référence non null, a.equals(null) doit renvoyer false.
Le principal différence c’est que Head est utilisé pour les variables locals et les appels de
méthode. Alors que Stack est utilisé pour stocker les objet java. Le deuxième différence, s’il
n’y a plus de mémoire dans le stack, lorsque on fait appel à une fonction ou un variable local,
JVM lance une exception StackOverFlowError. Tandis que s’il n’y a pas d’espace dans le
Head lorsqu’on veut créer un objet, JVM lance une exception OutOfMemoryError. Une autre
différence c’est que la tail de la mémoire Head est plus petit que la tail de la mémoire Stack.
Une autre différence c’est que les variables stockés dans stack sont visible que par le thread
propriétaire. Tandis que les objets créés dans le Head sont visible par tous les threads.
We talk about concurrency when you have more than one task in a single processor with a
single core and the operation system switches from one thread to another, sot it seems that
all tasks run simultaneously. While, we talk about parallelism when have more than one task
that run simultaneously in the same time in a differents core.
What is Synchronization ?
Synchronization is the coordination of two or more tasks to get the desired results. We have
two kinds of synchronizations. The first is control synchronization when, for example, one
task depends on the end of another task. The second is Data access synchronization, when
two or more tasks have access to a shared variable and one of the tasks can access to the
variable at any given time.
What is Deadlock ?
we have deadlock when there are two or more tasks waiting for a shared resource that must
be free from the other, so none one of them will get resources they need.
How is a liveLock ?
A liveLok occurs when tow tasks changing their states due to the actions of the other.
Spring 5
C’est IOC ?
IOC est un processus par lequel un framework permet d’établir les relations entre les
différentes parties de l’application.
C’est quoi un design pattern ?
Un design pattern est une solution générale qui permet de résoudre un problème commun
dans les applications
Un anti pattern est une habitude chez les développeur qui réduit la productivité ou la qualité
d’un logiciel.
Revu de code :
Quel est l'objectif de revu de code ?
Le revue de code permet d’assurer une bonne qualité de code, une facilité de
compréhension du code et une collaboration et monté en compétence de l’équipe.
Il y a plusieurs règles de nommage qu’il faut respecter je peux citer par exemple :
Le nom de la méthode, classe, variable ect doit correspondre à l’intention métier.
pour ça il faut s'intéresser avant tout à satisfaire le besoin métier et le comprendre
bien, pour respecter un nommage fonctionnellement parlant, afin de comprendre à
quoi correspond une variable ou ce que fait une méthode.
Le nom doit être prononçable afin de communiquer facilement.
Le nom d’une classe ou une méthode doit appartenir aux groupes nominaux, car une
classe ou une méthode définit un objet en particulier
Le nom d’une méthode doit appartenir aux groupes verbaux, car elles font une action
Le nom d’une variable boolean ou une méthode qui renvoie un boolean doit avoir une
forme interrogatoire car elle répond à une questions
Le nom d’un énumérateur doit être un adjectif, car il représente un état.
La loi de demeter dit qu’un module ne doit pas connaître les détails interne des objets qu’il
manipule. Plus précisément, la loi de demeter stipule qu’une méthode f d’une class C ne doit
apeler que les méthodes de la classe C, les méthodes d’objet créé par la méthode f, les
méthodes des objets passé en argument de la méthode, les méthodes d’un objet contenu
dans une variable d’instance de C.
Un objet expose un comportement et masque les données. Alors que les structures de
données exposent des données et n’ont pas de comportement significatif. Les structures de
données sont utilisées comme des enregistrement actif qui sont habituellement des
transformation des tables de base de données ou une autre source de données. Alors que
les objet sont utilisé pour contenir les règles métier et cachent leurs données interne.
la gestion des exception est important, mais s’il masque la logique il est mauvais.
Pour utiliser les conditions proprement, tout d’abord, il faut préférer une condition positif à
une condition négative. Deuxièmement, une condition complexe doit être isolé dans une
méthode.
Il y a plusieurs règles à respecter pour les classes.Je peux citer par exemple :
Une classe doit avoir une seul responsabilité
On dit qu'une conception est simple s’il respecte les quatres régles suivantes :
Il réussit tout les tests
il ne contient aucun redondance
il exprime les intentions de programmeur
il réduit le nombre de classes et de méthodes
Comment on peut éviter les données partagées ?
Pour protéger les données partagées on peut utiliser le mot clé synchronezed pour protéger
les sections critiques du code. Une autre manière consiste à copier les objets et à les
manipuler en lecture seul. ça permet d’éviter le point de synchronisation.
Ce principe indique qu’une classe ne doit pas avoir qu'une seul raison pour être modifié.
Parce que le fait de changer une responsabilité de la classe va influencer les autres
responsabilités.
Dans un premier temps, il faut lister les responsabilités de la classe. Puis choisir une
responsabilité et extraire les méthodes pour en créer une nouvelle classe. Ensuite, on
continue tant que la liste de responsabilité contient plus d’un élément.
Ce principe indique qu’une classe doit être fermé à la modification et ouvert à l’extension.
Cela signifie que le comportement d’une classe peut être étendu si les exigences de
l’application sont modifiées. Le but de ce principe est de mettre en place des classes pour
lesquelles il sera possible de nouveau comportement sans modifier le code source de la
classe elle même.
Pour mettre en place le principe OPC on peut utiliser plusieurs mécanismes. Premièrement,
en utilisant l’abstraction qui consiste à séparer le code en deux parties. une partie abstraite
qui regroupe le code commun. Une autre partie concrète qui propose une implémentation
spécifique pour un comportement donné. Deuxièmement en utilisant les design pattern
comme le design pattern de l’injection de dépendance. Troisièmement, on peut aussi utiliser
des classes génériques.
Ce principe indique que tout type de base doit pouvoir être remplacé par un de ces sous-
type.
Ce principe indique qu'un client ne doit jamais être forcé de dépendre à une interface qui
n’utilise pas. Pour cette raison ce principe insiste que chaque interface doit être simple à
comprendre de manière unitaire.
Ce principe indique que les modules de haut niveau ne doivent pas dépendre aux modules
de bas niveau. Les deux doivent dépendre aux abstractions. D’une autre manière, les
abstraction ne doivent pas dépendre des détails. Les détails doivent dépendre aux
abstractions.
Pour mettre en pratique le principe DIP, il faut d’abord analyser les modules de bas niveau.
Pour chaque élément il faut mettre en place une abstraction (soit par une interface ou une
classe de type Factory). Ensuite, il faut écrire les implémentation qui respecte ces interfaces.
Enfin, l’application doit définir quel implémentation doit utiliser pour chaque interface.
L’avantage de DIP est qu’il permet de changer l’implémentation en modifiant simplement une
ligne de code dans l’application.
un cas de test est un ensemble composé de trois objets. Un état de départ, un état d’arrivée
et un oracle qui permet de prédire l’état d’arrivée en fonctionne d’état de départ et permet
aussi de comparer le résultat théorique avec le résultat pratique.
Il existe deux types de tests les tests boite noir et les tests boîte blanch. Pour les tests boîte
noire se font sans que le testeur connaisse le contenu de la méthode qu’il va tester, alors
que les tests boîte blanch doit avoir l’accès au contenu de la méthode.
Les tests sont insuffisants tant que il existe des conditions qui n’ont pas été explorées par les
tests ou des calculs qui n’ont pas été validés.
Il permet de montrer quelles sont les dépendances qui peuvent être monté de version au
sein de notre projet.
Un pipe est élément anglur qui prend des données en input et les transforment et puis
affiche les données modifiées dans le DOM. exemple, date, currencyPipe, UpercasePipe,
lawerCasePipe.
Une directive c’est élément angular qui permet soit de modifier le DOM et l’apparence de
DOM. Il existe deux type de directives. Les directives structurelle et les attributs directives
C’est un élément qui permet de synchroniser les données entre une template HTML est une
un composant. Angular défini quatres sorties de databing. Premièrement l’interpolation qui
permet de modifier le DOM à partir du model. Deuxièmement, Property Binding, ce
mécanisme permet de modifier une propriété d’une directive ou un composant de DOM.
Troisièmement, Event Binding qui permet d’exécuter une fonction porté par un composant
suite à événement émis par un élément du DOM. Dernièrement, tow way binding qui est une
combinaison entre le Property Binding et Event Binding.
Nous avons géré les erreurs côté back en implémentant errorHandler. Il permet de traiter de
types d’erreurs les erreurs de serveur et les erreurs de client. On plus de ça on utilise
HttpInterceptor.
La lazy laoding est une fonctionnalité d’angular permettant de charger du code applicatif
lorsque celui-ci est sollicité. L’idée c’est que à la compilation cli et webpack va découper
l’application en plusieurs fichiers ou chunk.
Le routage en angular ?
La première différence c’est avec les observable tant que vous n’avez pas appelé les
méthode subscribe sur un observable le contenu de celui-ci ne s’exécute pas. Alors que un
promise dès qu'il est créé est exécuté quoi qu’il arrive. Deuxièmement les promise produit
une valeur alors que les observables des stream. Troisièmement, les promise non annulable,
alors que les observable sont annulable. Dernièrement, les promise proposent juste deux
méthodes alors que les observable proposent plus opérations.
ngrx permet de gérer l’état de l’application en stockant dans un objet javascripte, ce qui
permet à les composant de l’application de partager la même état.
Le store est simple objet javascripte contenant l’état de l’application. Pour qu'un composant
utilise le store il faut qu’il fasse une souscription à ce dernier
une librairie de side-effect, pour nous permettre de communiquer avec une donnée
externe ( ex: une API ). .
SOAP est un protocole et REST est un style d’architecture. La principale différence entre
SOAP et REST réside dans le degrée de couplage entre les implémentation client et le
serveur. Un client SOAP fonctionne comme une application bureau personnalisée,
étroitement couplé au serveur. Il y a un contrat rigide entre le client et le serveur, si l’un ou
l’autre change quelque chose il casse le contrat. Par contre SOAP est plus facile pour vérifier
si le contrat est respecté entre le client et le serveur. Une autre différence c’est qu’un si un
client veut consommer une API REST il a pas besoin d’avoir aucune connaissance sur l’API,
à l’exception du point d'entrée et le type de support. Tandis qu'avec SOAP, le client a besoin
au préalable de connaître tout ce qu'il va utiliser. La troisième différence c’est que REST
n’est pas couplé à le protocole HTTP.
WSDL est un interface permet d’indiquer comment utiliser un web service et comment
interagir avec lui. Il est basé sur XML et permet de décrire de façon précise les détails
concernant le service web tel que les protocoles, les ports utilisés, les opérations peuvent
être effectuées, les formats d’entrée et de sortie et les exception pouvant être envoyées.
Le cycle de vie de maven est découpé en phase qui sont exécutées séquentiellement les
uns à la suites des autres :
Validate : vérifier que la configuration de projet est correct.
Compile : compiler le code source
Test : permet de lancer les tests unitaire de l’application
Package : package les éléments issus de la compilation sous une forme distribuable
(jar, war)
Install : permet d’installer le package dans le repository local
Deploy : envoie le package dans le repository distance défini dans le POM
Elasticsearch se base sur le moteur de recherche lucene. Il permet l’indexation des données
sous forme de documents.
Un cluster est composé de plusieurs noeud qui communique entre eux. Chaque noeud
correspond à une instance d’Elasticsearch en cours d’exécution et il peut être ajouté ou
retiré du cluster même lorsque celui là est en train de fonctionner.
Un noeud est un serveur qui fait partie du cluster, il stocke les données et il participe à
l'indexation et à la recherche.
Un index est comme une base de données relationnelle. Chaque index dispose d’un
mapping qui permet de définir la structure des types.
Les types sont similaire à les tables dans une base de données relationnelle.
Un document représente une entrée dans un type. il possède un index, un type et un id.
C’est un mécanisme qui permet de répartir les données sur plusieurs noeud. Un shard peut
être primaire ou duplicata.
Les replica sont des copies des shards primaires auxquels ils sont rattaché. En cas d’erreur
du primaire, un replicas devient shard primaire afin que les système reste disponible.
C’est quoi mongodb ?
Il y a deux types d’exigences dans un projet informatique : les exigences fonctionnelles et les
exigences techniques.
Authentification forte :
L’utilisateur se connecte au moyen d’une page d'authentification de l’application CNX. Puis il
saisit son login et mot de passe cliquez sur le bouton connexion pour envoyer une requête
https avec la méthode poste. L’application côté back reçoit la requête, puis elle interroge le
LDAP pour savoir est ce que l’utilisateur existe ou non. Si il existe, il vérifie est ce que il est
interne ou externe. Si il est interne il récupère le mot de passe hache de l’utilisateur depuis
l’active directory. Si il est externe il récupère le mot de passe hache de l’utilisateur depuis un
autre LDAP. Une qu’on récupère le mot de passe hashe on hashe aussi le mot de passe
envoyé par l’utilisateur et on compare les deux mot de passes. Si les deux mot de passes
sont identiques on envoie une requête pour récupérer les applications auxquelles l’utilisateur
a le droit avec leurs profils et droits. Ensuite, On génère une JWT signé avec une clé de
cryptage. Ce JWT contient les application auxquelles l’utilisateur a le droit avec leurs profils
et droits. Une fois ce JWT est créé au niveau de serveur, il envoie une réponse au client
HTTP avec un status 200 et au même temps assigne un jeton JWT à un cookie sécurisé et
non exploitable côté client puisque il est HTTPONLY. La prochaine fois lorsque l’utilisateur
envoie une requête vers le serveur il envoie aussi le jeton JWT dans le header de la requête.
Si la requête est dont le pattern d’url est /api/* ou /fr/* est systématiquement contrôlées par
un filtre de sécurité. Ce filtre valide le jeton d’authentification en vérifiant sa signature
numérique au moyen de la clé secrète utilisé pour générer le jeton d’authentification.
Le JWT est composé de trois parties séparées par des points. La première partie est sous
format JSON et encodée en BASE64 et elle contient l'algorithme de chiffrement utilisé pour
signer le token. La deuxième partie est aussi sous forme JSON et encodée en BASE64, elle
représente la charge utile du token, il contient le login, les applications, les profils et les rôles.
La dernière partie contient la signature numérique du token.
Quels sont les information qu’on envoie dans l’entête de la requête HTTP ?
Dans une requête http on peut trouver plusieurs information comme par exemple :
host : c’est le nom de domain utilisé pour envoyer la requête
accept : il permet d’indiquer le format du la réponse que le client attend
context-Type : il indique le format du contenu envoie par le client
Cookie : ce sont les cookies associé à le domaine
Connection : indique si la connexion TCP doit rester ouvert (Keep-Alive) ou être
fermé (close)
Quels sont les informations qu’on trouve dans l’entête de la réponse HTTP ?
Dans l’entête de la réponse HTTP on peut trouver plusieurs informations comme par
exemple :
La version du protocole HTTP
Le statut de la réponse et le message associé
La date du serveur
Le type de serveur qui a envoyé la réponse
La date du dernière modification
Content-Type : le format du contenu de la réponse
Content-legnth :
Set-Cookie : il est utilisé par le serveur pour demander au client d’enregistrer une
information dans les cookies.
Dans un premier temps le client essaie d’établir une connexion avec serveur en créant une
socket. Si cette connexion est accepté par le serveur le serveur également va créer une
socket. Cette opération est faite par le navigateur. La deuxième étape le client envoie une
requête http en utilisant le méthodes http (GET, PUT,POST,DELETTE,OPTION) pour
demander au serveur un document. Ensuite, le serveur il va chercher ressource et envoie la
réponse. Dans la réponse il y a le statut et l'entêt et le corps de la réponse. Une fois le
réponse est complètement chargé par le client et il n a pas besoin d’autres ressources il y a
une déconnexion côté client. Une déconnexion veut dire que les sockets qu’ont été créée au
moment de la connexion détruites.
Une requête HTTP peut être envoie en utilisant les méthodes suivantes :
GET : pour consulter une ressource
POST : Pour soumettre ou ajouter une nouvelle ressource
PUT : pour mettre à jour une ressource
DELETTE : pour supprimer une ressource
HEAD : permet de consulter les métadonnées d’une ressource comme le type, la
date de dernière modification
OPTIONS : permet au client d'interroger le serveur pour fournir les options de
communication à utiliser pour une ressource ou un ensemble de ressources. Par fois
avant d’envoyer une requête avec GET ou POST ou DELETTE on envoie une
requête avec OPTIONS pour demander au serveur les options de communication
que le client doit respecter pour accéder au ressource au niveau de serveur. Par
exemple on peut récupérer les méthodes autorisées ou les entêtes autorisées et les
Origines autorisées.
Comment on peut personnaliser les code statut des requêtes http avec Spring ?
Pour personnaliser les codes statut des requêtes http avec Spring on peut utilisé la classe
ResponseEntity. C’est une classe qu’est hérité de la classe HttpEntity et permet de définir le
code statut à retourner.
Le Cross Site Request Forgoret ,l’abrégé de CSRF, est type de vulnérabilité des services
d’authentification. L’objectif de cette attaque est de transmettre à un utilisateur authentifié
une requête http falsifié qui pointe sur une action interne du site, afin qu’il l’exécute sans en
avoir conscience et en utilisant ses propres droits.
Pour éviter l’attaque Cross Site Request Forgoret on peut utiliser des jetons de validité
(CSRF Synchronizer Token) dans les formulaires. ça veut dire qu’une formulaire posté ne
soit pas accepté que s’il a été produite quelques minutes auparavant. Le jeton de validité en
sera la preuve. Ce jeton est généré par le serveur.
Pour éviter ce type d’attaque d’une part il convient de filtrer les données injecter dans notre
site en vérifiant la syntaxe des entrés avec la suppression comme < > / script ect. D’une
autre part pour protéger les cookies contre cette attaque il faut utiliser les cookies en mode
HttpOnly pour ne pas permettre aux scriptes de lire ces cookies.
Quels sont les problèmes qu’on peut rencontrer si on travailler avec l’authentification
basé sur les sessions et comment les résoudre ?
Le premier problème on le rencontre si l’application est déployé sur plusieurs serveur. Dans
ce cas on doit penser à mettre en place un load balancer qui permet de diviser les
différentes requêtes client sur les différentes serveurs disponible pour régler le problème de
montée en charge. Le problème, c’est que si l’utilisateur est authentifié avec un serveur1 si
la prochaine requête de client passe un autre serveur que le serveur1 il va le considérer
comme non authentifié. L’un des solution pour résoudre ce problème est d’utiliser un cache
mémoire partagé qui va se charger d’enregistrer les sessions. ça veut dire que si le client est
authentifié par le serveur1 la session ID est enregistrer dans le cache mémoire partagé par
tous les serveur. Le problème de cette solution c’est qu’on a créé un Single Point Of
Failure ça veut dire si le serveur de cache mémoire partagé tombe en panne veut dire que
tous les sessions des utilisateurs sont perdus. La deuxième solution est l’utilisation d’un
cache distribué ça veut dire que les session ID sont dupliqué dans tous les serveurs. Une
autre solution consiste à utiliser un Stiky Session ça veut dire qu’on va configurer le load
balancer afin que si un client1 est authentifié par un serveur1 on va enregistrer dans la load
balancer cette information, comme tout les requête qui vient du client1 le load balancer va
les envoyer au serveur1. Le problème de cette solution c’est que si le serveur1 tombe en
panne la session de l’utilisateur est perdu.
JSON Web Tocken est un standard qui définit une solution compacte (en raison de leur petit
taille, les JWT peuvent être envoyés via un URL http ou paramètre POST ou dans un en-tête
HTTP) et autonome ( Le JWT contient tous les informations requises sur l’utilisateur ce qui
évite d’avoir besoin interroger la base de données plus d’une fois) pour transmettre de
manière sécurisée des informations entre les applications distribuées. Ce JWT peut être
vérifié et fiable car il est signée numériquement.
L’aidé c’est qu’il y a un utilisateur qui se connecte il va saisir son login et son mot de passe
et envoie une requête http vers le serveur. Une fois le serveur reçoit la requête il va se
connecte au LDAP pour savoir si l’utilisateur existe ou pas. Si il existe il va récupérer les rôle
et tous les informations de l’utilisateur. Ensuite, l’application va générer un Token JWT en
calculant une signature basée sur un secret. Une fois le JWT est généré on va envoyer ce
JWT dans la réponse au client. On va l’envoyer dans un entête http. Ce Token une fois reçu
par le client il l’enregistre dans les cookies. Après à chaque fois que l’utilisateur envoie une
nouvelle requête il envoie ce Token dans l’entête de la requête. Le serveur à chaque fois
qu’il reçoit une requête il vérifier si l’entête de la requête contient ce Token. Si il existe il va
vérifier la signature de ce token en utilisant le même secret.
Quel est la structure de JWT ?
Le JWT est constitué de trois parties séparés par un point le header, le payload et la
signature. Le header est un objet JSON encodé en Base64 composé de deux parties. La
première partie est le type de jeton,qu’est JWT. La deuxième partie est l’algorithme de
hachage utilisé tel que HMAC et RSA. Le payload contient les claims. Les claims sont des
informations à échanger entre le client et le serveur .La signature permet de d’assurer
l’intégrité du contenu de JWT. Pour générer la signature on concatène le header et payload
puis on encode avec l’algorithme défini dans le header.
Il y a trois type de claims, les public claims, private claims et registred claims. les registred
claims sont des attributs réservés définis dans la spécification comme par exemple la date
d’expiration de token,l’origine de token ,l’identifiant de token et la date de création de token.
Les public claim sont des attribués qui peuvent être défini à volonté mais il faut l’enregistrer
dans un annuaire. Les private claim sont des attribués personnalisé pour l’application.
La différence c’est Session ID est token par référence (parce que on a besoin d’une
application tiers pour connaitre les information de ce token) alors que JWT est un token par
valeur (Parce que le JWT contient tous les informations de l’utilisateur).
Il y a deux manière pour signer le JWT. La première est symétrique avec un secret partagé
entre toutes les application. La deuxième est Asymétrique avec une clé privé pour signer et
générer le JWT et un clé public pour vérifier la validité du JWT. Pour la deuxième façon dans
une architecture micro-service on besoin un seul micro-service qui possède la clé privé ce
qu’on appelle le référentiel des identités. C’est ce micro-service qui permet de s’authentifier
et de générer et signer le JWT. Après l’authentification les autres micro-services n’ont pas
besoin de clé privé ils ont besoin seulement la clé public pour vérifier la validité du JWT.
On peut stocker le JWT soit dans les cookies, localStorage ou SessionStorage. Dans le ca
de LocalStorage et sessionStorage à chaque fois qu’on veut envoyer une requête,
l’application doit aller vers le localStorage pour lire le JWT et le stocker puis l’envoyer en
utilisant javaScripte. L’avantage de cette technique c’est qu’il n’y a pas le faille CSRF par ce
que CSRF ne concerne que les cookies. Cependant, comme le JWT doit être disponible
pour l’application javaScripte, il est exposé à l’attaque Cross Site Scripting. Dans le cas des
cookies on n’a pas besoin d’écrire de code javaScripte pour envoyer le JWT. Pour sécuriser
le JWT contre l’attaque Cross Site Scripting, il faut lui attribuer les flags HttpOnly et Secure.
Le flag Secure est utilisé pour que le JWT ne soit pas envoyé soit via une requête HTTPS.
Parce que si on envoie une requête avec HTTP le token est envoyé en clair, on peut le
pirater. L’inconvénient des cookies c’est que le JWT est envoyé automatique via le
navigateur pour chaque requête, ce que l’expose aux attaques CSRF.
Comment on peut s’assurer qu’une requête a été bien envoyé par l’application ?
Il existe plusieurs techniques pour vérifier qu’une requête est envoyé par l’application :
La première, à l’authentification le serveur calcule un identifiant aléatoire qu’il stocke
dans le JWT en tant que claim privée. Ce identifiant sera stocker aussi dans le
localStorage. La bonne concordance entre entre les deux informations permet de
vérifier que la requête a été bien envoyé depuis l’application.
La deuxième solution consiste à contrôler les entêtes “Origine” et “Referer” côté
serveur pour vérifier que la requête a été bien envoyé depuis l’application.
La première utilisation est avec les formulaire en plusieurs parties. On peut stocker, côté
serveur les données saisies dans la première page dans le JWT et l’envoyer au client dans
la page 2 jusqu’à la page final qui sera stocké côté serveur. La deuxième utilisation est avec
la confirmation des emails. Comme le JWT est en base46, on peut générer un JWT avec
une date d'expiration réduite et l’envoyer par mail sous forme d'un URL.
Avec Spring Security touts les requêtes passe par le filtre SpringSecurityFiltre avant d’arriver
au dispatcher Servlet. Si l’utilisateur veut se connecte il envoie une requête HTTP avec le
login et le mode de passe. Cette requête passe par le filtre SpringSecurityFiltre qui fait appel
à la méthode attemptAuthentification d’une classe FilterAuthentification. Cette méthode
permet de récupérer les données saisis par l’utilisateur puis retourner un objet de type
Authentification à SpringSecurityFilter. Ensuite, SpringSecurityFilter fait appel à la méthode
loadUserByUsername qui permet de charger les informations de l’utilisateur. Ensuite,
SpringSecurityFilter vérifier si est le mot de passe envoyé par l’utilisateur est le même que
celui récupéré à partir de la base de données. Pour faire cette comparaison il utilise un
algorithme de hachage. Dans notre cas on a utilisé Bcrypte. Ensuite SpringSecurityFilter fait
appel à la méthode succesfulAuthentification qui permet de générer le JWT en utilisatn un
secret. Une fois le JWT est généré on va à le header http une entête qui s’appelle
authorisation dans laquelle on va ajouter un prefix suivi de JWT généré. Ensuite le JWT est
stocké dans les cookies.
Quand un utilisateur envoie une requête http, il envoie dans le header de la requête l’entête
authentification qui contient le JWT. Cette requête est intercepté par le filtre
SpringSécurityFiltre qui fait appel à la méthode doFilterInternal de la classe
AuthentificationFiltre. Cette méthode permet de signer le JWT est ce que il est valide ou non.
S’il est valide on récupère les informations de l’utilisateur. Ensuite SpringSecurityFiltre
charge les informations de l’utilisateur dans le context du spring sécurité. Ensuite, la requête
va être envoyé vers Dispatche Servlet.
qu’est ce Hazelcast ?
Hazelcast est un Middleware Open source en java, qui permet de créer un cache mémoire
distribué. Dans une grille Hazelcast, les données sont réparties uniformément entre les
noeuds d’un groupe d’ordinateurs, ce qui permet un stockage distribué scalable, un
traitement distribué scalable et la réplication des données sur plusieurs noeud pour tolérance
aux pans.
Ces techniques réduisent la charge de requête sur les base de données et améliorent les
performances des systèmes distribués.
Comment HazelCast fait le mécanisme de distribution ?
HazelCast découpe une collection à plusieurs fragment. Ces fragments appelés des
partitions. Les partitions sont des segments de mémoire pouvant contenir des centains ou
des milliers d’entrées de données, en fonction de la capacité de mémoire de votre système.
Chaque partition peut avoir plusieurs répliques qui sont répartis entre les membres du
cluster. Une des répliques devient la réplique principale et les autres sont appelés des
sauvegardes. Le membre qui possède le réplica principal d’une partition est appelé partition
propriétaire. Lorsque vous lisez ou écrivez une entrée de données particulière, vous parlez
parlez de manière transparente au propriétaire de partition.
HazelCast propose par defaut 271 partitions. Si on démarre un cluster avec un seul membre,
ce membre possède toutes les 271 partitions. En démarrant d’autres membres les partitions
sont distribuées équitablement aux membres de cluster.
Un cluster HazelCast est un réseau de membres qui exécutent Haselcast. Un membre peut
se rejoindre automatiquement au cluster. Cette jonction se fait avec divers mécanismes de
découverte que les membres de cluster utilisent pour se retrouver. Celui utilisé par défaut est
multicast (UDP), il fait un multicast sur le réseau pour découvrir les instances démarrés. Ce
mécanisme est assez pratique, mais il est déconseillé en production. La deuxième solution
est TCP. La troisième solution est le cloud.
IOC est patron d’architecture qui permet à l’application de déléger la gestion de cycle de vie
des dépendance et leur injection à une autre entité
configuration basé sur XML, configuration basé sur annotation, configuration basé sur java.
Quelles sont les portées proposées par spring ?
L’annotation @Component est un stéréotype générique pour tout composant géré par
spring. Alors que les annotations @Service et @Respository et @Controler sont des
stéréotypes spécialisées de l’annotation @Component. L’annotation @Service est utilisé
dans la couche service et il fournit aucun comportement supplémentaire par rapport à
l’annotation @Componenet. L’annotation @Controler marque une classe en tant que
contrôleur spring MVC. L’annotation @Repository est utilisé dans la couche DAO.
@RestControler vs @Controler ?
il est utilisé avec @Autowire et il permet de supprimer l'ambiguïté en spécifiant le bean qui
doit être injecté.’
Singleton
AOP est un paradigme de programmation qui permet de traiter les occupation technique
séparément des occupations fonctionnels.
Atomicité : ça veut dire que tous les changements sont effectué ou pas du tout
Cohérence : ça veut dire respecter les contraintes d’intégrité des données de la base
de données
Isolation : Chaque transaction doit s’exécuter comme si il était la seul dans le
système pour éviter les possibilités d’interférences.
Durabilité : ça veut dire que les transactions réussies survivront de façon permanente
Un verrou c’est un mécanisme qui permet de placer une interdiction temporaire à une partie
de la base de données, dans le cas d’accès simultanés qui pourraient aboutir à des
incohérences.
Pour simplifier cette logique on peut regrouper ces cinq étapes en trois grands idées :
Tester tout d’abord
Rendre fonctionnel
Rendre meilleure
MongoDB :
MongoDB est une base de données NoSQL orientée documents.
Nous prendrons Robo3T version 1.1
Une collection c’est l’équivalent d’une table pour une base de données relationnelle.
Un document est encapsulé dans des accolades {...}, pouvant contenir des listes de
clés/valeurs
Une valeur peut être un type scalaire (entier, nombre, texte, booléen, null), des listes
de valeurs [...], ou des documents imbriqués
Version 12
Expression switch -> permet de retourner un résultat et classer plusieurs valeurs pour
chaque CASE
Version 11
Linux
Comment les utilisateurs sont organisés sous lunix ?
Sur linux il y a un utilisateur spécial, root. Il a tous les droits sur la machine. Puis il des
groupes des utilisateurs.
On peut devenir root un instant en utilisant la commande sudo. Pour devenir root
définitivement on utilise la commande sudo su.
Pour créer un utilisateur on utilise la commande adduser. Puis pour modifier le mot de passe
on utilise la commande passwd suivi par le nom de l’utilisateur. Pour supprimer un utilisateur
on utilise la commande deluser suivi par le nom de l’utilisateur. Cette commande ne
supprime pas le dossier personnel de l’utilisateur. Pour supprimer le dossier personnel aussi,
il faut utiliser la commande deluser --remove-home. Pour renommer un utilisateur on utilise
la commande usermod -l (par contre le nom de répertoire personnel ne sera pas changé).
Pour créer un nouveau groupe on utilise la commande addgroup. Pour ajouter un utilisateur
à un groupe on utilise la commande usermod -g suivi par le nom du groupe puis le nom de
l’utilisateur. Pour faire en sorte qu’un utilisateur appartient à plusieurs groupes on utilise la
commande usermod -G suivi par les noms des groupes séparés par des virgules puis le nom
de l’utilisateur. Pour supprimer un groupe on utilise la commande delgroup.
Il existe trois types de droit : r (droit de lecture 4), w (droit d’écriture 2), x (doit d’exécuter)
Pour changer le propriétaire d’un fichier on utilise la commande chown suivi de nom de
nouveau propriétaire puis le nom de fichier. Pour affecter les sous-dossier récursivement on
utiliser l’option -R.
Pour changer le groupe propriétaire d’un fichier on utiliser la commande chgrp suivi de nom
de nouveau groupe propriétaire puis le nom du fichier. Pour affecter les sous-dossier
récursivement on utilise l’option -R.
Les droits d’un fichier sont découpés en trois triples. Le premier triple indique les droits que
possède le propriétaire de fichier. Le seconde triple indique les droits que possède les
utilisateurs appartient au groupe propriétaire. Le dernier triples indique les droits des autres
utilisateurs.
Pour modifier les droits d’accès d’un fichier on utilise la commande chmod suivi soit des
chiffres ou des lettres. Pour les chiffres, il y a trois chiffres. chaque chiffre représente les droit
du propriétaire, du groupe et des autres utilisateur successivement. Chaque chiffre est le
calcule des chiffres qui représentent les droits. 4 pour la lecture, 2 pour l’écriture et 1 pour le
droit d’exécution. Pour attribuer les doit en utilisant le lettre que u représente le propriétaire,
p le groupe et o les autres utilisateurs. il faut savoir aussi que + signifie ajouter un doit et -
signifie supprimer un droit et = signifie effectuer un droit. exemple chmod u+rwx, p+rw, o+r
fichier.txt.
Pour assembler des fichiers dans un archive avec linux, il deux étapes. La première étape
consiste à rassembler les fichiers dans un seul fichier appelé archive. On utilise pour ça le
programme tar. La deuxième étape consiste à compresser le gros fichier en utilisant le
programme gzip ou bzip2.
Pour créer un archive tar on utilise la commande tar -cvf suivi de nom de tar, puis le fichier à
archiver.
Pour afficher le contenu de l'archive sans l’extraire on utilise la commande tar -tf suivi de
nom de l’archive.
Comment ajouter un fichier à un archive ?
Pour ajouter un fichier à un archive on utilise la commande tar -rvf suivi du nom de l’archive
puis le nom de fichier à ajouter.
Pour extraire les fichiers de l’archive on utilise la commande tar -xvf suivi du nom de
l’archive.
Pour compresser un archive on utilise l’une des commandes suivantes : gzip ou bzip2. pour
décompresser on utilise soit gunzip ou bunzip2.
Pour archiver et compresser en même temps en gzip on utilise la commande tar -zcvf suivi
du nom de l’archive puis le nom de fichier à archiver et compresser. Pour décompresser on
utiliser la commande tar -zxvf. Pour archiver et compresser en même temps en bzip2 on
utilise la commande tar -jcvf suivi du nom de l’archive puis le nom du fichier à archiver et
compresser. Pour décompresser on utiliser la commande tar -jxvf
Pour lire le contenu d’un fichier compresser on utilise la commande zcat, zmore et zless.
Pour décompresser un .zip et .rar on utilise la commande unzip et unrare. Pour compresser
on utilise la commande zip -r
Pour afficher le contenu d’un fichier on peut utiliser plusieurs commande. Tout d’abord, la
commande cat permet d’afficher tout le contenu de fichier dans la console tout d’un coup. Il y
a aussi la commande less qui permet d’afficher le contenu d’un fichier page par page. Pour
afficher le début d’un fichier on utilise la commande head -n 3. pour afficher la fin d’un fichier
on utilise la commande tail -n 3 -f -s 2. le paramètre -f permet de suivre ma fin d’un fichier
au fure et à mesure de son évolution.
Pour créer des fichier on utilise la commande touch suivi par la liste des nom des fichiers à
créer séparées par un espace. Pour créer un dossier on utilise la commande mkdir -p suivi
par le nom du dossier. Le paramètre -p permet de créer tous les dossier intermédiaires.
Pour supprimer des fichiers on utilise la commande rm suivi par les noms des fichiers à
supprimer séparées par des espaces. Pour supprimer un dossier et son contenu on utilise la
commande rm -r.