[JAVA] Modifications majeures liées à Spring Framework 5.0 Web MVC

Il s'agit du 4ème volet de la série «Spring Framework 5.0 Major Changes» et des principaux changements liés à WebMVC (nouvelles fonctionnalités, améliorations, etc.) Je voudrais présenter.

séries

Version de vérification de fonctionnement

Ligne de base


Individuel


Cette fois, j'ai utilisé Spring Boot 2.0.0.M1 (qui dépend de Spring Framework 5.0.0.RC1 par défaut) pour vérifier l'opération. Vous trouverez ci-dessous une brève introduction à la procédure de création d'un projet de vérification.

Note:

Il peut également être créé à l'aide des fonctions SPRING INITIALIZR et IDE.

$ curl -s https://start.spring.io/starter.tgz\
  -d name=spring5-web-demo\
  -d artifactId=spring5-web-demo\
  -d dependencies=web\
  -d baseDir=spring5-web-demo\
  -d bootVersion=2.0.0.M1\
  | tar -xzvf -
$ ./mvnw spring-boot:run
/usr/local/apps/spring5-web-demo
[INFO] Scanning for projects...
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Building spring5-web-demo 0.0.1-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] >>> spring-boot-maven-plugin:2.0.0.M1:run (default-cli) > test-compile @ spring5-web-demo >>>
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ spring5-web-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ spring5-web-demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source files to /usr/local/apps/spring5-web-demo/target/classes
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ spring5-web-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /usr/local/apps/spring5-web-demo/src/test/resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ spring5-web-demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source files to /usr/local/apps/spring5-web-demo/target/test-classes
[INFO] 
[INFO] <<< spring-boot-maven-plugin:2.0.0.M1:run (default-cli) < test-compile @ spring5-web-demo <<<
[INFO] 
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.0.0.M1:run (default-cli) @ spring5-web-demo ---

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::             (v2.0.0.M1)

2017-05-17 01:19:22.357  INFO 26370 --- [           main] c.e.s.Spring5WebDemoApplication          : Starting Spring5WebDemoApplication on xxx with PID 26370 (/usr/local/apps/spring5-web-demo/target/classes started by xxx in /usr/local/apps/spring5-web-demo)
2017-05-17 01:19:22.360  INFO 26370 --- [           main] c.e.s.Spring5WebDemoApplication          : No active profile set, falling back to default profiles: default
2017-05-17 01:19:22.411  INFO 26370 --- [           main] ConfigServletWebServerApplicationContext : Refreshing org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@25c892fe: startup date [Wed May 17 01:19:22 JST 2017]; root of context hierarchy
2017-05-17 01:19:23.717  INFO 26370 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2017-05-17 01:19:23.733  INFO 26370 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2017-05-17 01:19:23.735  INFO 26370 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.15
2017-05-17 01:19:23.822  INFO 26370 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-05-17 01:19:23.822  INFO 26370 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1414 ms
2017-05-17 01:19:23.956  INFO 26370 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-05-17 01:19:23.961  INFO 26370 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-05-17 01:19:23.961  INFO 26370 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-05-17 01:19:23.961  INFO 26370 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-05-17 01:19:23.961  INFO 26370 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-05-17 01:19:24.206  INFO 26370 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext@25c892fe: startup date [Wed May 17 01:19:22 JST 2017]; root of context hierarchy
2017-05-17 01:19:24.292  INFO 26370 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-05-17 01:19:24.293  INFO 26370 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-05-17 01:19:24.323  INFO 26370 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-05-17 01:19:24.323  INFO 26370 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-05-17 01:19:24.370  INFO 26370 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-05-17 01:19:24.533  INFO 26370 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-05-17 01:19:24.610  INFO 26370 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http)
2017-05-17 01:19:24.615  INFO 26370 --- [           main] c.e.s.Spring5WebDemoApplication          : Started Spring5WebDemoApplication in 2.996 seconds (JVM running for 6.973)
Sur le terminal, "Ctrl+ C」!!

Modifications liées à WebMVC

Dans Spring Framework 5.0, les modifications suivantes ont été apportées à la fonction WEB.

Numéro d'article Changements
1 Le filtre de servlet fourni par Spring Framework est Servlet 3.Ce sera une implémentation conforme à la spécification API de 1. (En fait, je ne sais pas comment cela s'est conformé ...:sweat_smile:) [Vers les détails:arrow_right:]
2 Servlet 4 qui est un composant de Java EE 8 comme argument de la méthode Handler de Spring MVC.Ajouté à 0PushBuilder(HTTP/API pour faire Server Push of 2)Vous pourrez le recevoir.[Vers les détails:arrow_right:]
3 Servlet 3.Lorsqu'une erreur surdimensionnée se produit pour la fonction de téléchargement de fichier prise en charge par 0MaxUploadSizeExceededException(MultipartExceptionSous-exception)Sera jeté.[Vers les détails:arrow_right:]

**Note:**Cependant,certainsmotsdumessaged'erreur("size"Quand"exceed")Puisqu'il est jugé selon qu'il est inclus ou non, il est le même qu'auparavant en fonction de l'implémentation du serveur d'applications.MultipartExceptionがスローされる可能性があるQuandいう点は意識しておいた方がよいでしょう。
4 Mécanisme de résolution de type de média unifié(MediaTypeFactoryclasse)Sera ajouté.[Vers les détails:arrow_right:]

**Note:**Aveccettecorrespondance,JAF(JavaBeansActivationframework)Le code dépendant est éliminé.
5 La liaison de données à des objets immuables est prise en charge.[Vers les détails:arrow_right:]
6 Jackson 2.9 est pris en charge.[Vers les détails:arrow_right:]
7 Un composant de Java EE 8JSON BindingSeraprisencharge.

**Note:**IlpeutêtreutilisécommealternativeàJacksonetGSON.[Verslesdétails:arrow_right:]
8 Google Protobuf 3.x est pris en charge.[Vers les détails:arrow_right:]
9 Reactive Programing Model(Spring WebFlux décrit plus tard)Avec le soutien de Reactor 3.1 cours(Flux, Mono)、RxJava 1.3 et 2.1 cours(Observable, Sigle, FlowableTel)Peut être traité comme la valeur de retour de la méthode Handler.[Vers les détails:arrow_right:]
10 AntPathMatcherComme alternative àParsingPathMatcherSera ajouté.[Vers les détails:arrow_right:]

**Note:**Pour les composants liés à Spring WebFlux,ParsingPathMatcherEst utilisé par défaut.
11 @ExceptionHandlerEn tant qu'argument de méthodeRedirectAttributesVous pourrez le recevoir.(=RedirectAttributesVous pourrez lier les données à la destination de la redirection via) [Vers les détails:arrow_right:]
12 ResponseStatusExceptionEst ajouté, et vous pouvez contrôler l'état HTTP en générant et en lançant une exception en spécifiant un code d'état arbitraire et une phrase de raison.[Vers les détails:arrow_right:]

Note: ResponseStatusExceptionSemble avoir été créé pour Spring WebFlux, mais il est également pris en charge pour une utilisation avec Spring MVC.
13 ScriptTemplateView(JSR-Afficher l'implémentation à l'aide du moteur de script 223)Objets nécessaires pour réaliser "l'internationalisation des messages" et la "fragmentation des modèles"(Locale,MessageSourceTel)Sera remis du côté du moteur de gabarit.[Vers les détails:arrow_right:]

Note: 「国際化」や「テンプレートをフラグメント化」するための実装自体はSpringからは提供されないので、SpringのテストケースTelを参考に開発者が実装する必要があります。

L'implémentation du filtre de servlet est-elle conforme à la spécification de l'API Servlet 3.1? : pouces vers le haut:

[SPR-14467]: Apparemment, il s'agit d'une implémentation purement (non limitée au filtre de servlet) basée sur la spécification de l'API Servlet 3.1 ( = Le fonctionnement sur Servlet 3.0 n'est pas pris en charge)! Il paraît que. À cet égard, jusqu'à Spring Framework 4.3, du code a été implémenté pour maintenir la compatibilité avec l'environnement Servlet 2.5, mais à partir de Spring Framework 5.0, la prise en charge de l'environnement Servlet 2.5 sera interrompue du tout ([SPR-13189]. ](Https://jira.spring.io/browse/SPR-13189)).

