Spring MVC

Spring MVC ist Request-Driven, d. h. ein Controller erhält Http-Requests (GET, POST), die er verarbeitet und zu guter Letzt eine Antwort in Form eines Views (= UI) sendet.


Spring MVC Rest

siehe eigenes Kapitel

Ein @RestController und ein @Controller (für UIs) sehen fast identisch aus. Während der RestController allerdings HttpResponses zurückliefert, liefert der Controller View-Identifier, über die dann eine HTML-Response gebaut und zum Client (= Browser) geliefert wird.

Die Grenzen zwischen UI-Client und Rest-Client verschwimmen ... und das ist auch gut so.


Spring MVC - View-Technologie

Spring MVC ist zunächst mal unabhängig von der konkret eingesetzten View-Technologie. Es bietet Adapter, um beispielsweise JSP oder Thymeleaf zu verwenden. Hier findet man weitere Informationen:

i18n - Internationalization

Locale-Resolver

Per Default verwendet Spring MVC den org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver, so daß die Sprache aus dem Accept-Header gewählt wird. Diese Sprache ist die Sprache, die im Browser eingestellt ist. Da dort mehrere Sprachen eingestellt sein können, wird die erste genommen (= man unterstellt, daß das die bevorzugte Sprache ist).

Will man seine eigene Logik verwenden, so muß man nur einen eigenen MyLocaleResolver schreiben und als Bean konfigurieren:

public class MyLocaleResolver extends AcceptHeaderLocaleResolver {

    @Override
    public Locale resolveLocale(HttpServletRequest r) {
      // ... your implementation
    }
}

Dadurch wird dieser MyLocaleResolver automatisch eingebunden, wenn ich beispielsweise in einem Controller die Locale injecten lasse:

