[JAVA] Legen Sie die @ Max-Parameter in den Eigenschaften fest

Entwicklungsumgebung

Einführung

Weiter vom letzten Mal sprechen wir über "BeanValidation". Diesmal haben wir ein Spring-Element: Ghost :. Varidaten, die numerische Werte wie "@ Max" und "@ Min" validieren, die von "BeanValidation" erstellt wurden, werden vorbereitet. Da "value" jedoch durch "long" definiert ist, werden diese als Argumente verwendet. Es können nur Ganzzahlen angegeben werden.

Es ist natürlich, weil wir den numerischen Wert überprüfen, aber vielleicht möchten wir den zulässigen Wert abhängig von der Umgebung ändern. Es kann eine solche Szene geben. In einem solchen Fall ist es zweckmäßig, auf den Wert aus der Eigenschaft verweisen zu können. Daher möchte ich eine Annotation "@ MaxFromProperty" erstellen, die sich auch auf den in der Eigenschaft festgelegten Wert beziehen kann.

Dieses Ziel ↓

Zulässige Werte einstellen


age.max=100

Überprüfen Sie den numerischen Wert anhand der Eigenschaft


@MaxFromProperty("age.max")
private long age; //Überprüfen Sie, ob es 100 oder weniger ist

Sie können den Wert direkt angeben


@MaxFromProperty("20")
private long age; //Überprüfen Sie, ob es 20 oder weniger ist

Am Ende dieses Artikels habe ich einen Link zur Quelle der fertigen Version eingefügt, aber dies ist nicht die optimale Lösung. Es gibt also einen besseren Weg! Wir suchen Meinungen von denen, die sagen: bow_tone1:

Machen Sie eine Anmerkung

Jetzt erstellen wir eine Anmerkung. Die Anmerkung selbst ist jedoch nichts Besonderes. Reicht es aus, den Wert auf "String" zu setzen, damit er den Eigenschaftsschlüssel empfangen kann?

MaxFromProperty.java


@Documented
@Constraint(validatedBy = { MaxFromPropertyValidator.class })
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface MaxFromProperty {

	String message() default "{com.neriudon.example.validator.MaxFromProperty.message}";

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

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

	String value();

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

Machen Sie Bali Daten

Das ist wichtig.

Eigenschaftswert abrufen

Ich benutze PropertyResolver, um Eigenschaftswerte aufzulösen, aber ich brauchePropertySources als Argument. Darüber hinaus werden Eigenschaftswerte von applyPropertySources in "PropertySourcesPlaceholderConfigurer" verwaltet. Also werde ich den Code nur in den wichtigen Teil setzen

MaxFromPropertyValidator.java



//Resolver im Konstruktor generieren
public MaxFromPropertyValidator(PropertySourcesPlaceholderConfigurer configurer) {
	//Holen Sie sich einen Resolver, der einen Eigenschaftswert auflöst
	resolver = new PropertySourcesPropertyResolver(configurer.getAppliedPropertySources());
}

@Override
public void initialize(MaxFromProperty constraintAnnotation) {
	max = getMaxValue(constraintAnnotation.value());
}

private long getMaxValue(String key) {
	//Auflösung des Eigenschaftswerts
	//Das zweite Argument ist der Standardwert
	String value = resolver.getProperty(key, key);
	try {
		//In Nummer umwandeln
		return Long.parseLong(value);
	} catch (NumberFormatException e) {
		//Ausnahmebehandlung
	}
}

Okay: v :. Ich würde es gerne tun ...: Schweiß:

Entspricht mehreren "PropertySourcesPlaceholderConfigurer"

Abhängig von der Anwendung können mehrere "PropertySourcesPlaceholderConfigurer" definiert werden. Daher müssen die AppliedPropertySources jedes "PropertySourcesPlaceholderConfigurer" zu einem "PropertySources" kombiniert werden.

Da "MutablePropertySources", die Standardimplementierung von "PropertySources", mehrere "PropertySource" in einer Liste verwaltet, fügen Sie den Inhalt von "AppliedPropertySources" jedes "PropertySourcesPlaceholderConfigurer" in "MutablePropertySources" ein.

Geänderte Version des MaxFromPropertyValidator-Konstruktors


public MaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers) {
	MutablePropertySources propertySources = new MutablePropertySources();
	configurers.forEach(c -> c.getAppliedPropertySources().forEach(p -> {
		propertySources.addLast(temp);
	}));
	this.resolver = new PropertySourcesPropertyResolver(propertySources);
}