Servlet 4.0 PushBuilder peut être reçu par la méthode Handler: thumbsup:

[SPR-12674] PushBuilder ajouté dans Servlet 4.0, qui est un composant de Java EE 8, comme argument de la méthode Handler de Spring MVC. Vous pourrez recevoir (API pour HTTP / 2 Server Push).

Implémentons un exemple simple et essayons HTTP / 2 Server Push en action. Tout d'abord, créez un fichier HTML avec un lien dans le fichier css et le fichier css.

src/main/resources/static/style.css


body {
    background-color: azure;
}

src/main/resources/static/hello.html


<html>
<head>
    <link rel="stylesheet" type="text/css" href="style.css"/> <!--★★★ Lien vers le fichier css-->
</head>
<body>
<h1>Hello World !!</h1>
</body>
</html>

Ensuite, créez un contrôleur.

package com.example.spring5webdemo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.PushBuilder;

@Controller
public class PushBuilderController {

	@GetMapping("/push-builder")
	String get(PushBuilder builder) { //★★★ Déclarer PushBuilder comme argument
		builder.path("/style.css").push(); //★★★ Définissez le fichier css sur le chemin à pousser et poussez
		return "forward:/hello.html"; //Renvoyer le nom de la vue pour la transition vers HTML
	}

}

Je voudrais dire, "Commençons le serveur et vérifions-le!", Mais je ne pense pas qu'il puisse être compilé parce que l'API (PushBuilder) de Servlet 4.0 n'est pas trouvée en premier lieu. Donc ... je voudrais modifier le fichier pom.xml pour utiliser un serveur d'application qui prend en charge Servlet 4.0. Au début, je pensais l'essayer avec Tomcat 9.0, mais Undertow semblait être plus facile à convertir en HTTP / 2, alors j'ai utilisé ici Undertow 2.0 (une version en développement) qui prend en charge Servlet 4.0. Pour vérifier le fonctionnement.

Note:

Undertow 2.0.0.Alpha1 a été publié dans Maven Central, mais malheureusement cette version n'est pas disponible. La raison est que les spécifications API de Servlet 4.0 ont changé au milieu et que les changements n'ont pas été incorporés. Donc ... Dans cette entrée, récupérez le code source de GitHub et installez Undertow 2.0.0.Alpha2-SNAPSHOT dans votre référentiel local.

$ git clone https://github.com/undertow-io/undertow.git
$ cd undertow
$ mvn -U install -DskipTests=true

Modifiez le fichier pom.xml pour utiliser Undertow 2.0.0.Alpha2-SNAPSHOT au lieu de Tomcat.

pom.xml


<!-- ... -->

<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
  <undertow.version>2.0.0.Alpha2-SNAPSHOT</undertow.version> <!-- ★★★ Servlet 4.0 Spécifier la version de support(Version installée dans le référentiel local) -->
</properties>

<!-- ... -->

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <!--★★★ Exclure Tomcat-->
    <exclusions>
      <exclusion>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
      </exclusion>
    </exclusions>
  </dependency>

  <!-- ... -->

  <!--★★★ Ajouté Undertow-->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
  </dependency>

  <!-- ★★★ alpn-Ajouter un démarrage-->
  <!-- 
    ALPN (Application Layer Protocol Negotiation)Paramètres de téléchargement de la bibliothèque d'implémentation de.
Le fichier téléchargé est une option de démarrage lorsque l'application est exécutée.(-Xbootclasspath)Spécifié comme.
  -->
  <dependency>
    <groupId>org.mortbay.jetty.alpn</groupId>
    <artifactId>alpn-boot</artifactId>
    <version>8.1.11.v20170118</version>
    <scope>provided</scope>
  </dependency>

  <!-- ... -->

</dependencies>

<!-- ... -->

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
        <!--★★★ Option pour démarrer la bibliothèque d'implémentation ALPN(-Xbootclasspath)Désigné pour-->
        <jvmArguments>
          -Xbootclasspath/p:${settings.localRepository}/org/mortbay/jetty/alpn/alpn-boot/8.1.11.v20170118/alpn-boot-8.1.11.v20170118.jar
        </jvmArguments>
      </configuration>
    </plugin>
  </plugins>
</build>

<!-- ... -->

Afin de communiquer avec HTTP / 2, la communication entre le serveur et le navigateur doit être une communication SSL / TLS par HTTPS, créez donc un keystore qui stocke le certificat auto-signé.

$ keytool -genkey -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore src/main/resources/keystore.p12 -validity 3650
Veuillez saisir le mot de passe du fichier de clés:  
Veuillez saisir à nouveau votre nouveau mot de passe: 
Quel est votre nom et prénom?
 [Unknown]:  Sample
Quel est le nom de l'unité organisationnelle?
 [Unknown]:  Sample
Quel est le nom de l'organisation?
 [Unknown]:  Sample
Quel est le nom de la ville ou de la région?
 [Unknown]:  Sample
Quel est l'état ou le nom de l'état?
 [Unknown]:  Sample
Quel est le code du pays à deux lettres pour cet appareil?
 [Unknown]:  JP
CN=Sample, OU=Sample, O=Sample, L=Sample, ST=Sample, C=Êtes-vous sûr de vouloir JP?
 [Non]:  Y

