Routing in Play-Anwendungen in Java

1. Übersicht

Routing ist ein gängiges Konzept, das in den meisten Webentwicklungs-Frameworks einschließlich Spring MVC verwendet wird.

Eine Route ist ein URL-Muster, das einem Handler zugeordnet ist. Der Handler kann eine physische Datei sein, z. B. ein herunterladbares Asset in der Webanwendung, oder eine Klasse, die die Anforderung verarbeitet, z. B. ein Controller in einer MVC-Anwendung.

In diesem Tutorial werden wir den Aspekt des Routings bei der Entwicklung von Webanwendungen mit dem Play Framework untersuchen.

2. Setup

Zuerst müssen wir eine Java Play-Anwendung erstellen. Einzelheiten zum Einrichten des Play Frameworks auf einem Computer finden Sie in unserem Einführungsartikel.

Am Ende des Setups sollten wir eine funktionierende Play-Anwendung haben, auf die wir über einen Browser zugreifen können.

3. HTTP-Routing

Woher weiß Play, welchen Controller er konsultieren muss, wenn wir eine HTTP-Anfrage senden? Die Antwort auf diese Frage liegt in der Konfigurationsdatei app / conf / routen .

Der Router von Play übersetzt HTTP-Anforderungen in Aktionsaufrufe. HTTP-Anforderungen werden als Ereignisse in der MVC-Architektur betrachtet, und der Router reagiert darauf, indem er in der Routendatei nachfragt, für welchen Controller und welche Aktion in diesem Controller ausgeführt werden soll.

Jedes dieser Ereignisse stellt einem Router zwei Parameter zur Verfügung: einen Anforderungspfad mit seiner Abfragezeichenfolge und die HTTP-Methode der Anforderung.

4. Grundlegendes Routing mit Spiel

Damit der Router seine Arbeit erledigen kann, muss die Datei conf / routen Zuordnungen von HTTP-Methoden und URI-Mustern zu geeigneten Controller-Aktionen definieren:

GET / controllers.HomeController.index GET / assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Alle Routendateien müssen auch die statischen Ressourcen im Ordner " play-routing / public" zuordnen, die dem Client auf dem Endpunkt / assets zur Verfügung stehen .

Beachten Sie die Syntax von HTTP Routen definiert, und die HTTP - Methode Raum URI Musterraum Controller - Aktion.

5. URI-Muster

In diesem Abschnitt werden wir ein wenig auf URI-Muster eingehen.

5.1. Statische URI-Muster

Die ersten drei URI-Muster oben sind statisch. Dies bedeutet, dass die Zuordnung der URLs zu Ressourcen ohne weitere Verarbeitung in den Controller-Aktionen erfolgt.

Solange eine Controller-Methode aufgerufen wird, gibt sie eine statische Ressource zurück, deren Inhalt vor der Anforderung bestimmt wird.

5.2. Dynamische URI-Muster

Das letzte URI-Muster oben ist dynamisch. Dies bedeutet, dass die Controller-Aktion, die eine Anforderung für diese URIs bearbeitet, einige Informationen aus der Anforderung benötigt, um die Antwort zu bestimmen. Im obigen Fall wird ein Dateiname erwartet.

Die normale Abfolge von Ereignissen besteht darin, dass der Router ein Ereignis empfängt, den Pfad aus der URL auswählt, seine Segmente dekodiert und an den Controller weiterleitet.

Pfad- und Abfrageparameter werden dann als Parameter in die Controller-Aktion eingefügt. Wir werden dies in den nächsten Abschnitten anhand eines Beispiels demonstrieren.

6. Erweitertes Routing mit Wiedergabe

In diesem Abschnitt werden die erweiterten Optionen für das Routing mithilfe dynamischer URI-Muster ausführlich erläutert.

6.1. Einfache Pfadparameter

Einfache Pfadparameter sind unbenannte Parameter in einer Anforderungs-URL, die nach dem Host und dem Port angezeigt und in der Reihenfolge ihres Auftretens analysiert werden.

Innerhalb Play-Routing / app / HomeController.java , lassen Sie uns eine neue Aktion zu erstellen:

public Result greet(String name) { return ok("Hello " + name); }

