[JAVA] Ermöglichen, dass beliebige Werte in Bean Validation-Fehlermeldungen aufgerufen werden

Entwicklungsumgebung

Einführung

Verwendung der Bean-Validierung

Wenn Sie die Eingabe mit "Java" überprüfen möchten, ist dies "Bean Validation". Es ist einfach zu bedienen und bequem, da nur eine Anmerkung hinzugefügt wird: smile :. Erstellen wir nun eine Anmerkung, die prüft, ob der Eigenschaftswert in dem als Argument übergebenen Array von Zeichenfolgen enthalten ist, wie unten gezeigt.

AcceptedStringValues.java


@Documented
@Constraint(validatedBy = {AcceptedStringValuesValidator.class})
@Target({METHOD, FIELD})
@Retention(RUNTIME)
public @interface AcceptedStringValues {
  String message() default "{com.neriudon.example.validator.AcceptedStringValues.message}";

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

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

  String[] value();

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

Validator prüft, ob die an value () der Annotation AcceptedStringValues übergebene Zeichenfolge den Rückgabewert des mit Annotationen versehenen Felds oder der Methode enthält.

AcceptedStringValuesValidator.java


public class AcceptedStringValuesValidator
    implements ConstraintValidator<AcceptedStringValues, String> {

  // accepted values array
  private String[] validValues;

  @Override
  public void initialize(AcceptedStringValues constraintAnnotation) {
    validValues = constraintAnnotation.value();
  }

  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    if (value == null) {
      return true;
    }
    // check to exist value or not in accepted values array
    return Arrays.stream(validValues).anyMatch(s -> Objects.equals(value, s));
  }
}

Der Testcode sieht aus wie ↓.

ValidationSampleApplicationTests.java


public class ValidationSampleApplicationTests {

  private ValidatorFactory validatorFactory;
  private Validator validator;

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

  @Test
  public void acceptedStringValuesNormal() throws UnsupportedEncodingException {
    AcceptedStringValuesSample sample = new AcceptedStringValuesSample("1");
    Set<ConstraintViolation<AcceptedStringValuesSample>> result = validator.validate(sample);
    // no error
    assertThat(result.isEmpty(), is(true));
  }

  @Test
  public void acceptedStringValuesNg() throws Exception {
    AcceptedStringValuesSample sample = new AcceptedStringValuesSample("0");
    Set<ConstraintViolation<AcceptedStringValuesSample>> result = validator.validate(sample);
    // error
    assertThat(result.size(), is(1));
    // assert error value and message
    result.stream().forEach(r -> {
      assertThat(r.getInvalidValue(), is("0"));
      assertThat(r.getMessage(), is("not accepted value."));
    });
  }

  private static class AcceptedStringValuesSample {