Définissez le fichier de clés qui stocke le certificat auto-signé créé sur Undertow et définissez le serveur HTTPS.

src/main/resources/application.properties


server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password

Enfin, si vous ajoutez la définition de Bean suivante, le support HTTP / 2 d'Undertow est terminé.

// ...
import io.undertow.UndertowOptions;
import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
// ...

@SpringBootApplication
public class Spring5WebDemoApplication {
	// ...
	@Bean
	public WebServerFactoryCustomizer<UndertowServletWebServerFactory> webServerFactoryCustomizer() {
		return undertow -> {
			undertow.addBuilderCustomizers(builder -> builder.setServerOption(UndertowOptions.ENABLE_HTTP2, true));
		};
	}
}

Démarrez Spring Boot et vérifiez que Undertow est HTTP / 2.

$ ./mvnw spring-boot:run
...
2017-05-18 09:02:40.807  INFO 60554 --- [           main] o.s.b.w.e.u.UndertowServletWebServer     : Undertow started on port(s) 8080 (https)
2017-05-18 09:02:40.813  INFO 60554 --- [           main] c.e.s.Spring5WebDemoApplication          : Started Spring5WebDemoApplication in 2.538 seconds (JVM running for 3.086)
$ curl -s -D - --http2 -k https://localhost:8080/
HTTP/2.0 404
content-type:application/json;charset=UTF-8
date:Thu, 18 May 2017 00:07:16 GMT

{"timestamp":"2017-05-18T00:07:15.967+0000","status":404,"error":"Not Found","message":"Not Found","path":"/"}

Note: La commande cURL sur Mac n'était pas compatible HTTP / 2, j'ai donc exécuté la commande suivante pour installer la commande cURL compatible HTTP / 2.

$ brew reinstall curl -- --with-nghttp2
$ brew link curl --force
$ hash -r

Maintenant que nous avons confirmé qu'il a été converti en HTTP / 2, utilisons Chrome pour accéder au chemin qui exécute réellement HTTP / 2 Server Push.

spring50-push-builder.png

C'est un peu difficile, mais vous pouvez voir que l'initiateur de "/style.css" est "Push / push-builder".

Warning:

Cependant ... Il semble que Server Push ne soit pas activé après la deuxième fois ... + Le journal suivant apparaît dans le journal du serveur ... Donc, quelque chose ne va pas Peut être: pleurer:

