[JAVA] Définir les paramètres @Max à partir des propriétés

Environnement de développement

introduction

Dans la continuité de la dernière fois, nous parlons de «BeanValidation». Cette fois, nous avons un élément Spring: ghost:. Les variables qui valident les valeurs numériques telles que «@ Max» et «@ Min» préparées par «BeanValidation» sont préparées, mais comme «valeur» est définie par «long», elles sont utilisées comme arguments. Seuls des entiers peuvent être spécifiés.

C'est naturel parce que nous vérifions la valeur numérique, mais peut-être que nous voulons changer la valeur autorisée en fonction de l'environnement. Il peut y avoir une scène comme celle-là. Dans un tel cas, il est pratique de pouvoir se référer à la valeur de la propriété. Donc, je voudrais créer une annotation @ MaxFromProperty qui peut également faire référence à la valeur définie dans la propriété.

Cet objectif ↓

Définition des valeurs autorisées


age.max=100

Vérifiez la valeur numérique en vous référant à la propriété


@MaxFromProperty("age.max")
private long age; //Vérifiez s'il est égal ou inférieur à 100

Vous pouvez spécifier la valeur directement


@MaxFromProperty("20")
private long age; //Vérifiez si c'est 20 ou moins

A la fin de cet article, je mets un lien vers la source de la version terminée, mais ce n'est pas la solution optimale. Il y a donc un meilleur moyen! Nous recherchons les opinions de ceux qui disent: bow_tone1:

Faire une annotation

Maintenant, créons une annotation. Cependant, il n'y a rien de spécial dans l'annotation elle-même. Est-il suffisant de définir la valeur sur String pour qu'il puisse recevoir la clé de propriété?

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();
	}
}

Faire des données de Bali

C'est important.

Obtenir la valeur de la propriété

J'utilise PropertyResolver pour résoudre les valeurs de propriété, mais j'ai besoin dePropertySources comme argument. De plus, les valeurs de propriété sont gérées par applyPropertySources dans PropertySourcesPlaceholderConfigurer. Je mettrai donc le code uniquement dans la partie importante

MaxFromPropertyValidator.java



//Générer un résolveur dans le constructeur
public MaxFromPropertyValidator(PropertySourcesPlaceholderConfigurer configurer) {
	//Obtenez un résolveur qui résout une valeur de propriété
	resolver = new PropertySourcesPropertyResolver(configurer.getAppliedPropertySources());
}

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

private long getMaxValue(String key) {
	//Résolution de la valeur de la propriété
	//Le deuxième argument est la valeur par défaut
	String value = resolver.getProperty(key, key);
	try {
		//Convertir en nombre
		return Long.parseLong(value);
	} catch (NumberFormatException e) {
		//Gestion des exceptions
	}
}

D'accord: v:. J'aimerais le faire ...: suer:

Correspond à plusieurs PropertySourcesPlaceholderConfigurer

Selon l'application, plusieurs PropertySourcesPlaceholderConfigurers peuvent être définis, il est donc nécessaire de combiner les appliquésPropertySources de chaque PropertySourcesPlaceholderConfigurer en un seul PropertySources.

Ainsi, étant donné que «MutablePropertySources», qui est l'implémentation par défaut de «PropertySources», gère plusieurs «PropertySource» dans une liste, insérez le contenu de applyPropertySources de chaque «PropertySourcesPlaceholderConfigurer» dans «MutablePropertySources».

Version modifiée du constructeur MaxFromPropertyValidator


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

Ce n'est pas grave: v:. J'aimerais le faire ...: sévère:

Si vous vérifiez réellement l'opération avec PropertySourcesPlaceholderConfigurer défini dans plusieurs beans, l'opération attendue peut ne pas être réalisée.