    @AcceptedStringValues({"1", "2", "3", "4", "5"})
    private String code;

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

Sie wissen, dass Fehlermeldungen automatisch aufgerufen werden, wenn Sie "ValidationMessages.properties" unter dem Klassenpfad erstellen und die Nachricht mit dem in "message" der Annotation-Klasse angegebenen Wert als Schlüssel festlegen. In diesem Beispiel sieht es wie ↓ aus.

ValidationMessages.properties


com.neriudon.example.validator.AcceptedStringValues.message = not accepted values.

Wert in Fehlermeldung einbetten

Es mag wie ↑ aussehen, aber wenn die Nachricht behoben ist, kann es für den Benutzer schwierig sein, sie zu verstehen. In "Bean Validation" kann der Eigenschaftswert der Anmerkungsklasse in die Fehlermeldung eingebettet werden. Versuchen Sie daher, den im Wert der Anmerkungsklasse in "{value}" festgelegten Wert als Zeichenfolge auszugeben.

ValidationMessages.properties


com.neriudon.example.validator.AcceptedStringValues.message = not contained accepted values: {value}.

Es wird die Meldung "Nicht akzeptierte Werte enthalten: [1, 2, 3, 4, 5]" angezeigt.

Machen Sie Fehlermeldungen intelligenter

Bean Validation unterstützt EL-Ausdrücke ab 1.1! Durch Angabe von $ {validatedValue} können Sie das Objekt einbetten, das den Fehler verursacht hat ...

ValidationMessages.properties


com.neriudon.example.validator.AcceptedStringValues.message = ${validatedValue} is not contained accepted values.

Dies führt zu der Meldung "xxx enthält keine akzeptierten Werte". (XXX ist das Objekt, das den Fehler verursacht hat)

** $ {validatedValue} `gibt das fehlerhafte Objekt jedoch so aus, wie es ist. Verwenden Sie es daher nicht, wenn Sie vertrauliche Informationen wie Kennwörter verarbeiten. ** **.

Problem, dass andere Arrays nicht eingebettet werden können

Nun, das Hauptthema ist von hier. Bisher haben wir uns mit "String" befasst, aber wir werden mit "Integer" eine Annotation mit derselben Funktion erstellen.

AcceptedIntegerValues.java


@Documented
@Constraint(validatedBy = { AcceptedIntegerValuesValidator.class })
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface AcceptedIntegerValues {
	String message() default "{com.neriudon.example.validator.AcceptedIntegerValues.message}";

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

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

	int[] value();

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

AcceptedIntegerValuesValidator.java


public class AcceptedIntegerValuesValidator implements ConstraintValidator<AcceptedIntegerValues, Integer> {

	// accepted values array
	private Integer[] validValues;

	@Override
	public void initialize(AcceptedIntegerValues constraintAnnotation) {
		validValues = ArrayUtils.toObject(constraintAnnotation.value());
	}

	@Override
	public boolean isValid(Integer value, ConstraintValidatorContext context) {
		if (value == null) {
			return true;
		}
		// check to exist value or not in accepted values array
		return Arrays.stream(validValues).anyMatch(s -> Objects.equals(value, s));
	}
}

Fehlermeldung einstellen ...

ValidationMessages.properties


com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: {value}.

Testen Sie jetzt

TestCode.java


	@Test
	public void acceptedIntegerValuesNormal() {
		AcceptedIntegerValuesSample sample = new AcceptedIntegerValuesSample(1);
		Set<ConstraintViolation<AcceptedIntegerValuesSample>> result = validator.validate(sample);
		assertThat(result.isEmpty(), is(true));
	}

	@Test
	public void acceptedIntegerValuesNg() {
		AcceptedIntegerValuesSample sample = new AcceptedIntegerValuesSample(0);
		Set<ConstraintViolation<AcceptedIntegerValuesSample>> result = validator.validate(sample);
		assertThat(result.size(), is(1));
		result.stream().forEach(r -> {
			assertThat(r.getInvalidValue(), is(0));
		});
	}
	private static class AcceptedIntegerValuesSample {

		@AcceptedIntegerValues({ 1, 2, 3, 4, 5 })
		private int code;

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

Dann ...

javax.validation.ValidationException: HV000149: An exception occurred during message interpolation
	at org.hibernate.validator.internal.engine.ValidationContext.interpolate(ValidationContext.java:477)
	at org.hibernate.validator.internal.engine.ValidationContext.createConstraintViolation(ValidationContext.java:322)
	at org.hibernate.validator.internal.engine.ValidationContext.lambda$createConstraintViolations$0(ValidationContext.java:279)
	at java.util.stream.ReferencePipeline$3$1.accept(Unknown Source)
	at java.util.Collections$2.tryAdvance(Unknown Source)
	at java.util.Collections$2.forEachRemaining(Unknown Source)
	at java.util.stream.AbstractPipeline.copyInto(Unknown Source)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(Unknown Source)
	at java.util.stream.AbstractPipeline.evaluate(Unknown Source)
	at java.util.stream.ReferencePipeline.collect(Unknown Source)
	at org.hibernate.validator.internal.engine.ValidationContext.createConstraintViolations(ValidationContext.java:280)
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateSingleConstraint(ConstraintTree.java:182)
	at org.hibernate.validator.internal.engine.constraintvalidation.SimpleConstraintTree.validateConstraints(SimpleConstraintTree.java:68)
	at org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree.validateConstraints(ConstraintTree.java:73)
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.doValidateConstraint(MetaConstraint.java:127)
	at org.hibernate.validator.internal.metadata.core.MetaConstraint.validateConstraint(MetaConstraint.java:120)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateMetaConstraint(ValidatorImpl.java:533)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForSingleDefaultGroupElement(ValidatorImpl.java:496)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForDefaultGroup(ValidatorImpl.java:465)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateConstraintsForCurrentGroup(ValidatorImpl.java:430)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validateInContext(ValidatorImpl.java:380)
	at org.hibernate.validator.internal.engine.ValidatorImpl.validate(ValidatorImpl.java:169)
	at com.neriudon.example.ValidationSampleApplicationTests.acceptedIntegerValuesNg(ValidationSampleApplicationTests.java:98)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)
Caused by: java.lang.ClassCastException: [I cannot be cast to [Ljava.lang.Object;
	at org.hibernate.validator.internal.engine.messageinterpolation.ParameterTermResolver.interpolate(ParameterTermResolver.java:30)
	at org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm.interpolate(InterpolationTerm.java:64)
	at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.interpolate(ResourceBundleMessageInterpolator.java:76)
	at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateExpression(AbstractMessageInterpolator.java:385)
	at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolateMessage(AbstractMessageInterpolator.java:274)
	at org.hibernate.validator.messageinterpolation.AbstractMessageInterpolator.interpolate(AbstractMessageInterpolator.java:220)
	at org.hibernate.validator.internal.engine.ValidationContext.interpolate(ValidationContext.java:468)
	... 55 more

Etwas ist schief gelaufen: müde :. Es scheint, dass das Casting aus dem Array von "Integer" von "" [Ich kann nicht in [Ljava.lang.Object; `" "fehlgeschlagen ist.

Versuchen wir, es mit einem EL-Ausdruck zu einem String zu machen.

ValidationMessages.properties


com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: ${value.toString()}.

Als ich es damit getestet habe, habe ich keinen Fehler erhalten, aber es sieht so aus:

not contained accepted values: [I@6e9319f. //Integer[]Auflage
not contained accepted values: [1, 2, 3, 4, 5]. // String[]Auflage

Also konvertieren wir "Integer []" mit "Arrays.toString" in "String".

ValidationMessages.properties


com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: ${java.util.Arrays.toString(value)}.

Also, wenn Sie es ausführen ...

javax.el.PropertyNotFoundException: ELResolver cannot handle a null base Object with identifier [java]

Es gibt kein Objekt wie Java! Ich habe den Fehler bekommen: enttäuscht_relieved :.

Zurück zum Anfang: [EL-Ausdruck der Fehlermeldung von Hibernate Validator 5](http://docs.jboss.org/hibernate/validator/5.2/reference/en-US/html/ch04.html#section-interpolation- Lesen wir über (mit Nachrichtenausdrücken). Dann ...

The validation engine makes the following objects available in the EL context:

the attribute values of the constraint mapped to the attribute names the currently validated value (property, bean, method parameter etc.) under the name validatedValue a bean mapped to the name formatter exposing the var-arg method format(String format, Object…​ args) which behaves like java.util.Formatter.format(String format, Object…​ args).

Wenn Sie es frei übersetzen ...

Die Validierungs-Engine kann die folgenden Objekte in EL-Ausdrücken verwenden.

  1. In der Anmerkung festgelegter Attributwert
  2. Der Wert, der den Fehler tatsächlich verursacht hat
  3. java.util.Formatter.format (String format, Object… args) basierte Formatierung

Mit anderen Worten, nichts anderes kann verwendet werden ...? Selbst wenn Sie sich auf das Beispiel unter dem Link beziehen, haben Sie die von Java bereitgestellte Klasse nicht aufgerufen, um die Nachricht zu verarbeiten. / (^ O ^) \ Nantekotai.

Ermöglicht das Aufrufen beliebiger Werte in Fehlermeldungen

Aber es ist nicht ohne Weg. In den Tipps unter dem Beispiel ist es wie folgt geschrieben.

Only actual constraint attributes can be interpolated using message parameters in the form {attributeName}. When referring to the validated value or custom expression variables added to the interpolation context (see Section 11.9.1, “HibernateConstraintValidatorContext”), an EL expression in the form ${attributeName} must be used.

Wenn Sie es frei übersetzen ... Wenn Sie den Wert festlegen, den Sie für den Kontext verwenden möchten, können Sie mit $ {} darauf verweisen!

Und das. Der Satz basiert auf dem in 11.9.1. HibernateConstraintValidatorContext geschriebenen Beispiel. Lass es uns versuchen.

Fügen Sie den Prozess der Konvertierung von "Interger []" in "String" hinzu und speichern Sie ihn im Kontext als "acceptValuesToString" zur Methode "isValid".

AcceptedIntegerValuesValidator.java


	@Override
	public boolean isValid(Integer value, ConstraintValidatorContext context) {
		if (value == null) {
			return true;
		}
		// add acceptedValuesToString variable converted accepted integer values to string 
		context.unwrap(HibernateConstraintValidatorContext.class).addExpressionVariable("acceptedValuesToString", Arrays.toString(validValues));

		// check to exist value or not in accepted values array
		return Arrays.stream(validValues).anyMatch(s -> Objects.equals(value, s));
	}

Wenn Sie also "$ {acceptValuesToString}" in "ValidationMessages.properties" aufrufen ...

ValidationMessages.properties


com.neriudon.example.validator.AcceptedIntegerValues.message = not contained accepted values: ${acceptedValuesToString}.

Ich habe die Fehlermeldung "nicht akzeptierte Werte enthalten: [1, 2, 3, 4, 5]" erhalten. Ich habe es getan: stecken_out_tongue_closed_eyes :!

Zusammenfassung

In diesem Artikel habe ich Ihnen gezeigt, wie Sie Bean-Validierungsfehlermeldungen erlauben, beliebige Werte aufzurufen. Bei dieser Methode gibt es jedoch zwei Probleme.

Da die Standardvalidierungsfunktion erweitert ist, gibt es kein Problem, wenn Sie sie einzeln entwickeln. Wenn Sie sie jedoch beispielsweise als Bibliothek veröffentlichen, müssen Sie beschreiben, welche Werte in JavaDoc usw. verwendet werden können. ..

~~ Diese Funktion wurde in WARN im Abschnitt HibernateConstraintValidatorContext geschrieben und kann sich in Zukunft ändern. ~~

~~ Wie die Person, die den Artikel geschrieben hat, sagt, sollte diese Funktion nicht zu oft verwendet werden: rollende_Augen :. ~~

In Hibernate Validator 6.0.13.Final wurde die obige Warnung entfernt. War es behoben?

Wenn sich Ihr Projekt für die Verwendung des "Hibernate Validator" entscheidet, ist diese Funktion nützlich (welche).

Recommended Posts

Ermöglichen, dass beliebige Werte in Bean Validation-Fehlermeldungen aufgerufen werden
So zeigen Sie Fehlermeldungen auf Japanisch an
Wenn Sie in der Bean-Validierung den Feldnamen in die Fehlermeldung aufnehmen möchten, können Sie keine Ausgabe von LocalValidatorFactoryBean ausführen.
[Rails] Bei der Beschreibung der Validierung ist Vorsicht geboten
[Spring Boot] Liste der Validierungsregeln, die in der Eigenschaftendatei für Fehlermeldungen verwendet werden können
[Rails] Unerwarteter Validierungsfehler im Gerät