Das ist okay: v :. Ich würde es gerne tun ...: streng:

Wenn Sie den Vorgang tatsächlich mit "PropertySourcesPlaceholderConfigurer" überprüfen, der in mehreren Beans definiert ist, wird der erwartete Vorgang möglicherweise nicht ausgeführt.

weil

  1. PropertySource überschreibt equals () (https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/ core / env / PropertySource.java # L135-L138) und wenn die Namen von "PropertySource" identisch sind, wird "true" zurückgegeben.
  2. PropertySourcesPlaceholderConfigurer Die Systemeigenschaft lautet "environmentProperties", und die aus der Eigenschaftendatei gelesene Eigenschaft (= lokale Eigenschaft) lautet "localProperties", und der Name ist festgelegt.
  3. [MutablePropertySources](https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java## Beim Hinzufügen von "PropertySource" vergleicht L104-L107) die bereits hinzugefügte "PropertySource" mit "equals ()". Wenn sie "true" ist, löschen Sie die bereits hinzugefügte "PropertySource". , PropertySource wird hinzugefügt.

Mit anderen Worten, diese Implementierung fügt nur den Inhalt des zuletzt geladenen "PropertySourcesPlaceholderConfigurer" zu "MutablePropertySources" hinzu: confounded :.

Also ... geben Sie ihm zwangsweise einen eindeutigen Namen und fügen Sie ihn zu "MutablePropertySources" hinzu. Leider hatte "PropertySource" keine Methode zum Überschreiben eines Namens wie "setName ()". Füllen Sie ihn daher mit einem Alias für "CompositePropertySource" und fügen Sie ihn dann zu "MutablePropertySources" hinzu.

Geänderte Version des Konstruktors


public MaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers) {
	MutablePropertySources propertySources = new MutablePropertySources();
	configurers.forEach(c -> c.getAppliedPropertySources().forEach(p -> {
	//Füllen Sie eine eindeutig benannte CompositePropertySource nach und fügen Sie sie MutablePropertySources hinzu
	CompositePropertySource temp = new CompositePropertySource(
		c.toString().concat("$").concat(p.getName()));
			temp.addPropertySource(p);
			propertySources.addLast(temp);
		}));
	this.resolver = new PropertySourcesPropertyResolver(propertySources);

Hier gibt es Raum für Verbesserungen, obwohl ich es selbst sage: unschuldig :.

Übrigens, wenn es Beans von "PropertySourcesPlaceholderConfigurer", "configurationer1" und "configurationer2" gibt und der Wert des Attributs "order" "1" bzw. "2" ist und der Wert des Attributs "localOverride" "false" ist, "MutablePropertySources" Zu

  1. Systemeigenschaften von configure1
  2. Lokale Eigenschaften von configure1
  3. Systemeigenschaften von configure2
  4. Lokale Eigenschaften von configure2

Da sie in der Reihenfolge von hinzugefügt werden, werden die Systemeigenschaften dupliziert, aber ich denke, es ist schwierig, sie zu verbessern, während die Konsistenz mit den Einstellungen von "order" und "localOverride" erhalten bleibt.

Stellen Sie sich den Fall vor, in dem es überhaupt keinen "PropertySourcesPlaceholderConfigurer" gibt

Bisher haben wir angenommen, dass PropertySourcesPlaceholderConfigurer Bean-definiert ist, aber ich denke, dass einige Anwendungen möglicherweise nicht Bean-definiert sind. In diesem Fall setzen Sie Environment, weil Environment eine Subschnittstelle von PropertyResolver ist.

Geänderte Version des Konstruktors


public MaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers, Environment environment) {
	if (configurers != null) {
		//Abkürzung
	} else {
		this.resolver = environment;
	}
}

Verwenden Sie den Original-MaxValidator als Referenz ...

Nachdem der Vorgang zum Abrufen des Werts aus der Eigenschaft abgeschlossen ist, müssen Sie nur noch den zu überprüfenden Wert mit dem Maximalwert vergleichen. Die Validator-Klasse "@ Max" im ursprünglichen "Hibernate-Validator" unterstützt Zahlen, die in den Typen "Number" und "CharSequence" ausgedrückt werden.

Dies ist eine persönliche Präferenz, aber es ist schwer zu verstehen, ob es mehrere Java-Dateien der Validator-Klasse gibt. Machen Sie "MaxFromPropertyValidator" zu einer abstrakten Klasse und erben Sie sie an die inneren Klassen für "Number" und "CharSequence". Zu diesem Zeitpunkt kann die Klasse für "Number" und "CharSequence" numerische Werte vergleichen, indem die "isValid" -Methode mit "MaxFromPropertyValidator" überschrieben und nur eine Nullprüfung durchgeführt wird, indem ein konkreter numerischer Vergleich zu einer abstrakten Methode gemacht wird. Sie können sich darauf konzentrieren und das von "MaxFromPropertyValidator" erhaltene Maximalwert-Qualifikationsmerkmal auf "privat" setzen. Schließlich gibt die in "@ Constraint" der Annotation-Klasse angegebene Klasse auch sowohl für "Number" als auch für "CharSequence" an.

Bitte lesen Sie die Quelle von ↓, da es schwer zu verstehen ist, ob es sich um einen Satz handelt: stecken_out_tongue_closed_eyes:

Abgeschlossene Version

Anmerkungsklasse

MaxFromProperty.java


@Documented
@Constraint(validatedBy = { MaxFromPropertyValidator.NumberMaxFromPropertyValidator.class,
		MaxFromPropertyValidator.CharSequenceMaxFromPropertyValidator.class })
@Target({ METHOD, FIELD })
@Retention(RUNTIME)
public @interface MaxFromProperty {

	String message() default "{com.neriudon.example.validator.MaxFromProperty.message}";

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

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

	String value();

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

Varidata-Klasse

MaxFromPropertyValidator.java


public abstract class MaxFromPropertyValidator<T> extends ApplicationObjectSupport
		implements ConstraintValidator<MaxFromProperty, T> {

	private long max;

	private final PropertyResolver resolver;

	public MaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers, Environment environment) {
		if (configurers != null) {
			MutablePropertySources propertySources = new MutablePropertySources();
			configurers.forEach(c -> c.getAppliedPropertySources().forEach(p -> {
				// named unique name.
				// Because MutablePropertySources override propertySources if defined same name.
				CompositePropertySource temp = new CompositePropertySource(
						c.toString().concat("$").concat(p.getName()));
				temp.addPropertySource(p);
				propertySources.addLast(temp);
			}));
			this.resolver = new PropertySourcesPropertyResolver(propertySources);
		} else {
			this.resolver = environment;
		}
	}

	@Override
	public void initialize(MaxFromProperty constraintAnnotation) {
		max = getMaxValue(constraintAnnotation.value());
	}

	@Override
	public boolean isValid(T value, ConstraintValidatorContext context) {
		// null values are valid
		if (value == null) {
			return true;
		}
		return compareToMaxValue(value, max);
	}

	/**
	 * compare target value to maximum value
	 * 
	 * @param value
	 *            target value
	 * @param max
	 *            maximum value
	 * @return true if value is less than or equal to max.
	 */
	protected abstract boolean compareToMaxValue(T value, long max);

	/**
	 * return max value.<br>
	 * if no value mapped key, convert key to long.
	 */
	private long getMaxValue(String key) {
		String value = resolver.getProperty(key, key);
		try {
			return Long.parseLong(value);
		} catch (NumberFormatException e) {
			throw new IllegalArgumentException(
					"failed to get int value from Property(key:" + key + ", value:" + value + ")");
		}
	}

	/**
	 * MaxFromPropertyValidator for Number
	 */
	public static class NumberMaxFromPropertyValidator extends MaxFromPropertyValidator<Number> {

		public NumberMaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers,
				Environment environment) {
			super(configurers, environment);
		}

		@Override
		public boolean compareToMaxValue(Number value, long max) {
			// handling of NaN, positive infinity and negative infinity
			if (value instanceof Double) {
				if ((Double) value == Double.NEGATIVE_INFINITY) {
					return true;
				} else if (Double.isNaN((Double) value) || (Double) value == Double.POSITIVE_INFINITY) {
					return false;
				}
			} else if (value instanceof Float) {
				if ((Float) value == Float.NEGATIVE_INFINITY) {
					return true;
				} else if (Float.isNaN((Float) value) || (Float) value == Float.POSITIVE_INFINITY) {
					return false;
				}
			}
			if (value instanceof BigDecimal) {
				return ((BigDecimal) value).compareTo(BigDecimal.valueOf(max)) != 1;
			} else if (value instanceof BigInteger) {
				return ((BigInteger) value).compareTo(BigInteger.valueOf(max)) != 1;
			} else {
				long longValue = value.longValue();
				return longValue <= max;
			}
		}
	}

	/**
	 * MaxFromPropertyValidator for CharSequernce
	 */
	public static class CharSequenceMaxFromPropertyValidator extends MaxFromPropertyValidator<CharSequence> {

		public CharSequenceMaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers,
				Environment environment) {
			super(configurers, environment);
		}

		@Override
		public boolean compareToMaxValue(CharSequence value, long max) {
			try {
				return new BigDecimal(value.toString()).compareTo(BigDecimal.valueOf(max)) != 1;
			} catch (NumberFormatException nfe) {
				return false;
			}
		}
	}
}

