3.22 Règles-Java

3.22.1 - Java 17

La partie Socle de cette solution est initialement développée avec Java 17. Elle utilise donc certaines fonctionnalités intéressantes (et pour certaines relativement récentes) de Java :

  • les record sont une nouvelle façon de déclarer une classe en Java. Plus succincte, elle est pratique à utiliser pour déclarer des classes sans aucune méthode spécifique (uniquement des constructeurs, des getter et des setter). En voici la documentation.
  • les text bloc (documentation) sont autorisés (si on y trouve une utilité dans notre code).
  • le pattern matching est une fonctionnalité visiblement utile et intéressante à utiliser documentation

Par contre, certaines nouveautés ne me semble pas pertinente voire génantes. Donc ne sont pas autorisées sur le projet les fonctionnalités suivantes :

  • les var documentation
  • les switch n’ont jamais été le bien venu et leur nouvelle syntaxe ne les rend pas beaucoup plus attrayants documentation

3.22.2 - Spécificités associées aux record de Java

Les record de Java ont l’avantage de ne plus nécessiter de coder les getter et setter.

Mais attention ! Un record est une structure de données dont tous les attributs sont finaux. Donc aucun setter n’est disponible !!!

Pour les besoins de notre code, il peut être utile de créer un constructeur spécifique. Ceci est possible à condition d’appeler le constructeur natif du record avec une valeur par défaut pour chaque attribut (false, new ArrayList<>(), new HashMap<>() ou null). Exemple :

/** Constructeur vide */
public PageDto() {
	this(new ArrayList<>(), null, null, null, false, false);
}

/** Constructeur par défaut */
public PageDto(List<BlocDto> blocs, String conditionVisibilite) {
	this(blocs, conditionVisibilite, null, null, false, false);
}

Pour les besoins de SpringDataMongo, si la classe décrit une structure de données stockée dans MongoDB et qu’un constructeur est codé explicitement, il faut créer un constructeur complet pour MongoDB. Ce constructeur doit

  • être annoté avec @PersistenceConstructor
  • comprendre tous les membres de la classe avec le premier du constructeur natif à la fin (pour ne pas avoir la même signature que le constructeur natif. Exemple :
/**
 * Constructeur contenant tous les attributs mais avec le premier en dernier pour fournit à SpringDataMongo un constructeur à utitiliser (les
 * setter n'existe pas dans un record). De plus, pour les mêmes raisons mais avec Jackson, il faut ajouter un @JsonCreator sur le constructeur et
 * un @JsonProperty sur chaque paramètre.
 */
@PersistenceConstructor
@JsonCreator
public PageDto(@JsonProperty("conditionVisibilite") String conditionVisibilite, @JsonProperty("titre") String titre,
		@JsonProperty("titreAriane") String titreAriane, @JsonProperty("exclueDuFilDariane") Boolean exclueDuFilDariane,
		@JsonProperty("specifiqueAlaDemarche") Boolean specifiqueAlaDemarche, @JsonProperty("blocs") List<BlocDto> blocs) {
	this(blocs, conditionVisibilite, titre, titreAriane, exclueDuFilDariane, specifiqueAlaDemarche);
}

3.22.3 - Les imports

Seule la classe StringUtils de Spring peut être utilisée (org.springframework.util). La méthode la plus utilisée est hasLength.


3.22.4 - Validation de données en entrée des APIs exposées par le socle

Toute donnée doit être validée en entrée de toute API exposée par le socle.

La validation doit être la plus précise possible. Plusieurs REGEXs sont disponibles dans la classe RegexUtils du projet socle-commun. Si de nouvelles REGEXs doivent être créées pour correspondre au plus près au format d’une donnée, elles doivent être créées.

Pour valider un paramètre unique dans une méthode publiques d’un contrôlleur REST, il faut utiliser l’annotation @Pattern:

	ResponseEntity<byte[]> obtenirContenuDuCaptcha(//
			@Pattern(regexp = RegexUtils.ALPHABETIQUE_ETENDUE) String get, //
			@Pattern(regexp = RegexUtils.ALPHANUMERIQUE) String c, //
			@Pattern(regexp = RegexUtils.NOMBRE) String t);

Pour valider un objet passer dans un corps de message, il faut demander la validation de l’objet avec l’annotation @Valid et utiliser l’annnotation @Pattern pour valider chacun des membres de cette classe :

String soumettreUnTeledossier(@Valid @RequestBody DonneesDeSoumissionDto dto);

public class DonneesDeSoumissionDto {
	@Pattern(regexp = RegexUtils.CODE_DEMARCHE)
	private String codeDemarche;

	private Map<@Pattern(regexp = RegexUtils.DONNEE_SOUMISE_CLEF) String, @Pattern(regexp = RegexUtils.DONNEE_SOUMISE_VALEUR) String> donnees = new HashMap<>();

	@Pattern(regexp = RegexUtils.ID)
	private String idBrouillon;
	...
}

Pour information, dans les écrans WEB de SwaggerUI, les contraintes sur les membres de classes soumises (en bas de page, dans la partie schéma) sont bien décrits avec leurs validations. Mais, pour les paramètres d’appels de type simple (chaîne de caractères, nombres, …), les contraintes ne s’affichent pas dans la page. Néanmoins, si une donnée invalide est saisie, SwaggerUI n’exécute pas la requête car il sait que la donnée est invalide mais il affiche un message. Toutes les validations de données sont présentes dans le swagger (document JSON descriptif technique).


3.22.5 - L’injection de dépendance de Spring

L’annotation @Autowired permet d’injecter un composant dans un autre.

Depuis des années, l’habitude / le standard est d’utiliser l’annotation @Autowired sur chaque membre d’un composant qui est un composant à injecter (tout comme pour les valeurs de configuration avec @Value).

Mais, depuis quelques mois/années est poussée une autre façon de faire : l’usage du constructeur. Sonar, depuis peu, pousse aussi cette pratique avec la règle RSPEC-4288.

Sur le principe, l’idée est bonne :

  • si le composant est instancié via un constructeur initialisant tous les membres, il est donc impossible de créer une instance du composant sans tous les composants dont il a besoin (c’est l’objectif d’un constructeur de classe)
  • si tous les membres sont initialisé par le constructeur, les membres peuvent être finaux. Donc, une fois instancié, le composant ne peut plus être abimé.
    • mais ce point à son équivalent négatif : dans le cas des tests automatisés, il est utile de bidouiller/abimer un composant pour injecter un bouchon ou modifier une configuration (d’où l’usage de @MockBean de Mockito avec @Autowired versus @Mock couplé à @InjectMock).

La documentation de l’injection de dépendance de Spring est documentée ici.

Les consignes sont donc :

  • 1/ créer un constructeur sans l’annotation @Autowired dans tous les composants applicatifs (donc dans src/main/java)
    • avec les composants à injecter
    • avec les configurations @Value
  • 2/ utiliser, dans les tests automatisés, le couple
    • @MockBean pour faire créer les bouchons par Mockito
    • @Autowired pour les composants applicatifs dans lesquels injecter les bouchons