@RequestMapping(path = "/doit"
public String doIt(@Valid User user,
        BindingResult bindingResult, Model model, Locale language);

LocaleContextHolder

Über

  • LocaleContextHolder.getLocale()
  • LocaleContextHolder.getTimezone()

lassen sich Informationen zum aktuellen Thread ermitteln. Dort steckt beispielsweise die Locale drin, die der konfigurierte LocaleResolver ermittelt hat.

LocaleChangeInterceptor

Hat man diesen Interceptor konfiguriert (

@Configuration
@EnableAutoConfiguration
@ComponentScan
public class WebAppConfig extends WebMvcConfigurerAdapter {

    @Bean
    public LocaleChangeInterceptor myLocaleChangeInterceptor() {
        LocaleChangeInterceptor lci = new LocaleChangeInterceptor();
        lci.setParamName("language");
        return lci;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myLocaleChangeInterceptor());
    }

}

so läßt sich über http://localhost:8081/persondetails?language=de die Sprache auf deutsch umschalten.

Message-Resolver

Spring MVC kommt mit der in Spring Boot üblichen AutoConiguration (org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration) daher. Diese Klasse offenbart, was man tun muß, um Texte/Labels/Messages zu internationalisieren ... Dateien der Art:

  • src/main/resources/messages.properties
  • src/main/resources/messages_de.properties
  • ...

anlegen. Gemeinsam mit dem LocaleResolver (s. o.) wird zur Laufzeit der richtige Wert ausgelesen.

Sollen die Dateien in einem anderen Verzeichnis liegen, sollen es mehrere sein oder sollen sie einen anderen Namen haben, dann müssen die application.properties angepaßt werden:

spring.messages.basename=i18n/my_messages,i18n/your_messages

So läßt sich beispielsweise auch das Caching konfigurieren:

spring.messages.cacheSeconds = 1

Caching

Die vom Server bereitgestellten Ressourcen (JavaScript, Images, ...) sollten clientseitig gecached werden. Hierzu bietet Spring die org.springframework.boot.autoconfigure.web.ResourceProperties an, in der sich viele interessante Schalter befinden


Spring MVC - Controller und Model

Der Controller übernimmt die Seitensteuerung, d. h. die Controller-Methode liefert einen String als Rückgabewert, der die nachfolgende View-Page repräsentiert, z. B.

return "login";

Je nach gewählter View-Technologie (z. B. Thymeleaf) und deren Konfiguration wird dann beispielsweise eine Seite login.html gebaut (aus einem Template) und zum Client geschickt.

Hier kann aber beispielsweise auch ein Redirect-Request auf eine externe Seite getriggert werden:

return "redirect:http://www.cachaca.de";

Configuration

Wie üblich verwendet Spring einen annotationsbasierten Ansatz. Die Annotationen werden bei Anwendungsstart gescannt und dadurch die Applikation initialisiert. Handelt es sich um eine Spring Boot Applikation mit @EnableAutoConfiguration so sollte alles automatisch initialisiert werden. Ansonsten muß

<context:component-scan base-package="de.cachaca.myapp"/>

eingesetzt werden, um die Annotationen in den Initialisierungsfokus zu rücken.

@Controller

Kennzeichnet eine Klasse in der Rolle eines Controllers. Ein Controller erhält HTTP-Requests, z. B. aus Http-Post-Form-Requests. Spring MVC versucht, den Lücke zwischen textbasiertem HTTP und der Arbeit mit Java-Objekten im Controller so klein wie möglich erscheinen zu lassen. Hier muß sich niemand mehr um das Marshalling/Unmarshalling (aus HTTP-Stringwerten Java-Objekte machen) kümmern.

Ein Controller ist zudem für den Web-Flow zuständig und liefert deshalb den zu rendernden View im Return-Value. Diese Information wird vom konfigurierten ViewResolver (hier ist die Brücke zur UI-technologie) verwendet, um die HTML-Seite zu erzeugen. Siehe unten Typkonvertierung.

Einige der in diesem Kontext verwendeten Annotationen (z. B. @RequestMapping) kommen auch beim @RestController zur Anwendung.

ACHTUNG: im Gegensatz zu JSF ist Spring MVC ein Controller-First-Ansatz (ähnlich wie JSP). JSF ist ein View-First-Ansatz, d. h. der Einstiegspunkt der View.

@RequestMapping

Hierüber werden die Controller-Actions deklariert:

@RequestMapping(path = "/user")
public String getUser(){
    //...
}

Hierüber wird ein HTTP-Endpunkt, der dann über http://.../user auf ALLE HTTP-Methoden (GET, POST, PUT, ...) reagiert. Über method = RequestMethod.GET läßt sich das auf GET-Requests einschränken.

Solche Request-Mappings können relativ komplex werden, wenn beispielsweise @PathVariable verwendet werden (ein Beispsiel von hier):

@RequestMapping(
  "/spring-web/
    {symbolicName:[a-z-]+}-
    {version:\\d\\.\\d\\.\\d}
    {extension:\\.[a-z]+}")
public void handle(
    @PathVariable String version,
    @PathVariable String extension) {
    // ...
}

Man kann auch Headerwerte zur Selektion der richtigen Controllermethode aufzunehmen.

@GetMapping

Ein typischer HTTP-GET @Controller:

@Controller
public class MyController {

  @RequestMapping(path = "/user", method = RequestMethod.GET)
  public String getUser(Model model) { // ... }
      model.addAttribute("user", new User());
      return "index";
  }
}

Mittlerweile gibt es auch spezialisierte RequestMappings: @GetMapping, ...

@Controller
public class MyController {

  @GetMapping(path = "/user")
  public String getUser(Model model) { // }
}

@PostMapping

Ein typischer HTTP-POST-Controller:

@Controller
public class WebPatientRegistrationController {

  @RequestMapping(path = "/register", method = RequestMethod.POST)
  public String register(String username, String emailaddress
          BindingResult bindingResult, Model model) {
    return "redirect:/registerVerify";
  }
}

@RequestMapping ... Parameter der Java-Methode

Default-Methoden-Parameter:

Man kann in eine Controller-Methode die folgenden Parameter aufnehmen und bekommt sie richtig injeziert:

  • BindingResult
  • Model
  • Locale - die im Browser eingestellte Sprache (kommt über den HTTP Accept-Language Header)

Methoden-Parameter:

Dieser Link beschreibt die Regeln für die Java-Methode, die an einem @RequestMapping hängt:

Typ-Konvertierung:

Im HTTP-Request sind nun Strings enthalten, in den Controller-Methoden arbeitet man häufig aber mit komplexeren Java-Klassen. Hierfür gibt es bereits häufig automatische Konvertierungen, häufig wird aber auch JAXB verwendet.

Ist keine automatische Konvertierung möglich, so könnte es zu einer org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException kommen. In solchen Fällen muß man im worst-case entsprechende Converter schreiben (weitere Details).

Tritt beim Unmarshalling ein Fehler auf, so wird das in BindingResult hinterlegt. Dieser Parameter sollte in keiner HTTP-POST, so daß der Hier finden sich weitere Informationen zur Typ-Konvertierung:

Mit JAXB kann die Typ-Konvertierung unterstützt werden.

Semantische Prüfungen:

Neben diesen syntaktischen Typ-Prüfungen (ein String muß in ein java.util.Date konvertierbar sein) können weitere semantische Prüfungen sinnvoll sein. Diese können über Java Bean Validation mit @Valid sehr schön integriert werden:

@ValidUsername
@Size(min=8, max=12)
private String username;

public String sayHello(
   ModelMap m,
   @Valid Person p,
   BindingResult r) {
  // ...
}

Die Fehlermeldungen können über den üblichen i18n-Mechanismus (e. g. messages.properties) konfiguriert werden. Allerdings muß dann folgende Konfiguration durchgeführt werden:

public class MvcConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private MessageSource messageSource;

    @Override
    public Validator getValidator() {
        LocalValidatorFactoryBean factory = new LocalValidatorFactoryBean();
        factory.setValidationMessageSource(messageSource);
        return factory;
    }
}

HttpServletRequest

Wie kommt man an den HttpServletRequest im Controller?

Option 3:

HttpServletRequest request =
  ((ServletRequestAttributes)
    RequestContextHolder.currentRequestAttributes())
      .getRequest();

Hierzu braucht es aber noch einen

@Bean 
public RequestContextListener requestContextListener(){
    return new RequestContextListener();
}

AOP-Proxying

Will man im Controller Transaktionen deklarieren (@Transactional), so kann man Class-Based Proxying verwenden. Das wird im Spring-Context beispielweise über

<tx:annotation-driven proxy-target-class="true"/>

konfiguriert.

@RequestBody

Mit dieser Annotation kann man den HTTP-Body (z. B. eines HTTP-POST) in einen Parameter injecten lassen:

public void registerUser(@RequestBody String body) {}

Der gelieferte String kann über HTTP Message Conversion verarbeitet werden. Bei folgender Schnittstelle geschieht das automatisch ... das entsprechende Marshalling vorausgesetzt (z. B. über JSON-Jackson oder JAXB):

public void registerUser(@RequestBody UserCredential c) {}

@ResponseBody

Mit dieser Annotation wird das Ergebnis einer Controller-Methode an den HTTP-Response-Body gebunden:

public @ResponseBody User getUser(String username) {}

Spring (Boot) Security

Zur Absicherung der HTTP-Schnittstellen stellt Spring bereits eine Auto-Configuration bereit, sofern man die Abhängigkeit

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

in der pom.xml eingetragen hat. Die Auto-Konfiguration sichert ALLE Http-Endpunkte per Default über HTTP-Basic-Authentication ab (hier zeigt der Browser ein Popup) - über einen WebSecurityConfigurerAdapter läßt sich die Konfiguration entsprechend anpassen, so daß einige Endpunkte auch public sind (keiner Authentifizierung bedürfen) oder beispielsweise auch Form-Authentication (mit entsprechendem Layout) ermöglicht wird.

results matching ""

    No results matching ""