2017-05-18 11:02:02.813 ERROR 60871 --- [  XNIO-2 task-9] io.undertow                              : UT005085: Connection io.undertow.server.protocol.http2.Http2ServerConnection@35f2c3a7 for exchange HttpServerExchange{ GET /style.css request {accept=[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8], accept-language=[en-US,en;q=0.8,ja;q=0.6], cache-control=[max-age=0], :authority=[localhost:8080], accept-encoding=[gzip, deflate, sdch, br], :path=[/style.css], user-agent=[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36], :scheme=[https], :method=[GET], Referer=[https://localhost:8080/push-builder], upgrade-insecure-requests=[1], Host=[localhost:8080]} response {Last-Modified=[Wed, 17 May 2017 19:36:35 GMT], Cache-Control=[no-store], Content-Length=[37], Content-Type=[text/css], Accept-Ranges=[bytes], Date=[Thu, 18 May 2017 02:02:02 GMT], :status=[200]}} was not closed cleanly, forcibly closing connection

La fonction de téléchargement de fichier Servlet 3.0 lance désormais MaxUploadSizeExceededException: thumbsup:

[SPR-9294]: MaxUploadSizeExceededException ( MultipartException) lorsqu'une erreur surdimensionnée se produit dans la fonction de téléchargement de fichier prise en charge par Servlet 3.0. La sous-exception `) sera maintenant lancée. Cependant, comme il est déterminé si le message d'erreur contient des mots spécifiques ("" taille "et" dépassé ""), "MultipartException" est lancé comme précédemment en fonction de l'implémentation du serveur d'application. Je pense que vous devriez être conscient qu'il y a une possibilité.

Note:

Spring Framework fournit un composant qui fonctionne avec Commons File Upload, et lorsqu'une taille est dépassée dans Commons File Upload, une exception `MaxUploadSizeExceededException 'est lancée.

Générons maintenant une erreur surdimensionnée. Créer un écran de téléchargement de fichier est un problème, donc ... Dans cette entrée, nous allons télécharger le fichier en utilisant la commande cURL.

Tout d'abord, créez un contrôleur qui reçoit le fichier téléchargé et démarrez Spring Boot.

package com.example.spring5webdemo;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class UploadRestController {
	@PostMapping("/upload")
	String upload(MultipartFile file) {
		return file.getOriginalFilename() + " is uploaded !";
	}
}

Ensuite, essayez de télécharger un fichier dans 1 Mo (un fichier plus petit que la taille maximale) comme indiqué ci-dessous.

$ pwd
/usr/local/apps/spring5-web-demo
$ curl -s -D - -X POST -F [email protected] http://localhost:8080/upload
HTTP/1.1 100 

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 21
Date: Tue, 16 May 2017 16:56:48 GMT

pom.xml is uploaded !

Maintenant, importons un fichier de 1 Mo ou plus. Au fait ... 1 Mo ou plus de fichiers (target / spring5-web-demo-0.0.1-SNAPSHOT.jar) peuvent être créés avec ./mvnw package.

$ ./mvnw package
...
$ curl -s -D - -X POST -F file=@target/spring5-web-demo-0.0.1-SNAPSHOT.jar http://localhost:8080/upload
HTTP/1.1 100 

HTTP/1.1 500 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 16:59:21 GMT
Connection: close

{"timestamp":"2017-05-16T16:59:21.255+0000","status":500,"error":"Internal Server Error","message":"Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (14620179) exceeds the configured maximum (10485760)","path":"/upload"}

J'ai eu une erreur surdimensionnée, mais je ne sais pas si l'exception MaxUploadSizeExceededException s'est produite, alors jetons un coup d'œil au journal du serveur.

Journal du serveur(console)


...
2017-05-17 01:59:21.246 ERROR 26570 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.multipart.MaxUploadSizeExceededException: Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (14620179) exceeds the configured maximum (10485760)] with root cause

org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (14620179) exceeds the configured maximum (10485760)
        at org.apache.tomcat.util.http.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:811) ~[tomcat-embed-core-8.5.15.jar:8.5.15]
...

En regardant le message du journal, il semble que «MaxUploadSizeExceededException» s'est produit, ajoutons donc un processus de gestion des exceptions pour «MaxUploadSizeExceededException».

package com.example.spring5webdemo;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartFile;

@RestController
public class UploadRestController {
	@PostMapping("/upload")
	String upload(MultipartFile file) {
		return file.getOriginalFilename() + " is uploaded !";
	}
	@ExceptionHandler(MaxUploadSizeExceededException.class) //★★★ Ajout de la gestion des exceptions
	@ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
	String handleMaxUploadSizeExceededException() {
		return "Upload file size is too large.";
	}
}

De plus ... L'analyse des demandes en plusieurs parties est effectuée (analyse différée) après la détermination de la méthode Handler. Cela permettra à la méthode @ ExceptionHandler de gérer les exceptions liées au téléchargement de fichiers.

src/main/resources/application.properties(Activer l'analyse des délais pour les demandes en plusieurs parties)


spring.servlet.multipart.resolve-lazily=true

Redémarrez Spring Boot et essayez à nouveau de télécharger un fichier de plus de 1 Mo.

$ curl -s -D - -X POST -F file=@target/spring5-web-demo-0.0.1-SNAPSHOT.jar http://localhost:8080/upload
HTTP/1.1 100 

HTTP/1.1 413 
Content-Type: text/plain;charset=UTF-8
Content-Length: 30
Date: Tue, 16 May 2017 17:01:38 GMT
Connection: close

Upload file size is too large.

Note:

Au fait ... Lors de l'utilisation de Spring Boot, la fonction de téléchargement de fichiers de Servlet 3.0 est activée par défaut, la taille maximale de chaque fichier est de 1M et la taille maximale de la requête entière est limitée à 10M. Vous pouvez modifier la taille maximale dans ʻapplication.properties`.

#Taille maximale par fichier
spring.servlet.multipart.max-file-size=2MB
#Taille maximale de la demande
spring.servlet.multipart.max-request-size=20MB

Un mécanisme de résolution de type de support unifié est pris en charge: thumbsup:

[SPR-14484] [SPR-14908]: Unification Un mécanisme de résolution du type de média (classe MediaTypeFactory) est ajouté et le code dépendant de JAF (Java Beans Activation Framework) est éliminé. Dans la nouvelle classe, résolvez les types de média selon la définition de / org / springframework / http / mime.types (type de média et fichier de mappage d'extension) stocké dans spring-web. ..

Par exemple, si vous créez la méthode Handler suivante et téléchargez le fichier pom.xml ...

@PostMapping("/upload")
String upload(MultipartFile file) {
	MediaTypeFactory.getMediaTypes(file.getOriginalFilename())
		.forEach(System.out::println);
	return file.getOriginalFilename() + " is uploaded !";
}

Les types de média suivants sont envoyés à la console.

application/xml

De plus, vous pouvez personnaliser la définition de mappage fournie par spring-web en créant (copiant) / org / springframework / http / mime.types dans votre projet.

src/main/resources/org/springframework/http/mime.types


...(réduction)
text/xml    xml

Si vous téléchargez à nouveau le fichier pom.xml ... Les types de média suivants seront sortis sur la console.

application/xml
text/xml

Les données peuvent être liées à des objets immuables: thumbsup:

[SPR-15199]: permet la liaison de données des paramètres de requête à des objets immuables. Si les informations de nom de paramètre existent dans l'argument du constructeur (si -d ou -paramètres est ajouté à l'option de compilation), la valeur du paramètre de demande qui correspond au nom de paramètre de l'argument du constructeur et le nom du paramètre de demande est liée. Sera fait.

Note:

S'il y a plusieurs constructeurs, le constructeur par défaut sera appelé comme précédemment. De plus, si vous ne voulez pas laisser les informations de nom de paramètre dans l'argument constructeur, vous pouvez utiliser @ java.beans.ConstructorProperties fourni par Java Beans pour mapper l'argument constructeur sur le paramètre request.

Créons maintenant une classe immuable et spécifions cette classe comme argument de la méthode Handler.

package com.example.spring5webdemo;

import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.NotNull;
import java.time.LocalDate;

@RestController
public class ImmutableObjectRestController {

	@GetMapping("/immutable")
	Query search(@Validated Query query) {
		return query;
	}

	public static class Query {

		@NotNull
		private final String name;
		private final String mail;
		private final String tel;
		private final LocalDate baseDate;

		public Query(String name, String mail, String tel,
				@DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate baseDate) {
			this.name = name;
			this.mail = mail;
			this.tel = tel;
			this.baseDate = baseDate;
		}

		public String getName() {
			return name;
		}

		public String getMail() {
			return mail;
		}

		public String getTel() {
			return tel;
		}

		public LocalDate getBaseDate() {
			return baseDate;
		}

	}

}

Dans l'état par défaut, si LocalDate est défini sur JSON, la lisibilité est très mauvaise, nous allons donc afficher une valeur formatée comme ça.

pom.xml


<dependency>
  <groupId>com.fasterxml.jackson.datatype</groupId>
  <artifactId>jackson-datatype-jsr310</artifactId> <!-- JSR-Ajout d'un module pour prendre en charge la sérialisation et la désérialisation de 310 classes-->
</dependency>

src/main/resources/application.properties


spring.jackson.serialization.write-dates-as-timestamps=false

Tout d'abord, essayez d'envoyer une requête avec toutes les valeurs normales.

$ curl -s -D - http://localhost:8080/immutable?name=kazuki43zoo\&[email protected]\&tel=09012345678\&baseDate=2017-08-01
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:02:24 GMT

{"name":"kazuki43zoo","mail":"[email protected]","tel":"09012345678","baseDate":"2017-08-01"}

Vous êtes lié correctement. Ensuite, vérifions si la validation est effectuée sans spécifier nom.

$ curl -s -D - http://localhost:8080/[email protected]\&tel=09012345678\&baseDate=2017-08-01
HTTP/1.1 400 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:04:25 GMT
Connection: close

{"timestamp":"2017-05-16T17:04:25.515+0000","status":400,"error":"Bad Request","errors":[{"codes":["NotNull.query.name","NotNull.name","NotNull.java.lang.String","NotNull"],"arguments":[{"codes":["query.name","name"],"arguments":null,"defaultMessage":"name","code":"name"}],"defaultMessage":"may not be null","objectName":"query","field":"name","rejectedValue":null,"bindingFailure":false,"code":"NotNull"}],"message":"Validation failed for object='query'. Error count: 1","path":"/immutable"}

Cela semble également fonctionner correctement. Enfin, spécifiez une date non valide pour baseDate ( LocalDate) et vérifiez si une erreur de liaison se produit.

$ curl -s -D - http://localhost:8080/immutable?name=kazuki43zoo\&[email protected]\&tel=09012345678\&baseDate=2017-08-32
HTTP/1.1 400 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:05:10 GMT
Connection: close

{"timestamp":"2017-05-16T17:05:10.161+0000","status":400,"error":"Bad Request","message":"Failed to convert value of type 'java.lang.String[]' to required type 'java.time.LocalDate'; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.time.LocalDate] for value '2017-08-32'; nested exception is java.lang.IllegalArgumentException: Parse attempt failed for value [2017-08-32]","path":"/immutable"}

Il semble que cela fonctionne également correctement.

Warning:

J'ai changé le style pour recevoir les informations d'erreur au moment de l'erreur de liaison et de l'erreur de validation en tant que BindingResult comme suit ... J'ai pu recevoir l'erreur de validation, mais une exception s'est produite au moment de l'erreur de liaison Je n'ai pas pu le recevoir. (Est-ce une spécification !? ...)

@GetMapping("/immutable")
Query search(@Validated Query query, BindingResult bindingResult) {
	// ...
	return query;
}

Je n'étais pas sûr s'il s'agissait d'une spécification ou d'un bug, j'ai donc donné un problème (SPR-15542) à Spring JIRA.

Jackson 2.9 pris en charge: pouce levé:

[SPR-14925]: Apparemment Jackson 2.9 ) Semble être la version requise sur Spring Framework 5.0. Jackson 2.9 n'a pas été officiellement publié pour le moment (14 mai 2017), mais il semble que Spring Framework 5.0 sera publié au moment de la sortie officielle. En regardant les changements dans Spring Framework ... À partir de Jackson 2.9, si l'erreur se produit en raison d'un problème avec l'implémentation côté serveur (implémentation POJO), ʻInvalidDefinitionException` sera levée, donc cette exception s'est produite. Il semble que je l'ai parfois corrigé pour qu'il s'agisse d'une erreur de serveur (500 Internal Server Error) au lieu d'une erreur de client (400 Bad Request).

Liaison JSON JSR-367 prise en charge: thumbsup:

[SPR-14923]: composant Java EE 8 JSON Binding Il est pris en charge et peut être utilisé comme alternative à Jackson et GSON.

Tout d'abord, Spring Boot (spring-boot-starter-web) dépend de Jackson, donc si vous voulez utiliser JSON-B, vous devez supprimer Jackson des bibliothèques dépendantes et ajouter la bibliothèque pour JSON-B. Il y a.

pom.xml


<dependencies>
  <!-- ... -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
      <exclusion>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId> <!--Exclure Jackson-->
      </exclusion>
    </exclusions>
  </dependency>

  <!-- ... -->

  <!--Cette entrée est l'implémentation de référence officielle de Yasson(+JSON-Mise en œuvre P)Ajouter-->
  <dependency>
    <groupId>org.eclipse</groupId>
    <artifactId>yasson</artifactId>
    <version>1.0.0-M2</version>
  </dependency>
  <dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.json</artifactId>
    <version>1.1.0-M2</version>
  </dependency>
</dependencies>

<!-- ... -->
<repositories>
  <!-- ... -->
  <!--Ajout du référentiel de versions de Yasson(Pour pouvoir obtenir la version jalon) -->
  <repository>
    <id>yasson-releases</id>
    <name>Yasson Release repository</name>
    <url>https://repo.eclipse.org/content/repositories/yasson-releases/</url>
  </repository>
</repositories>

Ensuite, créez la classe Controller. Il n'y a pas de précautions particulières pour créer la classe Controller, mais il semble qu'elle ne prend pas en charge la liaison à des classes immuables.

package com.example.spring5webdemo;

import java.time.LocalDate;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class JsonbRestController {

	@PostMapping("/jsonb")
	Resource post(@RequestBody Resource resource) {
		return resource;
	}

	public static class Resource {

		private String name;
		private String mail;
		private String tel;
		private LocalDate baseDate;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getMail() {
			return mail;
		}

		public void setMail(String mail) {
			this.mail = mail;
		}

		public String getTel() {
			return tel;
		}

		public void setTel(String tel) {
			this.tel = tel;
		}

		public LocalDate getBaseDate() {
			return baseDate;
		}

		public void setBaseDate(LocalDate baseDate) {
			this.baseDate = baseDate;
		}

	}

}

Lorsque vous accédez au point de terminaison que vous avez créé, vous pouvez voir que le JSON demandé a été converti (désérialisé) en objet Java et que cet objet a été converti (sérialisé) en JSON.

$ curl -s -D - -X POST -H "Content-Type:application/json" -d '{"name":"kazuki43zoo","mail":"[email protected]","tel":"09012345678","baseDate":"2017-08-01"}' http://localhost:8080/jsonb
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Content-Length: 97
Date: Tue, 16 May 2017 17:08:40 GMT

{"baseDate":"2017-08-01","mail":"[email protected]","name":"kazuki43zoo","tel":"09012345678"}

JSON-B est également disponible dans RestTemplate, une classe pour les clients HTTP. Vous pouvez également utiliser JSON-B avec le client HTTP de test (TestRestTemplate) fourni par Spring Boot.

package com.example.spring5webdemo;

import java.time.LocalDate;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) //★★★ Démarrez le serveur en utilisant un port libre lors de l'exécution du test
public class JsonbTests {

	@LocalServerPort private int port; //★★★ Le port de démarrage du serveur est injecté

	@Autowired TestRestTemplate testRestTemplate; //★★★ Le client HTTP est injecté pour accéder au port de démarrage du serveur

	@Test
	public void testUsingRestTemplate() {
		LocalDate now = LocalDate.now();

		RestOperations restOperations = new RestTemplate(); //★★★ JSON dans l'état par défaut-Le composant lié avec B est enregistré
		Resource request = new Resource();
		request.setName("kazuki43zoo");
		request.setMail("[email protected]");
		request.setTel("09012345678");
		request.setBaseDate(now);

		Resource response = restOperations.postForObject("http://localhost:" + port + "/jsonb", request, Resource.class);

		Assertions.assertThat(response.getName()).isEqualTo("kazuki43zoo");
		Assertions.assertThat(response.getMail()).isEqualTo("[email protected]");
		Assertions.assertThat(response.getTel()).isEqualTo("09012345678");
		Assertions.assertThat(response.getBaseDate()).isEqualTo(now);

	}

	@Test
	public void testUsingTestRestTemplate() {
		LocalDate now = LocalDate.now();

		Resource request = new Resource();
		request.setName("kazuki43zoo");
		request.setMail("[email protected]");
		request.setTel("09012345678");
		request.setBaseDate(now);

		Resource response = testRestTemplate.postForObject("/jsonb", request, Resource.class); //★★★ Si vous utilisez le client HTTP pour les tests, vous pouvez spécifier l'URL à partir du chemin.

		Assertions.assertThat(response.getName()).isEqualTo("kazuki43zoo");
		Assertions.assertThat(response.getMail()).isEqualTo("[email protected]");
		Assertions.assertThat(response.getTel()).isEqualTo("09012345678");
		Assertions.assertThat(response.getBaseDate()).isEqualTo(now);

	}

	public static class Resource {

		private String name;
		private String mail;
		private String tel;
		private LocalDate baseDate;

		public String getName() {
			return name;
		}

		public void setName(String name) {
			this.name = name;
		}

		public String getMail() {
			return mail;
		}

		public void setMail(String mail) {
			this.mail = mail;
		}

		public String getTel() {
			return tel;
		}

		public void setTel(String tel) {
			this.tel = tel;
		}

		public LocalDate getBaseDate() {
			return baseDate;
		}

		public void setBaseDate(LocalDate baseDate) {
			this.baseDate = baseDate;
		}

	}

}

Prise en charge de Google Protobuf 3.x: thumbsup:

[SPR-13589]: ProtobufHttpMessageConverter prend désormais en charge Google Protobuf 3.x. Apparemment, les formats pris en charge par défaut (com.google.protobuf: protobuf-java) sont uniquement" ʻapplication / x-protobuf"et"text / plain" et d'autres formats ("ʻapplication / Pour json "" ʻapplication / xml""text / html"), des bibliothèques séparées (" com.google.protobuf: protobuf-java-util(officiel) "et"com.googlecode.protobuf-" Il semble qu'il soit nécessaire d'ajouter java-format: protobuf-java-format` (fait par un tiers) ").

Les classes Reactor 3.1 et RxJava 1.3 / 2.1 peuvent être renvoyées avec la méthode Handler: thumbsup:

[SPR-15365]: En relation avec le support du modèle de programmation réactif (Spring WebFlux décrit plus loin), Spring MVC dispose également de la classe Reactor 3.1 (Flux). Les classes , Mono), RxJava 1.3 et 2.1 (ʻObservable, Sigle, Flowable`, etc.) peuvent maintenant être traitées comme la valeur de retour de la méthode Handler. Lorsque ces classes (ci-après dénommées «classe réactive») sont renvoyées, Spring MVC est un mécanisme à traiter à l'aide du traitement asynchrone de Spring MVC (fonction de liaison avec traitement asynchrone pris en charge depuis Servlet 3.0). Il est devenu.

Publié précédemment "[Understanding Asynchronous Requests on Spring MVC (+ Spring Boot)]](http://qiita.com/kazuki43zoo/items/ce88dea403c596249e8a#spring-mvc%E3%81%8C%E3%82% B5% E3% 83% 9D% E3% 83% BC% E3% 83% 88% E3% 81% 97% E3% 81% A6% E3% 81% 84% E3% 82% 8B% E9% 9D% 9E% E5% 90% 8C% E6% 9C% 9F% E5% 87% A6% E7% 90% 86% E3% 81% AE% E6% 96% B9% E5% BC% 8F) ", Spring MVC Le traitement asynchrone de est divisé selon les deux méthodes suivantes.

La méthode utilisée dépend de la combinaison du type de classe Reactive et du type de média à renvoyer. Plus précisément, "[Spring Framework Reference -Async Requests with Reactive Types-](http://docs.spring.io/spring/docs/5.0.0.RC1/spring-framework-reference/web.html#mvc -ann-async-réactifs-types) ".

Maintenant, retournons en fait la classe Reactive. Étant donné que cette entrée utilise la classe Reactor utilisée par Spring WebFlux, ajoutez d'abord Reactor à la bibliothèque dépendante.

<dependency>
  <groupId>io.projectreactor</groupId>
  <artifactId>reactor-core</artifactId>
  <version>3.1.0.M1</version>
</dependency>

Ensuite, implémentez la méthode Handler qui retourne le "Mono" et le "Flux" du Reactor.

package com.example.spring5webdemo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.Duration;
import java.util.Random;

@RestController
public class ReactiveRestController {

	@GetMapping("/reactor/mono")
	Mono<Resource> getMono() {
		//Une fois abonné, renvoyez la ressource avec l'ID défini sur un nombre aléatoire
		//Appelez subscribeOn pour que le traitement de l'abonné soit dans un thread séparé
		//    ->Vous n'êtes pas obligé de le faire dans un thread distinct pour traiter cet exemple de code,
		//    ->En fait, on suppose qu'un "traitement lourd = traitement lié au processeur" est effectué.
		return Mono.fromSupplier(() -> new Resource(new Random().nextInt()))
				.subscribeOn(Schedulers.parallel());
	}

	@GetMapping("/reactor/flux")
	Flux<Resource> getFlux() {
		//Une fois abonné, définissez Ressource avec un identifiant de 0 à 0.Retour toutes les 5 secondes
		//Si l'intervalle est utilisé, le traitement de l'abonné sera effectué dans un thread séparé.
		return Flux.from(Flux.range(0, 5)).zipWith(Flux.interval(Duration.ofMillis(500)))
				.map(tuple -> new Resource(tuple.getT1()));
	}

	static class Resource {
		private final int id;
		private Resource(int id) {
			this.id = id;
		}
		public int getId() {
			return id;
		}
	}

}

Enfin, accédons à la méthode Handler que nous avons créée.

Mono+json


$ curl -s -D - http://localhost:8080/reactor/mono
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:10:34 GMT

{"id":129024480}

Flux+event-stream


$ curl -s -D - http://localhost:8080/reactor/flux -H Accept:text/event-stream
HTTP/1.1 200 
Content-Type: text/event-stream;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:10:55 GMT

data:{"id":0}

data:{"id":1}

data:{"id":2}

data:{"id":3}

data:{"id":4}

Flux+stream+json


$ curl -s -D - http://localhost:8080/reactor/flux -H Accept:application/stream+json
HTTP/1.1 200 
Content-Type: application/stream+json
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:11:22 GMT

{"id":0}
{"id":1}
{"id":2}
{"id":3}
{"id":4}

Au fait ... Si vous retournez Flux avec ʻapplication / json` ...

$ curl -s -D - http://localhost:8080/reactor/flux -H Accept:application/json
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 17:11:48 GMT

[{"id":0},{"id":1},{"id":2},{"id":3},{"id":4}]

Et le JSON recevra une réponse une fois que toutes les données auront été transmises à l'abonné (environ 2,5 secondes plus tard).

ParsingPathMatcher est ajouté: thumbsup:

[SPR-14544]: ParsingPathMatcher est ajouté comme alternative à ʻAntPathMatcher. Spring MVC utilise ʻAntPathMatcher par défaut, mais les composants liés à Spring WebFlux semblent utiliser ParsingPathMatcher par défaut. Je ne l'ai pas vu correctement ...: sweat_smile: ʻIl semble que ce qui pourrait être exprimé avec AntPathMatcher peut aussi être exprimé avec ParsingPathMatcher, et ParsingPathMatcherest nouvellement appelé"{* nom de la variable}` " Les expressions sont prises en charge.

Prenons l'exemple suivant ... La spécification de chemin d'accès est la même que "/ users / **", mais si vous utilisez "{* nom de variable}", ce sera " / La partie "" peut être traitée comme une variable. Notez que vous ne pouvez pas spécifier de caractères après " { nom de la variable} " tels que " / users / { userPaths} .json` ".

package com.example.spring5webdemo;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.springframework.util.PathMatcher;
import org.springframework.web.util.ParsingPathMatcher;

public class PathMatcherTests {

	@Test
	public void parsingPathMatcher() {
		PathMatcher pathMatcher = new ParsingPathMatcher();
		String pattern = "/users/{*userPaths}";
		{
			String path = "/users/kazuki43zoo/name";
			Assertions.assertThat(pathMatcher.match(pattern, path));
			Assertions.assertThat(pathMatcher.extractUriTemplateVariables(pattern, path))
				.containsEntry("userPaths", "/kazuki43zoo/name");
		}
		{
			String path = "/users/kazuki43zoo";
			Assertions.assertThat(pathMatcher.match(pattern, path));
			Assertions.assertThat(pathMatcher.extractUriTemplateVariables(pattern, path))
				.containsEntry("userPaths", "/kazuki43zoo");
		}
		{
			String path = "/users/";
			Assertions.assertThat(pathMatcher.match(pattern, path));
			Assertions.assertThat(pathMatcher.extractUriTemplateVariables(pattern, path))
				.containsEntry("userPaths", "/");
		}
		{
			String path = "/users";
			Assertions.assertThat(pathMatcher.match(pattern, path));
			Assertions.assertThat(pathMatcher.extractUriTemplateVariables(pattern, path))
				.containsEntry("userPaths", "");
		}
	}

}

Si vous souhaitez utiliser ParsingPathMatcher dans le traitement de correspondance des requêtes Spring MVC, vous pouvez le faire en définissant le Bean suivant.

@Bean
public WebMvcConfigurer webMvcConfigurer() {
	return new WebMvcConfigurer() {
		@Override
		public void configurePathMatch(PathMatchConfigurer configurer) {
			//★★★ Appliquer ParsingPathMatcher
			configurer.setPathMatcher(new ParsingPathMatcher());
			//★★★ Désactiver useSuffixPatternMatch
			//★★★ Lorsque useSuffixPatternMatch est activé, "" à la fin du modèle pendant le traitement de la correspondance des demandes`.*`Est donné et une erreur se produit pendant l'analyse de modèle
			configurer.setUseSuffixPatternMatch(false);
		}
	};
}
package com.example.spring5webdemo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PathMatcherRestController {

	@GetMapping("/path-matcher/{*paths}") // ★★★ {*Nom de variable}Pour recevoir une valeur de variable comme argument de méthode en utilisant
	String get(@PathVariable String paths) {
		return paths;
	}

}
$ curl -s -D - http://localhost:8080/path-matcher/a/b/c
HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 6
Date: Tue, 16 May 2017 19:41:41 GMT

/a/b/c

Note:

Au fait ... Quand j'ai utilisé "{* nom de la variable}" dans Spring WebFlux où ParsingPathMatcher est appliqué par défaut, une erreur s'est produite dans le processus de mappage de la demande ...: déçu_relieved: Lorsque vous utilisez "{* nom de la variable}", est-ce un bogue qui doit être défini sur "ʻuseSuffixPatternMatch = false`"? J'en ai envie, j'ai donc créé Spring JIRA (SPR-15558).

RedirectAttributes peut être reçu par la méthode @ ExceptionHandler: thumbsup:

[SPR-14651]: Vous pouvez maintenant recevoir RedirectAttributes comme argument de la méthode @ ExceptionHandler et rediriger vers via RedirectAttributes. Vous pourrez lier des données avec.

Note:

En regardant le problème, il est également rétroporté vers Spring Framework 4.3 ... Je me demande si c'est un problème ...

Normalement, il est préférable de créer un écran et de le vérifier, mais ... Il est difficile de créer un écran, donc vérifiez en utilisant cURL.

package com.example.spring5webdemo;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
public class RedirectAttributesController {

	@GetMapping("/redirect/{id}")
	@ResponseBody
	String getIdWithMessage(@PathVariable int id, @ModelAttribute("message") String message) { //La valeur définie dans RedirectAttributes peut être obtenue comme argument de méthode
		return id + " : " + message;
	}

	@GetMapping("/redirect")
	String get() {
		throw new MyException("error.", 100);
	}

	@ExceptionHandler
	String handleMyException(MyException e, RedirectAttributes redirectAttributes) {
		redirectAttributes.addFlashAttribute("message", e.getMessage()); //★★★ Définir le message dans la portée Flush
		redirectAttributes.addAttribute("id", e.getId()); //★★★ Variable de chemin du chemin de redirection "{id}Définissez la valeur à intégrer
		return "redirect:/redirect/{id}";
	}

	private static class MyException extends RuntimeException {
		private final int id;
		public MyException(String message, int id) {
			super(message);
			this.id = id;
		}
		public int getId() {
			return id;
		}
	}

}
$ curl -s -D - -L http://localhost:8080/redirect
HTTP/1.1 302 
Set-Cookie: JSESSIONID=13743780747DC2A73F038F2E0DECFDDD; Path=/; HttpOnly
Location: http://localhost:8080/redirect/100;jsessionid=13743780747DC2A73F038F2E0DECFDDD
Content-Language: ja-JP
Content-Length: 0
Date: Tue, 16 May 2017 21:46:50 GMT

HTTP/1.1 200 
Content-Type: text/plain;charset=UTF-8
Content-Length: 12
Date: Tue, 16 May 2017 21:46:50 GMT

100 : error.

ResponseStatusException est ajouté: thumbsup:

[SPR-14895]: ResponseStatusException créé pour Spring WebFlux est maintenant disponible dans Spring MVC, avec le code d'état et la phrase de raison En spécifiant et en générant / lançant une exception, il devient possible de déterminer l'état HTTP pour répondre.

package com.example.spring5webdemo;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;

@RestController
public class ResponseStatusExceptionRestController {

	@GetMapping("/response-status")
	void get() {
		throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "request invalid."); //★★★ Lancez une exception en spécifiant un code de statut arbitraire et une phrase de raison
	}

}
$ curl -s -D - -L http://localhost:8080/response-status
HTTP/1.1 400 
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 16 May 2017 22:34:21 GMT
Connection: close