Wir möchten in der Lage sein, einen Pfadparameter aus der Anforderungs-URL auszuwählen und ihn dem Variablennamen zuzuordnen.

Der Router erhält diese Werte aus einer Routenkonfiguration.

Öffnen wir also Play-Routing / Conf / Routes und erstellen ein Mapping für diese neue Aktion:

GET /greet/:name controllers.HomeController.greet(name: String)

Beachten Sie, wie wir einen Router darüber informieren, dass der Name ein dynamisches Pfadsegment mit der Doppelpunktsyntax ist, und ihn dann als Parameter an den Aufruf der Begrüßungsaktion übergeben.

Laden wir nun // locahost: 9000 / greet / john in den Browser und wir werden mit Namen begrüßt:

Hello john

Nun ist es so , dass , wenn unsere Aktionsparameter von String - Typ ist, können wir es während der Aktion Aufruf ohne Angabe des Parameters Typ passieren kann , obwohl dies nicht das gleiche für andere Arten ist.

Lassen Sie uns unseren / greet- Endpunkt mit Altersinformationen aufpeppen .

Zurück zur Begrüßungsaktion von HomeController ändern wir sie in:

public Result greet(String name, int age) { return ok("Hello " + name + ", you are " + age + " years old"); }

Und der Weg zu:

GET /greet/:name/:age controllers.HomeController.greet(name: String, age: Integer)

Beachten Sie auch die Scala-Syntax zum Deklarieren einer Variablen, age: Integer . In Java würden wir die Integer- Alterssyntax verwenden. Das Play Framework wurde in Scala erstellt. Folglich gibt es viel Scala-Syntax.

Laden wir // localhost: 9000 / greet / john / 26 :

Hello john, you are 26 years old

6.2. Platzhalter in Pfadparametern

In unserer Routenkonfigurationsdatei lautet die letzte Zuordnung:

GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)

Wir verwenden einen Platzhalter im dynamischen Teil des Pfades. Was wir Play sagen, ist, dass jeder Wert, der die * Datei in der tatsächlichen Anforderung ersetzt, als Ganzes analysiert und nicht wie in anderen Fällen von Pfadparametern dekodiert werden sollte.

In diesem Beispiel ist der Controller ein integrierter Assets- Controller , mit dem der Client Dateien aus dem Ordner " Play-Routing / Public " herunterladen kann . Wenn wir //localhost:9000/assets/images/favicon.png laden , sollte das Bild des Play-Favicons im Browser angezeigt werden, da es im Ordner / public / images vorhanden ist .

Lassen Sie uns unsere eigene Beispielaktion in HomeController.java erstellen :

public Result introduceMe(String data) { String[] clientData = data.split(","); return ok("Your name is " + clientData[0] + ", you are " + clientData[1] + " years old"); }

Notice that in this action, we receive one String parameter and apply our logic to decode it. In this case, the logic is to split a comma-delimited String into an array. Previously, we depended on a router to decode this data for us.

With wildcards, we are on our own. We're hoping that the client gets our syntax correct while passing this data in. Ideally, we should validate the incoming String before using it.

Let's create a route to this action:

