[JAVA] Ich möchte eine generische Anmerkung für einen Typ erstellen

Entwicklungsumgebung

Einführung

Verwenden Sie "Bean Validation", um eine Anmerkung zu erstellen, die prüft, ob der eingegebene Wert in der codedefinierten Liste enthalten ist. Die Verwendung ist wie ↓.

Sample.java


public class Sample{

  @CodeExists(StringCode.class)
  private String code;

  :

Erstellen Sie eine Codeklasse

Der Code wird durch "Map" (Codewert, Codename) definiert. Erstellen Sie eine Methode "existiert", die prüft, ob der eingegebene Wert codedefiniert ist. Erstellen Sie außerdem die Codeklasse in einer Tonne, damit sie nicht "neu" ist. Es gibt verschiedene Möglichkeiten, Singleton zu implementieren, aber ich habe die Holder-Klasse unter Bezugnahme auf [Entwurfsmuster "Singleton"] verwendet (https://qiita.com/shoheiyokoyama/items/c16fd547a77773c0ccc1).

StringCode.java



public class StringCode {

  public static final StringCode INSTANCE = StringCodeHolder.INSTANCE;

  private final Map<String, String> codeMap = createCodeMap();

  private StringCode() {}

  public boolean exists(String code) {
    return codeMap.containsKey(code);
  }

  private Map<String, String> createCodeMap() {
    Map<String, String> map = new LinkedHashMap<>();
    map.put("1", "foo");
    map.put("2", "bar");
    map.put("3", "hage");
    return Collections.unmodifiableMap(map);
  }

  private static class StringCodeHolder {
    private static final StringCode INSTANCE = new StringCode();
  }
}

Implementieren Sie die Verarbeitung der Eingabeprüfung

Das Erstellen einer Instanz aus der Klasse des Arguments der Annotation ist unübersichtlich, gibt jedoch das Ergebnis der Methode "existiert" zurück.

CodeExistsValidator.java


public class CodeExistsValidator implements ConstraintValidator<CodeExists, String> {

  StringCode target;

  @Override
  public void initialize(CodeExists constraintAnnotation) {
    try {
      //Ruft eine Instanz der Zielkonstantenklasse ab
      target = (StringCode) (constraintAnnotation.value().getDeclaredField("INSTANCE").get(this));
    } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
        | SecurityException e) {
      //geeignet
      e.printStackTrace();
    }
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    //Gibt das Ergebnis der vorhandenen Methode zurück
    return target.exists(value);
  }
}

Erstellen Sie eine Anmerkung

Hier gibt es nichts Besonderes zu schreiben.

CodeExists.java


@Documented
@Constraint(validatedBy = {CodeExistsValidator.class})
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CodeExists {
  String message() default "";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  Class<?> value();

  @Target({METHOD, FIELD})
  @Retention(RUNTIME)
  @Documented
  public @interface List {
    CodeExists[] value();
  }
}

Anmerkungstest

Lass es uns jetzt testen.

ValidationSampleApplicationTests.java


@RunWith(SpringRunner.class)
@SpringBootTest
public class ValidationSampleApplicationTests {

  private static final String EXIST_CODE = "1";
  private static final String NOT_EXIST_CODE = "5";

  private ValidatorFactory validatorFactory;
  private Validator validator;

  @Before
  public void setup() {
    validatorFactory = Validation.buildDefaultValidatorFactory();
    validator = validatorFactory.getValidator();
  }

  @Test
  public void existStringCode() throws Exception {
    StringCodeSample code = new StringCodeSample(EXIST_CODE);
    Set<ConstraintViolation<StringCodeSample>> result = validator.validate(code);
    assertThat(result.isEmpty(), is(true));
  }

  @Test
  public void notExistStringCode() throws Exception {
    StringCodeSample code = new StringCodeSample(NOT_EXIST_CODE);
    Set<ConstraintViolation<StringCodeSample>> result = validator.validate(code);
    assertThat(result.size(), is(1));
    assertThat(result.iterator().next().getInvalidValue(), is(NOT_EXIST_CODE));
  }

  /**
   *Klasse mit String-Typcode
   */
  private static class StringCodeSample {
    @CodeExists(StringCode.class)
    private String code;