Parce que

  1. PropertySource remplace ʻequals ()[https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/ core / env / PropertySource.java # L135-L138) et si les noms dePropertySource sont identiques, true` est renvoyé.
  2. PropertySourcesPlaceholderConfigurer La propriété système est ʻenvironmentProperties, et la propriété (= propriété locale) lue à partir du fichier de propriétés est localProperties` et le nom est fixe.
  3. [MutablePropertySources](https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/core/env/MutablePropertySources.java# Lors de l'ajout de PropertySource, L104-L107) compare la PropertySource déjà ajoutée avec ʻequals (), et si elle est true, supprimez la PropertySourcedéjà ajoutée. ,PropertySource` est ajouté.

Cela signifie que cette implémentation ajoutera uniquement le contenu du dernier PropertySourcesPlaceholderConfigurer chargé à MutablePropertySources: confounded:.

Alors ... donnez-lui de force un nom unique et ajoutez-le à MutablePropertySources. Malheureusement, PropertySource n'avait pas de méthode pour écraser un nom comme setName (), alors remplissez-le avec un alias de CompositePropertySource puis ajoutez-le à MutablePropertySources.

Version modifiée du constructeur


public MaxFromPropertyValidator(List<PropertySourcesPlaceholderConfigurer> configurers) {
	MutablePropertySources propertySources = new MutablePropertySources();
	configurers.forEach(c -> c.getAppliedPropertySources().forEach(p -> {
	//Remplissez avec un CompositePropertySource nommé de manière unique et ajoutez-le à MutablePropertySources
	CompositePropertySource temp = new CompositePropertySource(
		c.toString().concat("$").concat(p.getName()));
			temp.addPropertySource(p);
			propertySources.addLast(temp);
		}));
	this.resolver = new PropertySourcesPropertyResolver(propertySources);

Il y a place à amélioration ici, même si je le dis moi-même: innocent:.

À propos, s'il y a des beans de PropertySourcesPlaceholderConfigurer, configurer1 et configurer2, et que la valeur de l'attribut ʻorder est 1 et 2 et que la valeur de l'attribut localOverrideestfalse, alors MutablePropertySources` À

  1. Propriétés système de configurer1
  2. Propriétés locales de configurer1
  3. Propriétés système de configurer2
  4. Propriétés locales de configurer2

Puisqu'elles sont ajoutées dans l'ordre de, les propriétés du système seront dupliquées, mais je pense qu'il est difficile de les améliorer tout en conservant la cohérence avec les paramètres de ʻorder et localOverride`.

Considérez le cas où il n'y a pas de PropertySourcesPlaceholderConfigurer en premier lieu

Jusqu'à présent, nous avons supposé que PropertySourcesPlaceholderConfigurer était défini par bean, mais je pense que certaines applications peuvent ne pas avoir de définition de bean. Dans ce cas, définissez ʻEnvironment car ʻEnvironment est une sous-interface de PropertyResolver.

Version modifiée du constructeur


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

Utilisez l'original MaxValidator comme référence ...

Maintenant que le processus d'obtention de la valeur de la propriété est terminé, tout ce que vous avez à faire est de comparer la valeur à vérifier avec la valeur maximale. La classe de validateur @ Max dans le hibernate-validator original prend en charge les nombres exprimés dans les types Number et CharSequence, veuillez donc vous y référer.

C'est une préférence personnelle, mais il est difficile de comprendre s'il y a plusieurs fichiers Java de la classe de validation, alors faites de MaxFromPropertyValidator une classe abstraite et héritez-en les classes internes pour Number et CharSequence. À ce moment-là, en remplaçant la méthode ʻisValid par MaxFromPropertyValidatoret en effectuant uniquement une vérification nulle, et en faisant d'une comparaison numérique concrète une méthode abstraite, les classes pourNumber et CharSequencepeuvent comparer des valeurs numériques. Vous pouvez vous concentrer dessus, et vous pouvez définir le qualificatif de valeur maximale obtenu parMaxFromPropertyValidator sur privé. Enfin, la classe spécifiée dans @ Constraint de la classe d'annotation spécifie également à la fois pour Number et CharSequence`.

Veuillez lire la source de ↓ car il est difficile de comprendre s'il s'agit d'une phrase: stuck_out_tongue_closed_eyes:

Version terminée

Classe d'annotation

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();
	}
}

Classe Varidata

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;
			}
		}
	}
}

Essayez de tester

Pour plus de clarté

Est divisé.

@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"));
		});
	}
	//Spécifiez une valeur numérique à partir du fichier de propriétés
	private static class MaxFromPropertySample {

		@MaxFromProperty("max")
		private long value;

		public MaxFromPropertySample(long value) {
			this.value = value;
		}
	}
	//Spécifiez directement la valeur numérique
	private static class MaxValueSetDirectly {

		@MaxFromProperty("99")
		private long value;

		public MaxValueSetDirectly(long value) {
			this.value = value;
		}
	}
	//Vous pouvez également vérifier les numéros de type de séquence de caractères
	private static class MaxFromPropertyForCharSequence {

		@MaxFromProperty("max")
		private CharSequence value;

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

Classe JavaConfig

TestConfig.java


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

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

Placez sample.properties directement sous le chemin de classe.

sample.properties


max=99

en conclusion

Obtenez simplement la valeur de la propriété. J'ai essayé de réussir, mais c'était difficile parce que j'avais beaucoup de pensées. Puisque le mécanisme Spring est utilisé pour résoudre les valeurs de propriété, les annotations créées en Java pur ne fonctionneront pas, mais je pense qu'il y a une demande, donc j'espère qu'Hibernate le fournira un jour.

À propos, la partie qui définit PropertyResolver dans la classe du validateur peut être utilisée à des fins générales, de sorte qu'elle peut être étendue à @ Min et @ Size de la même manière.

Le code créé cette fois-ci est répertorié dans GitHub, mais ce n'est pas la solution optimale comme décrit dans l'article, donc soyez prudent lorsque vous vous y référez: stuck_out_tongue_winking_eye :.

Recommended Posts

Définir les paramètres @Max à partir des propriétés
Passer des paramètres de JSP avec un servlet