Un classeur de données personnalisé dans Spring MVC
1. Vue d'ensemble
Cet article montre comment utiliser le mécanisme de liaison de données de Spring pour rendre notre code plus clair et plus lisible en appliquant des primitives automatiques aux conversions d'objets.
Par défaut, Spring ne sait convertir que des types simples. En d'autres termes, une fois que nous soumettons les données au type de données du contrôleurInt,String ouBoolean, elles seront automatiquement liées aux types Java appropriés.
Mais dans les projets du monde réel, cela ne suffira pas, commewe might need to bind more complex types of objects.
2. Lier des objets individuels à des paramètres de requête
Commençons simplement et lions d'abord un type simple; nous devrons fournir une implémentation personnalisée de l'interfaceConverter<S, T> oùS est le type à partir duquel nous effectuons la conversion etT est le type vers lequel nous convertissons:
@Component
public class StringToLocalDateTimeConverter
implements Converter {
@Override
public LocalDateTime convert(String source) {
return LocalDateTime.parse(
source, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
}
}
Nous pouvons maintenant utiliser la syntaxe suivante dans notre contrôleur:
@GetMapping("/findbydate/{date}")
public GenericEntity findByDate(@PathVariable("date") LocalDateTime date) {
return ...;
}
2.1. Utilisation d'énumérations comme paramètres de requête
Ensuite, nous verronshow to use enum as a RequestParameter.
Ici, nous avons un simpleenumModes:
public enum Modes {
ALPHA, BETA;
}
Nous allons construire unString enenum Converter comme suit:
public class StringToEnumConverter implements Converter {
@Override
public Modes convert(String from) {
return Modes.valueOf(from);
}
}
Ensuite, nous devons enregistrer nosConverter:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new StringToEnumConverter());
}
}
Nous pouvons maintenant utiliser nosEnum comme desRequestParameter:
@GetMapping
public ResponseEntity
Ou enPathVariable:
@GetMapping("/entity/findbymode/{mode}")
public GenericEntity findByEnum(@PathVariable("mode") Modes mode) {
// ...
}
3. Lier une hiérarchie d'objets
Parfois, nous avons besoin de convertir l’arbre complet de la hiérarchie des objets et il est logique d’avoir une liaison plus centralisée plutôt qu’un ensemble de convertisseurs individuels.
Dans cet exemple, nous avonsAbstractEntity notre classe de base:
public abstract class AbstractEntity {
long id;
public AbstractEntity(long id){
this.id = id;
}
}
Et les sous-classesFoo etBar:
public class Foo extends AbstractEntity {
private String name;
// standard constructors, getters, setters
}
public class Bar extends AbstractEntity {
private int value;
// standard constructors, getters, setters
}
Dans ce cas,we can implement ConverterFactory<S, R> where S will be the type we are converting from and R to be the base type définit la plage de classes que nous pouvons convertir en:
public class StringToAbstractEntityConverterFactory
implements ConverterFactory{
@Override
public Converter getConverter(Class targetClass) {
return new StringToAbstractEntityConverter<>(targetClass);
}
private static class StringToAbstractEntityConverter
implements Converter {
private Class targetClass;
public StringToAbstractEntityConverter(Class targetClass) {
this.targetClass = targetClass;
}
@Override
public T convert(String source) {
long id = Long.parseLong(source);
if(this.targetClass == Foo.class) {
return (T) new Foo(id);
}
else if(this.targetClass == Bar.class) {
return (T) new Bar(id);
} else {
return null;
}
}
}
}
Comme nous pouvons le voir, la seule méthode qui doit implémenter estgetConverter() qui renvoie le convertisseur pour le type nécessaire. Le processus de conversion est ensuite délégué à ce convertisseur.
Ensuite, nous devons enregistrer nosConverterFactory:
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverterFactory(new StringToAbstractEntityConverterFactory());
}
}
Enfin, nous pouvons l’utiliser comme bon nous semble dans notre contrôleur:
@RestController
@RequestMapping("/string-to-abstract")
public class AbstractEntityController {
@GetMapping("/foo/{foo}")
public ResponseEntity
4. Objets de domaine de liaison
Il y a des cas où l'on souhaite lier des données à des objets, mais cela vient soit de manière non directe (par exemple, à partir des variablesSession,Header ouCookie) ou même stockée dans une source de données. Dans ces cas, nous devons utiliser une solution différente.
4.1. Résolveur d'arguments personnalisés
Tout d'abord, nous allons définir une annotation pour de tels paramètres:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Version {
}
Ensuite, nous implémenterons unHandlerMethodArgumentResolver: personnalisé
public class HeaderVersionArgumentResolver
implements HandlerMethodArgumentResolver {
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
return methodParameter.getParameterAnnotation(Version.class) != null;
}
@Override
public Object resolveArgument(
MethodParameter methodParameter,
ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest,
WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request
= (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getHeader("Version");
}
}
La dernière chose est de faire savoir au printemps où les rechercher:
@Configuration
public class WebConfig implements WebMvcConfigurer {
//...
@Override
public void addArgumentResolvers(
List argumentResolvers) {
argumentResolvers.add(new HeaderVersionArgumentResolver());
}
}
C'est ça. Maintenant nous pouvons l’utiliser dans un contrôleur:
@GetMapping("/entity/{id}")
public ResponseEntity findByVersion(
@PathVariable Long id, @Version String version) {
return ...;
}
Comme nous pouvons le voir, la méthoderesolveArgument() deHandlerMethodArgumentResolver renvoie unObject. En d'autres termes, nous pourrions renvoyer n'importe quel objet, pas seulementString.
5. Conclusion
En conséquence, nous nous sommes débarrassés de nombreuses conversions de routine et avons laissé Spring s'occuper de la plupart des tâches pour nous. À la fin, concluons:
-
Pour une conversion individuelle de type simple en objet, nous devrions utiliser l'implémentationConverter
-
Pour encapsuler la logique de conversion pour une plage d'objets, nous pouvons essayer l'implémentation deConverterFactory
-
Pour toutes les données proviennent indirectement ou il est nécessaire d'appliquer une logique supplémentaire pour récupérer les données associées, il est préférable d'utiliserHandlerMethodArgumentResolver
Comme d'habitude, tous les exemples se trouvent toujours dans nosGitHub repository.