[JAVA] L'histoire de la rencontre avec l'annotation personnalisée Spring

Ravi de vous rencontrer

Cet article est le 9ème jour du Calendrier de l'avent MicroAd 2017 . Comme c'était ma première tentative à Qiita, j'ai décidé de garder un enregistrement comme mon propre mémo. Veuillez nous faire savoir si vous avez des suggestions telles que des informations incorrectes ou des omissions.

objectif

Faisons un outil facile mais réutilisable. Les annotations originales sont un petit pas dans cet effort. Et je connais un peu le contenu.

La logique de service suffit

~~ C'est vrai ~~

Je veux essayer quelque chose qui me plaît. Pour une raison quelconque @ Ce symbole a toujours semblé attrayant. De plus, lorsque la même logique de vérification est nécessaire à l'avenir, il suffit d'ajouter une annotation et il est facile de voir quel type de vérification sera effectué.

Annotation (@interface)

Pour le moment, les annotations en java sont positionnées comme des interfaces. Selon citation liée

The motivation for the introduction of annotations into the Java programming language was the need for semantic information for a given piece of code

Il est décrit comme résultant de la nécessité de traiter le "sens" d'un code donné dans la programmation java.

Voici les remplacements d'annotations typiques fournis en tant qu'API standard.

Override.java


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

L'annotation attachée à l'annotation est Il s'agit d'une méta-annotation fournie par le package java.lang.annotation.

@Target C'est la cible à annoter. Vous pouvez en spécifier plusieurs en les mettant entre `` {} ''. Comme type de cible

Il y en a plusieurs, mais dans le cas de @Override, METHOD est ciblé. En fait, @Override ne peut pas être attaché à des éléments autres que des méthodes.

@Retention Décrit la plage affectée par l'annotation. Retention renvoie un RetentionPolicy et il n'existe que trois types de RetentionPolicy.

Dans le cas de @Override, RetentionPolicy est SOURCE, donc A la fin de la compilation, cela n'a aucune signification == Il ne sera pas converti en byte code.

Dans certains cas, Target et Retention sont simplement écrits comme import static pour plus de lisibilité.

Cet exemple

Ce que je voulais cette fois, c'était une annotation pour compter séparément la demi-largeur et la pleine largeur </ b> et limiter la longueur maximale </ b>. Pour le modèle, je me suis généralement référé au package javax.validation.constraints (@Size et @NotNull, non?).

En fait, cette annotation d'origine, c'est-à-dire l'annotation de contrainte, a un modèle fixe (message (), groups (), payload () doivent être définis), alors créez-le en conséquence.

Je vais lui donner un nom.

CustomSize.java


@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CustomSizeValidator.class})
public @interface CustomSize {

  String message() default "{validation.CustomSize.message}";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
  int max();

  @Target({FIELD})
  @Retention(RUNTIME)
  @Documented
  @interface List {
    CustomSize[] value();
  }
}

@Documented Indique que l'écriture avec javadoc etc. est nécessaire pour utiliser cette annotation. Pour être honnête, je n'en ai peut-être pas eu besoin cette fois.

@Constraint Spécifiez la classe qui décrit la logique spécifique que vous souhaitez contraindre (vérifier) avec cette annotation. Voici la classe d'implémentation de validation pour cette annotation.

CustomSizeValidator.java


public class CustomSizeValidator implements ConstraintValidator<CustomSize, String> {

  private int max;

  @Override
  public void initialize(CustomSize customSize) {
    max = customSize.max();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    //CustomSizeUtil est une classe qui décrit uniquement la logique de contrôle
    //La valeur de retour de getLength est int
    return CustomSizeUtil.getLength(value) <= max;
  }
}

initialize </ b> est le processus d'initialisation pour appeler isValid.