{"timestamp":"2017-05-16T22:34:21.909+0000","status":400,"error":"Bad Request","message":"request invalid.","path":"/response-status"}

Les objets requis pour "l'internationalisation" et la "fragmentation du modèle" sont maintenant liés dans ScriptTemplateView: thumbsup:

[SPR-15064]: "Internationalisation des messages" dans ScriptTemplateView (Voir l'implémentation à l'aide du moteur de script de JSR-223)" Et les objets (Locale, MessageSource, etc.) nécessaires pour réaliser la "fragmentation du modèle" seront passés du côté du moteur de modèle. L'implémentation proprement dite pour «l'internationalisation» et les «modèles de fragmentation» n'est pas fournie par Spring.

Dans cette entrée, "nashorn" fourni par Java SE est utilisé comme moteur de script, et "moustache.js" est utilisé comme moteur de modèle. Et la "fragmentation du modèle" sera réalisée.

Tout d'abord, configurez pour utiliser ScriptTemplateView.

pom.xml(Installez moustachejs)


<dependency>
  <groupId>org.webjars.bower</groupId>
  <artifactId>mustache</artifactId>
  <version>2.3.0</version> <!--Dernière version au moment de la rédaction-->
</dependency>
package com.example.spring5webdemo;

import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.script.ScriptTemplateConfigurer;

