1. Overview
1.概述
Routing is a common concept that appears in most web development frameworks including Spring MVC.
路由是一个常见的概念,出现在大多数Web开发框架中,包括Spring MVC。
A route is a URL pattern that is mapped to a handler. The handler can be a physical file, such as a downloadable asset in the web application or a class that processes the request, such as a controller in an MVC application.
路由是一个URL模式,它被映射到一个处理程序。处理程序可以是一个物理文件,如网络应用中的可下载资产,或者是一个处理请求的类,如MVC应用中的控制器。
In this tutorial, we’ll explore the aspect of routing in developing web applications with the Play Framework.
在本教程中,我们将探讨使用Play框架开发Web应用程序中的路由方面。
2. Setup
2.设置
First, we’ll need to create a Java Play application. The details on how to set up the Play Framework on a machine are available in our introductory article.
首先,我们需要创建一个Java Play应用程序。关于如何在机器上设置 Play 框架的细节可在我们的介绍性文章中找到。
By the end of the setup, we should have a working Play application that we can access from a browser.
在设置结束时,我们应该有一个可以工作的Play应用程序,我们可以从浏览器访问。
3. HTTP Routing
3.HTTP路由
So how does Play knows which controller to consult whenever we send an HTTP request? The answer to this question lies in the app/conf/routes configuration file.
那么,Play如何知道每当我们发送一个HTTP请求时,要咨询哪个控制器呢?这个问题的答案在于app/conf/routes配置文件。
Play’s router translates HTTP requests into action calls. HTTP requests are considered to be events in MVC architecture and the router reacts to them by consulting the routes file for which controller and which action in that controller to execute.
Play的路由器将HTTP请求翻译成动作调用。HTTP请求在MVC架构中被认为是事件,路由器通过查阅routes文件对它们做出反应,以确定执行哪个控制器和该控制器中的哪个动作。
Each of these events supplies a router with two parameters: a request path with its query string and the request’s HTTP method.
这些事件中的每一个都为路由器提供了两个参数:一个带有查询字符串的请求路径和请求的HTTP方法。
4. Basic Routing With Play
4.基本路由与播放
For the router to do its work, the conf/routes file must define mappings of HTTP methods and URI patterns to appropriate controller actions:
为了让路由器开展工作,conf/routes文件必须定义HTTP方法和URI模式与适当的控制器动作的映射关系。
GET / controllers.HomeController.index
GET / assets/*file controllers.Assets.versioned(path="/public", file: Asset)
All routes files must also map the static resources in the play-routing/public folder available to the client on the /assets endpoint.
Notice the syntax of defining HTTP routes, and the HTTP method space URI pattern space controller action.
所有路由文件还必须映射play-routing/public文件夹中的静态资源,供客户端在/assets端点使用。
注意定义HTTP路由的语法,以及HTTP方法space URI模式space控制器动作。
5. URI Patterns
5 URI模式
In this section, we will expound a little on URI patterns.
在本节中,我们将对URI模式做一些阐述。
5.1. Static URI Patterns
5.1.静态URI模式
The first three URI patterns above are static. This means that mapping of the URLs to resources occurs without any further processing in the controller actions.
上面的前三个URI模式是静态的。这意味着URL到资源的映射发生在控制器动作中没有任何进一步处理。
As long as a controller method is called, it returns a static resource whose content is determined before the request.
只要一个控制器方法被调用,它就会返回一个静态资源,其内容在请求前就已经确定。
5.2. Dynamic URI Patterns
5.2.动态URI模式
The last URI pattern above is dynamic. This means that the controller action servicing a request on these URIs needs some information from the request to determine the response. In the above case, it expects a file name.
上面的最后一个URI模式是动态的。这意味着为这些URI上的请求提供服务的控制器动作需要一些来自请求的信息来确定响应。在上面的例子中,它希望得到一个文件名。
The normal sequence of events is that the router receives an event, picks the path from the URL, decodes its segments, and passes them to the controller.
正常的事件顺序是,路由器收到一个事件,从URL中选取路径,对其分段进行解码,并将它们传递给控制器。
Path and query parameters are then injected into the controller action as parameters. We’ll demonstrate this with an example in the next sections.
路径和查询参数然后作为参数注入到控制器动作中。我们将在接下来的章节中用一个例子来证明这一点。
6. Advanced Routing With Play
6.高级路由与播放
In this section, we’ll discuss advanced options in routing using Dynamic URI Patterns in detail.
在本节中,我们将详细讨论使用动态URI模式进行路由的高级选项。
6.1. Simple Path Parameters
6.1.简单的路径参数
Simple path parameters are unnamed parameters in a request URL that appear after the host and port and are parsed in order of appearance.
简单路径参数是请求URL中未命名的参数,出现在主机和端口之后,并按出现的顺序进行解析。
Inside play-routing/app/HomeController.java, let’s create a new action:
在play-routing/app/HomeController.java中,让我们创建一个新的动作。
public Result greet(String name) {
return ok("Hello " + name);
}
We want to be able to pick a path parameter from the request URL and map it to the variable name.
我们希望能够从请求的URL中选取一个路径参数,并将其映射到变量名称中。
The router will get those values from a route configuration.
路由器将从路由配置中获得这些值。
So, let’s open play-routing/conf/routes and create a mapping for this new action:
所以,让我们打开play-routing/conf/routes,为这个新动作创建一个映射。
GET /greet/:name controllers.HomeController.greet(name: String)
Notice how we inform a router that name is a dynamic path segment with the colon syntax and then go ahead to pass it as a parameter to the greet action call.
注意我们是如何用冒号语法通知路由器name是一个动态路径段,然后继续把它作为参数传递给greet动作调用。
Now, let’s load http://locahost:9000/greet/john in the browser, and we’ll be greeted by name:
现在,让我们在浏览器中加载http://locahost:9000/greet/john,我们会被问候的名字。
Hello john
It so happens that if our action parameter is of string type, we may pass it during the action call without specifying the parameter type, though this is not the same for other types.
碰巧的是,如果我们的动作参数是字符串类型的,我们可以在动作调用过程中传递它而不需要指定参数类型,尽管这对其他类型的参数来说是不一样的。
Let’s spice up our /greet endpoint with age information.
让我们用年龄信息给我们的/greet端点加点料。
Back to HomeController‘s greet action, we’ll change it to:
回到HomeController的问候动作,我们要把它改成。
public Result greet(String name, int age) {
return ok("Hello " + name + ", you are " + age + " years old");
}
And the route to:
以及前往的路线。
GET /greet/:name/:age controllers.HomeController.greet(name: String, age: Integer)
Notice also the Scala syntax for declaring a variable, age: Integer. In Java, we would use the Integer age syntax. The Play Framework is built in Scala. Consequently, there is a lot of scala syntax.
还请注意Scala声明变量的语法,age。Integer。在Java中,我们会使用Integer age语法。Play框架是用Scala构建的。因此,有很多Scala语法。
Let’s load http://localhost:9000/greet/john/26:
让我们加载http://localhost:9000/greet/john/26。
Hello john, you are 26 years old
6.2. Wildcards in Path Parameters
6.2.路径参数中的通配符
In our routes configuration file, the last mapping is:
在我们的路由配置文件中,最后一个映射是。
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
We use a wildcard in the dynamic part of the path. What we are telling Play is that whatever value replaces *file in the actual request should be parsed as a whole and not decoded like in other cases of path parameters.
我们在路径的动态部分使用一个通配符。我们要告诉Play的是,在实际请求中取代*file的任何值都应该作为一个整体被解析,而不是像其他路径参数的情况那样被解码。
In this example, the controller is a built-in one, Assets, which allows the client to download files from the play-routing/public folder. When we load http://localhost:9000/assets/images/favicon.png, we should see the image of the Play favicon in the browser since it’s present in the /public/images folder.
在这个例子中,控制器是一个内置的,Assets,它允许客户端从play-routing/public文件夹下载文件。当我们加载http://localhost:9000/assets/images/favicon.png,我们应该在浏览器中看到Play favicon的图像,因为它存在于/public/images文件夹中。
Let’s create our own example action in HomeController.java:
让我们在HomeController.java中创建我们自己的示例动作。
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.
请注意,在这个动作中,我们接收一个字符串参数,并应用我们的逻辑对其进行解码。在这种情况下,该逻辑是将一个以逗号分隔的String分割成一个数组。以前,我们依靠一个路由器来为我们解码这些数据。
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 http://localhost:9000/john,26. This will print:
现在加载URL http://localhost:9000/john,26。这将打印。
Your name is john, you are 26 years old
6.3. Regex in Path Parameters
6.3.路径参数中的Regex
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<[0-9]+> 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.
让我们把这个路由放在introduceMe路由下面,以引入一个新的概念。我们只能用这种路由配置处理regex部分为正整数的路由。
Now if we have placed the route as instructed in the previous paragraph, and we load http://localhost:9000/square/2, we should be greeted with an ArrayIndexOutOfBoundsException:
现在,如果我们按照上一段的指示放置了路由,并加载http://localhost:9000/square/2,我们应该得到一个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.
如果我们检查服务器控制台的错误日志,我们会意识到行动调用实际上是在introduceMe行动而不是squareMe行动上执行的。正如前面所说的通配符,我们是靠自己,我们没有验证传入的数据。
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.
在调用introduceMe方法时,没有使用以逗号分隔的字符串,而是使用了字符串”square/2“。因此,在分割之后,我们得到一个大小为1的数组。试图到达索引1时,抛出了一个异常。
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.
很自然地,我们会期望这个调用被路由到squareMe方法。为什么它被路由到introduceMe?原因是我们接下来要介绍的一个名为路由优先级的Play功能。
7. Routing Priority
7.路由优先
If there is a conflict between routes as there is between squareMe and introduceMe, then Play picks the first route in declaration order.
如果路线之间有冲突,就像squareMe和introduceMe之间的冲突,那么Play会选择声明顺序中的第一个路线。
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.
为什么会有冲突?因为通配符的上下文路径/*data可以匹配除基本路径/之外的任何请求URL。所以每一个URI模式使用通配符的路由应该按顺序最后出现。
Now let’s change the declaration order of the routes such that the introduceMe route comes after squareMe and reload:
现在让我们改变路由的声明顺序,使introduceMe路由排在squareMe之后并重新加载。
2 Squared is 4
To test the power of regular expressions in a route, try loading http://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.
为了测试正则表达式在路由中的威力,尝试加载http://locahost:9000/square/-1,一个路由器将无法匹配squareMe路由。相反,它将匹配introduceMe,,我们将再次得到ArrayIndexOutOfBoundsException。
This is because -1 does not match by the provided regular expression, neither does any alphabetic character.
这是因为-1与所提供的正则表达式不匹配,任何字母字符也不匹配。
8. Parameters
8.参数[/strong]
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
8.1.有固定值的参数
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.
有时我们想为一个参数使用一个固定值。这是我们告诉Play使用所提供的路径参数的方式,或者如果请求环境是路径/,则使用某个固定值。
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.
另一种方式是有两个端点或上下文路径通向同一个控制器动作–其中一个端点需要从请求的URL中获得一个参数,如果没有上述参数,则默认为另一个。
To demonstrate this, let’s add a writer() action to the HomeController:
为了证明这一点,让我们在HomeController上添加一个writer()动作。
public Result writer() {
return ok("Routing in Play by Baeldung");
}
Assuming we don’t always want our API to return a String:
假设我们并不总是希望我们的API返回一个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.
我们想通过与请求一起发送文章作者的名字来控制它,只有在请求中没有author参数时才默认为固定值Baeldung。
So let’s further change the writer action by adding a parameter:
因此,让我们进一步改变writer动作,添加一个参数。
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.
请注意,我们现在有两条独立的路由,都通向HomeController.index动作,而不是一条。
When we now load http://localhost:9000/writer from the browser we get:
当我们现在从浏览器加载http://localhost:9000/writer时,我们会得到。
Routing in Play by Baeldung
And when we load http://localhost:9000/writer/john, we get:
而当我们加载http://localhost:9000/writer/john时,我们得到。
Routing in Play by john
8.2. Parameters With Default Values
8.2.带有默认值的参数
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 http://localhost:9000/param1/param2 and query parameters are of the form http://localhost:9000/?param1=value1¶m2=value2.
路径参数的形式是http://localhost:9000/param1/param2,查询参数的形式是http://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.
我们使用?=操作符,在发现author不包含任何值时,有条件地将Baeldung分配给author。
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.
为了有一个完整的示范,让我们创建HomeController.writer动作。比方说,除了作者的名字是一个路径参数外,我们还想把作者id作为一个查询参数,如果请求中没有传递,则默认为1。
We’ll change writer action to:
我们要把writer行动改为。
public Result writer(String author, int id) {
return ok("Routing in Play by: " + author + " ID: " + id);
}
and the writer routes to:
和作者的路线。
GET /writer controllers.HomeController.writer(author="Baeldung", id: Int ?= 1)
GET /writer/:author controllers.HomeController.writer(author: String, id: Int ?= 1)
Now loading http://localhost:9000/writer we see:
现在加载http://localhost:9000/writer我们看到。
Routing in Play by: Baeldung ID: 1
Hitting http://localhost:9000/writer?id=10 gives us:
点击http://localhost:9000/writer?id=10 给我们提供。
Routing in Play by: Baeldung ID: 10
What about http://localhost:9000/writer/john?
那么http://localhost:9000/writer/john呢?
Routing in Play by: john ID: 1
And finally, http://localhost:9000/writer/john?id=5 returns:
最后,http://localhost:9000/writer/john?id=5返回。
Routing in Play by: john ID: 5
9. Conclusion
9.结论
In this article, we explored the notion of Routing in Play applications. We also have an article on building a RESTful API with Play Framework where the routing concepts in this tutorial are applied in a practical example.
在这篇文章中,我们探讨了Play应用程序中的路由概念。我们还有一篇关于使用 Play 框架构建 RESTful API 的文章,其中将本教程中的路由概念应用于一个实际示例。
As usual, the source code for this tutorial is available over on GitHub.
像往常一样,本教程的源代码可以在GitHub上获得。