    public StringCodeSample(String code) {
      this.code = code;
    }
  }
}

Sie können sehen, dass die Einstellung "1", die in "StringCode.java" codedefiniert ist, zu einem Eingabeprüfungsfehler von 0 führt, und die Einstellung "5", die nicht codedefiniert ist, zu einem Eingabeprüfungsfehler.

Hauptthema

Die Einführung ist sehr lang geworden, aber ... In ↑ war die Codedefinition auf "String" beschränkt. Nehmen wir jedoch an, dass die Anforderung hinzugefügt werden muss, den Code auch mit den Typen "int" und "boolean" definieren zu können.

Wenden Sie die Schnittstelle auf die Codedefinition an

Erstellen Sie eine Schnittstelle, um eine generische Codedefinitionsklasse zu erstellen. Die Überprüfung der Codeexistenz ist unabhängig vom Typ üblich. Definieren Sie sie daher mit der Standardmethode.

Code.java


public interface Code<T> {

  /**
   *Überprüfen Sie, ob der angegebene Code in der Codeliste vorhanden ist.
   */
  default boolean exists(T code) {
    return asMap().containsKey(code);
  }

  /**
   *Gibt eine Karte zurück, die Codewerte und Codenamen verknüpft.
   */
  Map<T, String> asMap();
}

Korrektur der Code-Definition

Ändern Sie die in ↑ erstellte StringCode.java.

StringCode.java


public class StringCode implements Code<String>{

  public static final StringCode INSTANCE = StringCodeHolder.INSTANCE;

  private final Map<String, String> codeMap = createCodeMap();

  private StringCode() {}

  @Override
  public Map<String, String> asMap() {
    return codeMap;
  }

  private Map<String, String> createCodeMap() {
    Map<String, String> map = new LinkedHashMap<>();
    map.put("1", "foo");
    map.put("2", "bar");
    map.put("3", "hage");
    return Collections.unmodifiableMap(map);
  }

  private static class StringCodeHolder {
    private static final StringCode INSTANCE = new StringCode();
  }
}

Korrektur der Eingabeprüfungsverarbeitung

Ändern Sie die in ↑ erstellte CodeExistsValidator.java.

CodeExistsValidator.java


public class CodeExistsValidator<T> implements ConstraintValidator<CodeExists, T> {

  Code<T> target;

  @Override
  public void initialize(CodeExists constraintAnnotation) {
    try {
      //Ruft eine Instanz der Zielkonstantenklasse ab
      target = convert(constraintAnnotation.value().getDeclaredField("INSTANCE").get(this));
    } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
        | SecurityException e) {
      //geeignet
      e.printStackTrace();
    }
  }

  @Override
  public boolean isValid(T value, ConstraintValidatorContext context) {
    //Gibt das Ergebnis der vorhandenen Methode zurück
    return target.exists(value);
  }

  @SuppressWarnings("unchecked")
  private Code<T> convert(Object o) {
    return (Code<T>) o;
  }
}

Annotation korrigieren

Nachdem wir die Code-Definition und die Eingabeprüfung geändert haben, müssen wir nur noch die Anmerkungen ändern. …… Aber der in @ Constraint angegebene Parameter ist eine echte Klasse, die ConstraintValidator implementiert, so dass es anscheinend nicht möglich ist, formale Argumente einzuschließen. Nun, es ist ein Problem, selbst wenn Sie einen generischen Typ angeben.

CodeExists.java


@Documented
@Constraint(validatedBy = {CodeExistsValidator.class}) // NG
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CodeExists {
  String message() default "";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  Class<? extends Code<?>> value();

  @Target({METHOD, FIELD})
  @Retention(RUNTIME)
  @Documented
  public @interface List {
    CodeExists[] value();
  }
}

Ausgabefehler

Type mismatch: cannot convert from Class<CodeExistsValidator> to Class<? extends ConstraintValidator<?,?>>

Die Eingabeprüfklasse für jeden Typ kann in "@ Constraint" angegeben werden