import java.util.Optional;

@Configuration
public class ScriptTemplateViewConfig implements WebMvcConfigurer {

	private final WebMvcProperties properties;

	public ScriptTemplateViewConfig(WebMvcProperties properties) {
		this.properties = properties;
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry) {
		WebMvcProperties.View view = properties.getView();
		registry.scriptTemplate()
				.prefix(Optional.ofNullable(view.getPrefix()).orElse("classpath:/templates/"))
				.suffix(Optional.ofNullable(view.getSuffix()).orElse(".html"));
	}

	@Bean
	ScriptTemplateConfigurer ScriptTemplateConfigurer() {
		ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
		configurer.setEngineName("nashorn");
		configurer.setScripts("/META-INF/resources/webjars/mustache/2.3.0/mustache.js", "/META-INF/js/render.js");
		configurer.setRenderFunction("render");
		return configurer;
	}
}

Créez un fichier JavaScript pour personnaliser le processus de rendu standard de Moustache. J'ai utilisé le terme de personnalisation, mais en réalité, je viens d'ajouter une fonction personnalisée au modèle et j'ai appelé la fonction render de Moustache.

/main/resources/META-INF/js/render.js


function render(template, model, renderingContext) {
    model["message"] = function () { //★★★ Ajout d'une fonction personnalisée pour "Internationalisation des messages"
        return function (code, render) {
            var messageSource = renderingContext.applicationContext.getBean("messageSource");
            var locale = renderingContext.locale;
            return render(messageSource.getMessage(code.trim(), null, locale));
        }
    };
    model["include"] = function () { //★★★ Ajout d'une fonction personnalisée pour la «fragmentation des modèles»
        return function (viewName, render) {
            return render(renderingContext.templateLoader.apply("templates/" + viewName.trim() + ".html"));
        }
    };
    return Mustache.render(template, model, renderingContext);
}

