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:
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();
}
}
C'est important.
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:
PropertySourcesPlaceholderConfigurer
Selon l'application, plusieurs PropertySourcesPlaceholderConfigurer
s 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
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 de
PropertySource sont identiques,
true` est renvoyé.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.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
localOverrideest
false, alors
MutablePropertySources` À
configurer1
configurer1
configurer2
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`.
PropertySourcesPlaceholderConfigurer
en premier lieuJusqu'à 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;
}
}
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 pour
Number et
CharSequencepeuvent comparer des valeurs numériques. Vous pouvez vous concentrer dessus, et vous pouvez définir le qualificatif de valeur maximale obtenu par
MaxFromPropertyValidator 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:
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();
}
}
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;
}
}
}
}
Pour plus de clarté
CharSequence
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
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 :.