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;
:
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();
}
}
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);
}
}
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();
}
}
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.
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.
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();
}
Ändern Sie die in ↑ erstellte StringCode.java
.
Code <String>
codeMap
mit asMap
zurückStringCode.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();
}
}
Ä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;
}
}
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<?,?>>
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.
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();
}
}
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.