Es wäre schön gewesen, wenn ich so etwas wie "@ Constraint (validatedBy = {CodeExistsValidator.class})" schreiben könnte, aber es scheint unmöglich ... "Bean Validation" -Dokument 5.7.4. ConstraintValidator-Auflösungsalgorithmus Ich habe beim Lesen festgestellt (: //beanvalidation.org/latest-draft/spec/#constraintdeclarationvalidationprocess-validationroutine-typevalidatorresolution), aber die Eingabeprüfung, die mit T von ConstraintValidator <A, T> übereinstimmt, ist Es scheint einen Mechanismus zu geben, der automatisch ausgeführt wird. Mit anderen Worten, es ist in Ordnung, wenn Sie die Eingabeprüfung für "String" und die Eingabeprüfung für "int" angeben.

Implementieren Sie die Eingabeprüfungsverarbeitung für String

Also werden wir den Eingabeprüfungsprozess für "String" implementieren, der "CodeExistsValidator" erbt. Der Verarbeitungsinhalt ist jedoch bereits in "CodeExistsValidator" implementiert, sodass der Inhalt leer ist.

StringCodeExistsValidator.java


public class StringCodeExistsValidator extends CodeExistsValidator<String> {
}

Geben Sie in der Anmerkung die Implementierungsklasse an.

CodeExists.java


@Documented
@Constraint(validatedBy = {StringCodeExistsValidator.class}) // OK
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CodeExists {
  String message() default "";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  Class<? extends Code<?>> value(); //Nur die Klasse, die Code implementiert

  @Target({METHOD, FIELD})
  @Retention(RUNTIME)
  @Documented
  public @interface List {
    CodeExists[] value();
  }
}

Implementieren Sie die Eingabeprüfungsverarbeitung für int

Erstellen Sie eine Klasse, die eine "Map" mit einem "int" -Typ als Schlüssel zurückgibt. Mit Ausnahme des Typs ist es dasselbe wie "String", daher wird nur ein Teil extrahiert.

IntCode.java


public class IntCode implements Code<Integer> {

 :

  @Override
  public Map<Integer, String> asMap() {
    return codeMap;
  }

  private Map<Integer, String> createCodeMap() {
    Map<Integer, String> map = new LinkedHashMap<>();
    map.put(4, "foo");
    map.put(5, "bar");
    map.put(6, "hage");
    return Collections.unmodifiableMap(map);
  }

 :
}

Der Eingabeprüfungsprozess erstellt nur eine Klasse wie "String" und die Implementierung ist leer. Machen Sie auch den ursprünglichen "CodeExistsValidator" zu einer abstrakten Klasse.

IntCodeExistsValidator.java


public class IntCodeExistsValidator extends CodeExistsValidator<Integer> {
}

Fügen Sie abschließend die Implementierungsklasse zur Annotation hinzu.

CodeExists.java


@Documented
@Constraint(validatedBy = {StringCodeExistsValidator.class, IntCodeExistsValidator.class}) //Hier
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface CodeExists {

 :
}

Fügen Sie in diesem Zustand einen Test hinzu.

ValidationSampleApplicationTests.java


@RunWith(SpringRunner.class)
@SpringBootTest
public class ValidationSampleApplicationTests {

  private static final String STRING_EXIST_CODE = "1";
  private static final String STRING_NOT_EXIST_CODE = "5";
  private static final int INT_EXIST_CODE = 4;
  private static final int INT_NOT_EXIST_CODE = 1;

  private ValidatorFactory validatorFactory;
  private Validator validator;

  @Before
  public void setup() {
    validatorFactory = Validation.buildDefaultValidatorFactory();
    validator = validatorFactory.getValidator();
  }

  @Test
  public void existStringCode() throws Exception {
    StringCodeSample code = new StringCodeSample(STRING_EXIST_CODE);
    Set<ConstraintViolation<StringCodeSample>> result = validator.validate(code);
    assertThat(result.isEmpty(), is(true));
  }

  @Test
  public void notExistStringCode() throws Exception {
    StringCodeSample code = new StringCodeSample(STRING_NOT_EXIST_CODE);
    Set<ConstraintViolation<StringCodeSample>> result = validator.validate(code);
    assertThat(result.size(), is(1));
    assertThat(result.iterator().next().getInvalidValue(), is(STRING_NOT_EXIST_CODE));
  }

  @Test
  public void existIntCode() throws Exception {
    IntCodeSample code = new IntCodeSample(INT_EXIST_CODE);
    Set<ConstraintViolation<IntCodeSample>> result = validator.validate(code);
    assertThat(result.isEmpty(), is(true));
  }

  @Test
  public void notExistIntCode() throws Exception {
    IntCodeSample code = new IntCodeSample(INT_NOT_EXIST_CODE);
    Set<ConstraintViolation<IntCodeSample>> result = validator.validate(code);
    assertThat(result.size(), is(1));
    assertThat(result.iterator().next().getInvalidValue(), is(INT_NOT_EXIST_CODE));
  }

  /**
   *Klasse mit String-Typcode
   */
  private static class StringCodeSample {
    @CodeExists(StringCode.class)
    private String code;

    public StringCodeSample(String code) {
      this.code = code;
    }
  }

  /**
   *Klasse mit int-Typcode
   */
  private static class IntCodeSample {
    @CodeExists(IntCode.class)
    private int code;

    public IntCodeSample(int code) {
      this.code = code;
    }
  }
}

Es wurde bestätigt, dass sich die auf Eingabe zu überprüfende Codedefinition in Abhängigkeit von der in "@ CodeExists" angegebenen Klasse geändert hat. Es ist jedoch unvermeidlich, dass die Anzahl der Klassen mit zunehmender Anzahl von Typen wie "boolean" und "char" zunimmt.

Frage gepostet auf Stack Over Flow: Hibernate Validator - Add a Dynamic ConstraintValidator war ebenfalls hilfreich.

Der Code in diesem Artikel lautet auf GitHub veröffentlicht.

Recommended Posts

Ich möchte eine generische Anmerkung für einen Typ erstellen
[Android] Ich möchte einen ViewPager erstellen, der für Tutorials verwendet werden kann
Ich möchte ein Formular erstellen, um die Kategorie [Schienen] auszuwählen
Ich möchte eine Parkettdatei auch in Ruby erstellen
Ich möchte eine Webanwendung entwickeln!
Ich möchte rekursiv nach Dateien in einem bestimmten Verzeichnis suchen
Ich möchte ein schönes build.gradle schreiben
Ich möchte einen Unit Test schreiben!
So erstellen Sie ein Maven-Repository für 2020
Ich möchte mit Jakarta EE 8 mit Java 11 ein dunkles Web-SNS erstellen
So erstellen Sie überall eine H2-Datenbank
[Ruby] Ich möchte einen Methodensprung machen!
So erstellen Sie Pagenationen für das "Kaminari" -Array
Ich möchte eine einfache Wiederholung einer Zeichenkette schreiben
Ich habe versucht, eine LINE-Klon-App zu erstellen
Ich möchte eine Struktur für die Ausnahmebehandlung entwerfen
Eine Notiz, die ich aufgegeben habe, um eine benutzerdefinierte Anmerkung für Lombok zu erstellen
[Azure] Ich habe versucht, eine Java-App für die Erstellung von kostenlosen Web-Apps zu erstellen. [Anfänger]
Ich möchte eine Methode einer anderen Klasse aufrufen
java: Wie schreibe ich eine generische Typliste? [Hinweis]
Ich möchte ein kleines Symbol in Rails verwenden
[Spring Boot] So erstellen Sie ein Projekt (für Anfänger)
Ich habe versucht, eine Clova-Fähigkeit in Java zu erstellen
Ich möchte eine bestimmte Datei mit WatchService überwachen
Ich möchte eine Funktion in der Rails Console definieren
Ich möchte in RSpec auf einen GoogleMap-Pin klicken
Ich möchte der Kommentarfunktion eine Löschfunktion hinzufügen
[Azure] Ich habe versucht, eine kostenlose Java-App zu erstellen ~ Mit FTP verbinden ~ [Anfänger]
Ich möchte Zeichen konvertieren ...
So erstellen Sie eine Methode
Ich habe versucht, mit Chocolatey eine Java8-Entwicklungsumgebung zu erstellen
Tutorial zum Erstellen eines Blogs mit Rails für Anfänger Teil 1
[Java] Ich möchte ein Byte-Array in eine Hexadezimalzahl konvertieren
Ich möchte einen relativen Pfad in einer Situation finden, in der Pfad verwendet wird
So erstellen Sie ein leichtes Container-Image für Java-Apps
[Rails] Ich habe versucht, eine Mini-App mit FullCalendar zu erstellen
Ich möchte eine Produktinformationsbearbeitungsfunktion ~ part1 ~ implementieren
Ich möchte eine Liste mit Kotlin und Java erstellen!
Ich möchte eine Methode aufrufen und die Nummer zählen
Ich möchte eine Funktion mit Kotlin und Java erstellen!
Selbst in Java möchte ich true mit == 1 && a == 2 && a == 3 ausgeben
Ich habe versucht, in Java von einer Zeichenfolge in einen LocalDate-Typ zu konvertieren
Ich möchte dem select-Attribut einen Klassennamen geben
Ich möchte FireBase verwenden, um eine Zeitleiste wie Twitter anzuzeigen
Ich habe versucht, mit Docker eine Padrino-Entwicklungsumgebung zu erstellen
Ich möchte mehrere Rückgabewerte für das eingegebene Argument zurückgeben
So erstellen und starten Sie eine Docker-Datei für Payara Micro
Tutorial zum Erstellen eines Blogs mit Rails für Anfänger Teil 0
Implementierung einer starken API für "Ich möchte ~~ auf dem Bildschirm anzeigen" mit einfachem CQRS
Ich möchte mit Java8 StreamAPI redu () einen anderen Typ als das Eingabeelement zurückgeben.
Ich habe versucht, eine einfache Karten-App in Android Studio zu erstellen
Ich möchte mit link_to [Hinweis] eine Schaltfläche mit einem Zeilenumbruch erstellen.
Vorbereiten der Erstellung einer Rails-Anwendung
Ich möchte eine Browsing-Funktion mit Ruby on Rails hinzufügen
Ich möchte Swipeback auf einem Bildschirm verwenden, der XLPagerTabStrip verwendet