[RenderingContext](https://github.com/spring-projects/spring-framework/blob/master/spring-webmvc/src/main/java/org/springframework/web/servlet" dans le troisième argument de la méthode de rendu Une instance de /view/script/RenderingContext.java) est transmise, alors récupérez-y les objets nécessaires et traitez-la.

Maintenant, créons un fichier modèle et un contrôleur et exécutons-les.

src/main/resources/templates/script-template/home.html


<html>
<head>
    <title>{{title}}</title>
</head>
<body>

<p>{{body}}</p>

<p>{{#message}}welcome{{/message}}</p> <!--★★★ Émet un message à l'aide d'une fonction personnalisée-->

{{#include}}script-template/footer{{/include}} <!--★★★ Inclure des fragments à l'aide de fonctions personnalisées-->

</body>
</html>

src/main/resources/templates/script-template/footer.html(Fragment)


<footer>
    Copyright (c) 2017 kazuki43zoo@com
</footer>
package com.example.spring5webdemo;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletResponse;

@Controller
public class ScriptTemplateController {

	@GetMapping("/script-template")
	String home(Model model, HttpServletResponse response) {

		model.addAttribute("title", "Sample title")
			.addAttribute("body", "Sample body");

		return "script-template/home";
	}

}

Vous devez également créer un fichier de définition de message.

src/main/resources/messages.properties


welcome=salut!

src/main/resources/messages_en.properties


welcome=Hello!

Lorsque vous démarrez Spring Boot et accédez à http: // localhost: 8080 / script-template ...

$ curl -s -D - http://localhost:8080/script-template
HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Content-Language: ja-JP
Content-Length: 183
Date: Sat, 20 May 2017 01:51:53 GMT

<html>
<head>
    <title>Sample title</title>
</head>
<body>

<p>Sample body</p>

<p>salut!</p>

<footer>
    Copyright (c) 2017 kazuki43zoo.com
</footer>

</body>
</html>

Si vous changez ʻAccept-Language en ʻen (anglais) et accédez à nouveau ...

$ curl -s -D - -H Accept-Language:en http://localhost:8080/script-template
HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Content-Language: en
Content-Length: 171
Date: Sat, 20 May 2017 01:53:46 GMT

<html>
<head>
    <title>Sample title</title>
</head>
<body>

<p>Sample body</p>

<p>Hello!</p>

<footer>
    Copyright (c) 2017 kazuki43zoo.com
</footer>

</body>
</html>

Vous pouvez confirmer que "Internationalisation du message" et "Inclure le fragment" ont été effectués.

Résumé

Cette fois, nous avons présenté les principaux changements liés à WebMVC. Le framework Web du modèle de programmation réactif (Spring WebFlux) a été ajouté à partir de Spring Framework 5.0, et il attire l'attention, mais ... Spring MVC est toujours actif (pour le moment ... j'utilise Spring MVC pour le travail. Je pense qu'il y en aura beaucoup plus: sweat_smile :). La prochaine fois, je présenterai "Changements majeurs liés à Test".

Recommended Posts

Modifications majeures liées à Spring Framework 5.0 Web MVC
Modifications majeures liées au test Spring Framework 5.0
Modifications majeures liées au conteneur Spring Framework 5.0 DI
Spring Framework 5.0 Résumé des principaux changements
Changements majeurs dans la fonctionnalité de base de Spring Framework 5.0
Changements majeurs dans Spring Boot 1.5
Pour recevoir une demande vide avec Spring Web MVC @RequestBody
Modifications lors de la migration de Spring Boot 1.5 vers Spring Boot 2.0
Transition de Struts2 à Spring MVC (contrôleur)
Modifications lors de la migration de Spring Boot 2.0 vers Spring Boot 2.2
[Spring MVC] Comment transmettre des variables de chemin
Cookie SameSite dans Spring Boot (Spring Web MVC + Tomcat)
J'ai essayé de lier JavaFX et Spring Framework.
Le nom officiel de Spring MVC est Spring Web MVC
Mémorandum (Spring Web)
J'ai essayé d'implémenter le téléchargement de fichiers avec Spring MVC
Comment utiliser Struts2 * Spring Framework (plugin Spring) Version de juin 2017