Versuche zu testen

Zur Klarheit

Ist geteilt.

@RunWith(SpringRunner.class)
@SpringBootTest
public class ValidationSampleApplicationTests {
	@Autowired
	private Validator validator;

	@Test
	public void maxFromPropertySampleNg() {
		MaxFromPropertySample sample = new MaxFromPropertySample(100);
		Set<ConstraintViolation<MaxFromPropertySample>> result = validator.validate(sample);
		assertThat(result.size(), is(1));
		result.stream().forEach(r -> {
			assertThat(r.getInvalidValue(), is(100L));
		});
	}

	@Test
	public void maxValueSetDirectlyNg() {
		MaxValueSetDirectly sample = new MaxValueSetDirectly(100);
		Set<ConstraintViolation<MaxValueSetDirectly>> result = validator.validate(sample);
		assertThat(result.size(), is(1));
		result.stream().forEach(r -> {
			assertThat(r.getInvalidValue(), is(100L));
		});
	}

	@Test
	public void maxFromPropertyForCharSequenceNg() {
		MaxFromPropertyForCharSequence sample = new MaxFromPropertyForCharSequence("100");
		Set<ConstraintViolation<MaxFromPropertyForCharSequence>> result = validator.validate(sample);
		assertThat(result.size(), is(1));
		result.stream().forEach(r -> {
			assertThat(r.getInvalidValue(), is("100"));
		});
	}
	//Geben Sie einen numerischen Wert aus der Eigenschaftendatei an
	private static class MaxFromPropertySample {