isValid </ b> implémente la logique de validation réelle. La value` '' passée en paramètre est l'objet réel à vérifier. Cette fois, la longueur de la chaîne de caractères d'entrée est vérifiée, donc une chaîne de caractères de type String correspond à cela.

Si `` valeur '' ne peut pas être vérifiée en raison d'une valeur incorrecte saisie, false est renvoyé.

message() Ce message est un avertissement (?) Lorsqu'une entrée incorrecte est effectuée. Contient le libellé à définir dans les propriétés du message (telles que ValidationMessages.properties d'Hibernate). En outre, la clé est écrite avec un nom de classe complet.

Par exemple, je pense que cela ressemblera à ceci.

ValidationMessages_jp.properties


validation.CustomSize.message = {0}Veuillez ne pas dépasser

groups() C'est un paramètre qui peut être personnalisé pour un groupe de validation spécifique. Doit être initialisé avec un type de classe <?> Vide.

Le regroupement sert à ordonner les contraintes.

@GroupSequence({CheckA.class, CheckB.class}) //Après avoir vérifié A B
public interface GroupedChecks {
}

payload() C'est une déclaration uniquement pour donner des méta-informations à l'objet à vérifier. En fait, je pense que le contenu de l'interface javax.validation.Payload``` est vide et est destiné aux marqueurs ou à la catégorisation.

Par exemple

CustomSize.java


@Target({FIELD})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {CustomSizeValidator.class})
public @interface CustomSize {

  String message() default "{validation.CustomSize.message}";
  Class<?>[] groups() default {};
  class Text implements Payload{} // Add Payload
  // omit
}

SampleModel.java


public class SampleModel {
  @CustomSizeValidator(max = 20, payload = {CustomSizeValidator.Text.class})
  private String memo;
  // omit
}

Ce n'est pas correct, mais vous pouvez donner un sens au champ mémo '' afin qu'il puisse être identifié comme une catégorisation telle que Texte ''.

Laissez-le vide sauf indication contraire.

max() Cette fois, j'ai mis le nom max car c'est une validation qui limite la "longueur maximale".

 @CustomSizeValidator(max = 20) //Nom à saisir ici
 private String memo;

List { A[] value() } Définissez la ou les cibles vérifiables en fonction de l'implémentation de ConstraintValidator.

@interface List {
  CustomSizeValidator value();
  AnotherValidator aaa();
}

Sauf si vous avez une raison spécifique, un élément peut suffire.

utilisation

C'est la même chose que les autres vérifications d'entrée, mais ajoutez @Valid au modèle supérieur passé comme paramètre du contrôleur et définissez BindingResult en le disposant immédiatement à côté.

SampleController.java


  @RequestMapping(value = "/sample", method = RequestMethod.POST)
  public String samplePost(@ModelAttribute @Valid BigModel model, BindingResult result) {
    // omit
   }

Pour les modèles imbriqués, ajoutez également @Valid au modèle supérieur.

BigModel.java


public class BigModel {
  @Valid
  private SmallModel smallModel;
  // omit
}

Cette annotation personnalisée est @Target ({ElementType.FIELD}), elle cible donc le champ. Attachez-le au champ à vérifier.

SmallModel.java


public class SmallModel {
  @CustomSize(max = 20)
  private String input;
  // omit
}

tester

  • Testez pour exécuter l'application et essayez-la directement (l'erreur est stockée dans BindingResult```)

  • Test avec code de test (donnez quelques valeurs et calculez vraiment exactement)

Puisqu'il s'agit d'un nombre de caractères avec une limite fixe, je l'ai testé avec une analyse des valeurs limites. Je teste le «isValid ()» «de la classe d'implémentation de la contrainte d'annotation.

CustomTest.groovy


   // omit
   when:
      def result = validator.isValid("", null)
    then:
      assert result == excepted
    where:
      testCase                | maxLen | doubleLen || excepted
      "Boundary value, small" | 5      | 4.5       || true
      "Boundary value, same"  | 5      | 5         || true
      "Boundary value, big"   | 5      | 5.5       || false

La fin

C'était facile de faire quelque chose comme ça. J'avais l'intention de le terminer dans environ Après tout, c'était très intéressant d'en connaître le contenu et c'était une très bonne occasion d'étudier.

référence

Hibernate Community Documentation

c'est tout

Recommended Posts