GET /*data controllers.HomeController.introduceMe(data)

Now load the URL //localhost:9000/john,26. This will print:

Your name is john, you are 26 years old

6.3. Regex in Path Parameters

Just like wildcards, we can use regular expressions for the dynamic part. Let's add an action that receives a number and returns its square:

public Result squareMe(Long num) { return ok(num + " Squared is " + (num * num)); }

Now we'll add its route:

GET /square/$num controllers.HomeController.squareMe(num:Long)

Let's place this route below the introduceMe route to introduce a new concept. We can only handle routes where the regex part is a positive integer with this routing configuration.

Now if we have placed the route as instructed in the previous paragraph, and we load //localhost:9000/square/2, we should be greeted with an ArrayIndexOutOfBoundsException:

If we check the error logs in the server console, we will realize that the action call was actually performed on introduceMe action rather than squareMe action. As said earlier about wildcards, we are on our own and we did not validate incoming data.

Instead of a comma-delimited string, the introduceMe method was called with the string “square/2“. Consequently, after splitting it, we got an array of size one. Trying to reach index 1 then threw the exception.

Naturally, we would expect the call to be routed to the squareMe method. Why was it routed to introduceMe? The reason is a Play feature we'll cover next called Routing Priority.

7. Routing Priority

If there is a conflict between routes as there is between squareMe and introduceMe, then Play picks the first route in declaration order.

Why is there a conflict? Because of the wildcard context path /*data matches any request URL apart from the base path /. So every route whose URI pattern uses wildcards should appear last in order.

Now let's change the declaration order of the routes such that the introduceMe route comes after squareMe and reload:

2 Squared is 4

To test the power of regular expressions in a route, try loading //locahost:9000/square/-1, a router will fail to match the squareMe route. Instead, it will match introduceMe, and we'll get the ArrayIndexOutOfBoundsException again.

This is because -1 does not match by the provided regular expression, neither does any alphabetic character.

8. Parameters

Up until this point, we've covered the syntax for declaring parameter types in the routes file.

In this section, we'll look at more options available to us when dealing with parameters in routes.

8.1. Parameters With Fixed Values

Sometimes we'll want to use a fixed value for a parameter. This is our way of telling Play to use the path parameter provided or if the request context is the path /, then use a certain fixed value.

Another way of looking at it is having two endpoints or context paths leading to the same controller action — with one endpoint requiring a parameter from the request URL and defaulting to the other in case the said parameter is absent.

To demonstrate this, let's add a writer() action to the HomeController:

public Result writer() { return ok("Routing in Play by Baeldung"); }

Assuming we don't always want our API to return a String:

Routing in Play by Baeldung

We want to control it by sending the name of an author of the article along with the request, defaulting to the fixed value Baeldung only if the request does not have the author parameter.

So let's further change the writer action by adding a parameter:

public Result writer(String author) { return ok("REST API with Play by " + author); }

Let's also see how to add a fixed value parameter to the route:

GET /writer controllers.HomeController.writer(author = "Baeldung") GET /writer/:author controllers.HomeController.writer(author: String)

Notice how we now have two separate routes all leading to the HomeController.index action instead of one.

When we now load //localhost:9000/writer from the browser we get:

Routing in Play by Baeldung

And when we load //localhost:9000/writer/john, we get:

Routing in Play by john

8.2. Parameters With Default Values

Apart from having fixed values, parameters can also have default values. Both provide fallback values to the controller action parameters in case the request does not provide the required values.

The difference between the two is that fixed values are used as a fallback for path parameters while default values are used as a fallback for query parameters.

Path parameters are of the form //localhost:9000/param1/param2 and query parameters are of the form //localhost:9000/?param1=value1¶m2=value2.

The second difference is in the syntax of declaring the two in a route. Fixed value parameters use the assignment operator as in:

author = "Baeldung"

While default values use a different type of assignment:

author ?= "Baeldung"

We use the ?= operator which conditionally assigns Baeldung to author in case author is found to contain no value.

To have a complete demonstration, let's create the HomeController.writer action. Let's say, apart from the author's name which is a path parameter, we also want to pass author id as a query parameter which should default to 1 if not passed in the request.

We'll change writer action to:

public Result writer(String author, int id) { return ok("Routing in Play by: " + author + " ID: " + id); }

und der Autor führt zu:

GET /writer controllers.HomeController.writer(author="Baeldung", id: Int ?= 1) GET /writer/:author controllers.HomeController.writer(author: String, id: Int ?= 1)

Jetzt laden wir // localhost: 9000 / writer und sehen:

Routing in Play by: Baeldung ID: 1

Wenn Sie // localhost: 9000 / writer? Id = 10 treffen, erhalten Sie:

Routing in Play by: Baeldung ID: 10

Was ist mit // localhost: 9000 / writer / john ?

Routing in Play by: john ID: 1

Und schließlich gibt // localhost: 9000 / writer / john? Id = 5 zurück:

Routing in Play by: john ID: 5

9. Fazit

In diesem Artikel haben wir den Begriff Routing in Play-Anwendungen untersucht. Wir haben auch einen Artikel zum Erstellen einer RESTful-API mit Play Framework, in dem die Routing-Konzepte in diesem Tutorial in einem praktischen Beispiel angewendet werden.

Wie üblich ist der Quellcode für dieses Tutorial auf GitHub verfügbar.