		@MaxFromProperty("max")
		private long value;

		public MaxFromPropertySample(long value) {
			this.value = value;
		}
	}
	//Geben Sie den numerischen Wert direkt an
	private static class MaxValueSetDirectly {

		@MaxFromProperty("99")
		private long value;

		public MaxValueSetDirectly(long value) {
			this.value = value;
		}
	}
	//Sie können auch die Nummern der Char Sequence-Typen überprüfen
	private static class MaxFromPropertyForCharSequence {

		@MaxFromProperty("max")
		private CharSequence value;

		public MaxFromPropertyForCharSequence(CharSequence value) {
			this.value = value;
		}
	}
}

JavaConfig-Klasse

TestConfig.java


@Configuration
@PropertySource("sample.properties")
public class TestConfig {

	@Bean
	public Validator getValidator() {
		return new LocalValidatorFactoryBean();
	}
}

Platzieren Sie sample.properties direkt unter dem Klassenpfad.

sample.properties


max=99

abschließend

Holen Sie sich einfach den Wert von der Immobilie. Ich habe versucht, es zu schaffen, aber es war schwer, weil ich viele Gedanken hatte. Da der Spring-Mechanismus zum Auflösen von Eigenschaftswerten verwendet wird, funktionieren in reinem Java erstellte Anmerkungen nicht, aber ich denke, es besteht Bedarf, daher hoffe ich, dass Hibernate ihn eines Tages bereitstellen wird.

Übrigens kann der Teil, der "PropertyResolver" in der Validator-Klasse festlegt, für allgemeine Zwecke verwendet werden, sodass er auf die gleiche Weise auf "@ Min" und "@ Size" erweitert werden kann.

Der diesmal erstellte Code ist in GitHub aufgeführt, stellt jedoch nicht die optimale Lösung dar, wie im Artikel beschrieben. Seien Sie also vorsichtig, wenn Sie darauf verweisen: stick_out_tongue_winking_eye :.

Recommended Posts

Legen Sie die @ Max-Parameter in den Eigenschaften fest
Übergeben von Parametern aus JSP mit Servlet