Et brugerdefineret databinder i Spring MVC

1. Oversigt

Denne artikel vil vise, hvordan vi kan bruge Spring's Data Binding-mekanisme for at gøre vores kode mere klar og læsbar ved at anvende automatiske primitiver til objektskonverteringer.

Som standard ved Spring kun, hvordan man konverterer enkle typer. Med andre ord, når vi sender data til controller Int, Snor eller Boolsk type data, vil den automatisk være bundet til passende Java-typer.

Men i virkelige projekter vil det ikke være nok, som Vi bliver muligvis nødt til at binde mere komplekse objekter.

2. Binding af individuelle objekter til anmodning om parametre

Lad os starte simpelt og først binde en simpel type; Vi bliver nødt til at levere en brugerdefineret implementering af Konverter interface hvor S er den type, vi konverterer fra, og T er den type, vi konverterer til:

@Component public class StringToLocalDateTimeConverter implementerer Converter {@Override public LocalDateTime convert (String source) {return LocalDateTime.parse (source, DateTimeFormatter.ISO_LOCAL_DATE_TIME); }}

Nu kan vi bruge følgende syntaks i vores controller:

@GetMapping ("/ findbydate / {date}") offentlig GenericEntity findByDate (@PathVariable ("date") LocalDateTime dato) {return ...; }

2.1. Brug af Enums som anmodningsparametre

Derefter får vi se hvordan man bruger eantal som en RequestParameter.

Her har vi en simpel enumTilstande:

offentlige enumtilstande {ALPHA, BETA; }

Vi bygger en Snor til enum Converter som følger:

offentlig klasse StringToEnumConverter implementerer Converter {@Override public Modes convert (String from) {return Modes.valueOf (from); }}

Derefter skal vi registrere vores Konverter:

@Configuration public class WebConfig implementerer WebMvcConfigurer {@Override public void addFormatters (FormatterRegistry registry) {registry.addConverter (new StringToEnumConverter ()); }}

Nu kan vi bruge vores Enum som en RequestParameter:

@GetMapping offentlig ResponseEntity getStringToMode (@RequestParam ("mode") Tilstandstilstand) {// ...}

Eller som en PathVariable:

@GetMapping ("/ entity / findbymode / {mode}") offentlig GenericEntity findByEnum (@PathVariable ("mode") Tilstandstilstand) {// ...}

3. Binding af et objekthierarki

Nogle gange er vi nødt til at konvertere hele træet i objekthierarkiet, og det giver mening at have en mere central binding i stedet for et sæt individuelle omformere.

I dette eksempel har vi AbstraktEnhed vores basisklasse:

offentlig abstrakt klasse AbstractEntity {lang id; offentlig AbstractEntity (lang id) {this.id = id; }}

Og underklasserne Foo og Bar:

offentlig klasse Foo udvider AbstractEntity {privat strengnavn; // standard konstruktører, getters, setters}
public class Bar udvider AbstractEntity {private int-værdi; // standard konstruktører, getters, setters}

I dette tilfælde, vi kan implementere ConverterFabrik hvor S vil være den type, vi konverterer fra, og R skal være basistypen definerer rækkevidden af ​​klasser, vi kan konvertere til:

offentlig klasse StringToAbstractEntityConverterFactory implementerer ConverterFactory {@Override public Converter getConverter (Class targetClass) {returner ny StringToAbstractEntityConverter (targetClass); } privat statisk klasse StringToAbstractEntityConverter implementerer Converter {private Class targetClass; public StringToAbstractEntityConverter (Class targetClass) {this.targetClass = targetClass; } @ Override public T convert (String source) {long id = Long.parseLong (source); hvis (this.targetClass == Foo.class) {return (T) ny Foo (id); } ellers hvis (this.targetClass == Bar.class) {return (T) ny Bar (id); } ellers {return null; }}}}

Som vi kan se, er den eneste metode, der skal implementeres getConverter () der returnerer konverter til den nødvendige type. Konverteringsprocessen delegeres derefter til denne konverter.

Derefter skal vi registrere vores ConverterFabrik:

@Configuration public class WebConfig implementerer WebMvcConfigurer {@Override public void addFormatters (FormatterRegistry registry) {registry.addConverterFactory (new StringToAbstractEntityConverterFactory ()); }}

Endelig kan vi bruge det som vi vil i vores controller:

@RestController @RequestMapping ("/ string-to-abstract") offentlig klasse AbstractEntityController {@GetMapping ("/ foo / {foo}") offentlig ResponseEntity getStringToFoo (@PathVariable Foo foo) {return ResponseEntity.ok (foo); } @GetMapping ("/ bar / {bar}") public ResponseEntity getStringToBar (@PathVariable Bar bar) {return ResponseEntity.ok (bar); }}

4. Bindende domæneobjekter

Der er tilfælde, hvor vi vil binde data til objekter, men det kommer enten på en ikke-direkte måde (for eksempel fra Session, Header eller Cookie variabler) eller endda gemt i en datakilde. I disse tilfælde er vi nødt til at bruge en anden løsning.

4.1. Custom Argument Resolver

Først og fremmest definerer vi en kommentar til sådanne parametre:

@Retention (RetentionPolicy.RUNTIME) @Target (ElementType.PARAMETER) offentlig @ interface-version {}

Derefter implementerer vi en brugerdefineret HandlerMethodeArgumentResolver:

public class HeaderVersionArgumentResolver implementerer HandlerMethodArgumentResolver {@Override public boolean supportsParameter (MethodParameter methodParameter) {return methodParameter.getParameterAnnotation (Version.class)! = null; } @Override public Object resolArgument (MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) kaster undtagelse {HttpServletRequest anmodning = (HttpServletRequRative) return request.getHeader ("Version"); }}

Den sidste ting er at lade Spring vide, hvor de skal søge efter dem:

@Configuration public class WebConfig implementerer WebMvcConfigurer {// ... @Override public void addArgumentResolvers (List argumentResolvers) {argumentResolvers.add (new HeaderVersionArgumentResolver ()); }}

Det er det. Nu kan vi bruge det i en controller:

@GetMapping ("/ entity / {id}") public ResponseEntity findByVersion (@PathVariable Long id, @Version String version) {return ...; }

Som vi kan se, HandlerMethodArgumentResolver'S resolArgument () metode returnerer en Objekt. Med andre ord kunne vi returnere ethvert objekt, ikke kun Snor.

5. Konklusion

Som et resultat slap vi af med mange rutinekonverteringer og lod Spring gøre de fleste ting for os. Lad os til sidst konkludere:

  • For en enkelt enkelt type til at modsætte konverteringer, skal vi bruge Konverter implementering
  • For at indkapsle konverteringslogik for en række objekter kan vi prøve ConverterFabrik implementering
  • For alle data kommer indirekte, eller det er nødvendigt at anvende yderligere logik for at hente de tilknyttede data, det er bedre at bruge HandlerMethodArgumentResolver

Som normalt kan alle eksemplerne altid findes i vores GitHub-lager.


$config[zx-auto] not found$config[zx-overlay] not found