Play Framework 框架的控制器(Controller)

来源:互联网 发布:ios存储数据的方法 编辑:程序博客网 时间:2024/05/28 15:21
Business logic is managed in the domain model layer. As a client (typically a web browser) cannot directly invoke this code, the functionality of a domain object is exposed as resources represented by URIs.

业务逻辑是在域模型层里进行管理,客户端(典型的客户端就是浏览器)无法直接调用业务逻辑代码,客户端是通过资源的URI来访问到域对象。

A client uses the uniform API provided by the HTTP protocol to manipulate these resources, and by implication the underlying business logic. However, this mapping of resources to domain objects is not a bijection: the granularity can be expressed at different levels, some resources may be virtuals, for some resources aliases may be defined…

客户端使用 HTTP 协议中提供的统一方法来访问这些特定资源,并隐式调用底层的业务逻辑。但是这种URI资源到域对象之间的映射关系并不是双向的,其粒度可以使用不同的层次来表示。某些资源可以是虚拟的,也可以给资源定义一个别名…

This is precisely the role played by the Controller layer: providing a glue between the domain model objects and transport layer events. As the Model layer, controllers are written in pureJava, making it easy to access or modify Model objects. Like the HTTP interface, Controllers are procedural and Request/Response oriented.

这正是控制器层所起的作用:提供一个域模型对象与传输层之间的映射关系。由于域模型层和控制器都是纯Java编写的,因此很容易访问或修改模型对象。与HTTP接口类似,控制器是面向过程和请求/响应模型的。

The Controller layer reduces the impedance mismatch between HTTP and the Domain Model.

控制器层可减少由于 HTTP 协议和域模型之间不匹配的障碍。

Note

注意

There are different architectural models with different strategies. Some protocols give you direct access to the domain model objects. This is typically what EJB or Corba protocols do. In these cases, the architectural style used is RPC (Remote Procedure Call). These communication styles are hardly compatible with web architecture.

不同的体系架构有着不同的设计策略,某些协议可以让你直接访问域模型对象,例如 EJB 和 CORBA 协议就是这么做的。在这种情况下,使用的 RPC 远程过程调用的设计风格,这种设计风格很难跟 Web 应用兼容。

Some technologies like SOAP try to give access to the model object domain through the Web. However, SOAP is just another RPC-style protocol, in this case using HTTP as a transport protocol. It is not an application protocol.

而另外一些技术例如 SOAP 试图通过 Web 来访问域对象模型,但不管怎样,SOAP 还是 RPC 风格的设计,尽管使用的是 HTTP 传输协议,这并不是应用的协议。

The web’s principles are not fundamentally object-oriented. So a layer is needed to adapt HTTP to your favorite language.


从根本上来说,Web 的原则并不是面向对象的,因此需要引入一个用来将 HTTP 协议转化为你喜好的编程语言的层,这就是控制层。

A controller overview

控制器概述

A Controller is a Java class, hosted by the controllers package, and subclassing play.mvc.Controller.

在 Play 框架中,控制器其实就是一个 Java 类,位于 controllers 包中,继承了父类 play.mvc.Controller。

This is a Controller:

这里是一个简单控制器的源码:

package controllers;
 
import models.Client;
import play.mvc.Controller;
 
public class Clients extends Controller {
 
    public static void show(Long id) {
        Client client = Client.findById(id);
        render(client);
    }
 
    public static void delete(Long id) {
        Client client = Client.findById(id);
        client.delete();
    }
 
}

Each public, static method in a Controller is called an action. The signature for an action method is always:

在这个示例控制器中,每一个被声明为 public static 的方法被称为 Action,每个 Action 的形式如下所示:

public static void action_name(params...);

You can define parameters in the action method signature. These parameters will be automatically resolved by the framework from the corresponding HTTP parameters.

你可以为 Action 定义各种参数,这些参数会自动与 HTTP 的参数进行绑定和赋值。

Usually, an action method doesn’t include a return statement. The method exit is done by the invocation of a result method. In this example, render(…) is a result method that executes and displays a template.

一般情况下,Action 方法无需返回任何值,该方法通过调用一个结果方法来结束执行,例如 render 就是用来执行并显示一个页面模板。

Retrieving HTTP parameters

获取 HTTP 参数

An HTTP request contains data. This data can be extracted from:

    * The URI path: in /clients/1541, 1541 is dynamic part of the URI Pattern.
    * The Query String: /clients?id=1541.
    * The request body: if the request was sent from an HTML form, the request body contains the form data encoded as x-www-urlform-encoded.


在 HTTP 的请求中包含各种参数数据,例如:

    * URI路径: /clients/1541, 1541 是一种动态的URI路径模式
    * Query String: /clients?id=1541.
    * POST方式的请求,该请求来自某个表单的提交,数据编码方式x-www-urlform-encoded.

In all cases, Play extracts this data and builds a Map<String, String[]> which contains all the HTTP parameters. The key is the parameter name. The parameter name is derived from:

    * The name of the dynamic part of the URI (as specified in the route)
    * The name portion of a name-value pair taken from the Query String
    * The contents of a x-www-urlform-encoded body.


在所有的这些情况中,Play 框架解析出这些数据,并将数据保存在一个 Map<String,String[]> 类型的变量中,Map 的 key 就是参数名,而参数名来自于:

    * routes 中定义的名称,针对于例如 /clients/1541 这种URI请求
    * Query String 中的参数名
    * 来自x-www-urlform-encoded 内容的参数名

Using the params map

使用参数映射

The params object is available to any Controller class (it is defined in the play.mvc.Controller super class). This object contains all the HTTP parameters found for the current request.

For example:


参数对象对所有控制器类都是可访问的,在 play.mvc.Controller 类中定义,该对象包含了当前请求的所有参数。

例如:

public static void show() {
    String id = params.get("id");
    String[] names = params.getAll("names");
}

You can also ask Play to do the type conversion for you:

你也可以让 Play 框架帮你做类型转换:

public static void show() {
    Long id = params.get("id", Long.class);
}

But wait, there are better ways to do this :)

但是等等,别急,还有更好的方法来获取参数 

From the action method signature

You can retrieve HTTP parameters directly from the action method signature. The Java parameter’s name must be the same as the HTTP parameter’s.

For example, in this request:


通过 action 方法

你可以通过 action 方法来直接获取 HTTP 的请求参数,只需要保证 action 方法的参数名和 HTTP 参数名一致即可。

例如下面这样一个请求:

/clients?id=1451

An action method can retrieve the id parameter value by declaring an id parameter in its signature:

要获取这个 id 参数,我们只需要编写如下的 action 方法即可:

public static void show(String id) {
    System.out.println(id);
}

You can use other Java types than String. In this case the framework will try to cast the parameter value to the correct Java type:

我们也可以指定不同的参数类型,而不仅仅是字符串类型,例如下面的代码将参数转成一个 Long 对象,而 Play 框架会自动帮我们实现数据的类型转换:

public static void show(Long id) {
    System.out.println(id); 
}

If the parameter is multivalued, you can declare an Array argument:


如果某个参数有多个值,可用数组来定义该参数:

public static void show(Long[] id) {
    for(String anId : id) {
        System.out.println(id);
    }
}

or even a collection type:

也可以是集合类型,例如 List:

public static void show(List<Long> id) {
    for(String anId : id) {
        System.out.println(id);
    }
}

Exceptions

例外

If the HTTP parameter corresponding to the action method argument is not found, the corresponding method argument is set to its default value (typically null for objects and 0 for primitive numeric types). If a value is found but can’t be properly cast to the required Java type, an error is added to the validation error collection and the default value is used.

如果与 action 方法中的参数没有找到对应的 HTTP 参数,那么 Play 框架会给这个参数设置一个默认值,例如 null 或者数值的 0;但如果action的参数定义为数值类型,而 HTTP 参数确实一个字符串无法转成数值时,那么将会生成一个错误的验证信息,并给这个数值参数设置为默认值

Advanced HTTP to Java binding
Simple types


高级HTTP 到 Java 的绑定
简单类型

All the native and common Java type are automatically bound:

所有 Java 自带的一些简单类型(如下所示),Play 框架都可以实现自动转换:

int, long, boolean, char, byte, float, double, Integer, Long, Boolean, Char, String, Float, Double.

Note that if the parameter is missing in the HTTP Request, or if automatic conversion fails, Object type will be set to null and native types will be set to their default value.

需要注意的是,如果转换失败,那么参数值会设置成默认。

Date

A date object can be automatically bound if the date’s string representation matches one of the following patterns:


日期类型

日期数据支持以下几种格式的自动转换:

    * yyyy-MM-dd’T’hh:mm:ss’Z' // ISO8601 + timezone
    * yyyy-MM-dd’T’hh:mm:ss" // ISO8601
    * yyyy-MM-dd
    * yyyyMMdd’T’hhmmss
    * yyyyMMddhhmmss
    * dd'/‘MM’/'yyyy
    * dd-MM-yyyy
    * ddMMyyyy
    * MMddyy
    * MM-dd-yy
    * MM'/‘dd’/'yy

For example:

例如:

archives?from=21/12/1980

public static void articlesSince(Date from) {
    List<Article> articles = Article.findBy("date >= ?", from);
    render(articles);
}

Files

文件上传

File upload is easy with Play. Use a multipart/form-data encoded request to post files to the server, and then use the java.io.File type to retrieve the file object:

在 Play 框架中处理文件上传是非常简单的事,一个 multipart/form-data 编码的请求发送到 Play 服务器,Play 框架会自动为该上传文件生成一个 File 对象:

public static void create(String comment, File attachment) {
    String s3Key = S3.post(attachment);
    Document doc = new Document(comment, s3Key);
    doc.save();
    show(doc.id);
}

The created file has the same name as the original file. It’s stored in a temporary directory and deleted at the end of the request. So you have to copy it in a safe directory or it will be lost.

而且File 对象的文件名跟上传文件原来的名称一致,它被存放在临时目录中,一旦请求结束会自动删除,因此如果要长期使用这个文件,请保存到安全的目录。

Arrays or collections of supported types

数组和集合类型

All supported types can be retrieved as an Array or a collection of objects:


所有被支持的类型都可以通过数组或者集合来获取:

public static void show(Long[] id) {
    ...
}

or:

public static void show(List<Long> id) {
    ...
}

or:

public static void show(Set<Long> id) {
    ...
}

POJO object binding

POJO 对象绑定

Play also automatically binds any of your model classes using the same simple naming convention rules.

Play 可自动将模型类与请求参数绑定,绑定的规则是类属性名与参数名一致:

public static void create(Client client ) {
    client.save();
    show(client);
}

A query string to create a client using this action would look like:

?client.name=Zenexity&client.email=contact@zenexity.fr

例如一个用来创建客户的请求链接如:
?client.name=Zenexity&client.email=contact@zenexity.fr

Play creates a Client instance and resolves HTTP parameters name to properties on the Client object. Unresolved parameters are safely ignored. Types mismatches are also safely ignored.

Parameter binding is done recursively, which means you can address complete object graphs:


Play 会自动创建一个 Client 的实例,并将对应的参数赋值给该实例的属性,一些无法解析的参数会被忽略,类型不匹配也会被忽略。

参数绑定是递归执行的,因此你可以使用如下方式定义整个对象的结构:

?client.name=Zenexity
&client.address.street=64+rue+taitbout
&client.address.zip=75009
&client.address.country=France

In order to update a list of model objects, use array notation and reference the object’s ID. For example imagine the Client model has a list of Customer models declared as List customers. To update the list of Customers you would provide a query string like the following:

如果需要更新一组模型对象,可使用数组的方式呈现,例如:

?client.customers[0].id=123
&client.customers[1].id=456
&client.customers[2].id=789

JPA object binding

JPA 对象绑定

You can automatically bind a JPA object using the HTTP to Java binding.

你可以使用 HTTP 到 Java 的绑定来自动绑定一个 JPA 对象。

You can provide the user.id field yourself in the HTTP parameters. When Play find the id field, it loads the matching instance from the database before editing it. The other parameters provided by the HTTP request are then applied. So you can save it directly.

通过在 HTTP 参数中提供 user.id 属性,Play 可以在编辑前,从数据库中加载匹配的实例,然后除 id 外的其他参数将会被更新到数据库中,如下面代码:

public static void save(User user) {
    user.save(); // ok with 1.0.1
}

Result types

控制器执行结果

An action method has to generate an HTTP response. The easiest way to do this is to emit a Result object. When a Result object is emitted, the normal execution flow is interrupted and the method returns.

For example:

Action 方法是需要生成 HTTP 回应内容的,最简单的方法就是直接通过 render 封装一个结果对象。

例如:

public static void show(Long id) {
    Client client = Client.findById(id);
    render(client);
    System.out.println("This message will never be displayed !");
}

The render(…) method emits a Result object and stops further method execution.

Render 方法输送一个结果对象,并停止 action 方法的执行。

Return some textual content

返回一些文本内容

The renderText(…) method emits a simple Result event which writes some text directly to the underlying HTTP Response.

Example:


可使用 renderText 方法来向浏览器返回一些文本内容。

例如:

public static void countUnreadMessages() {
    Integer unreadMessages = MessagesBox.countUnreadMessages();
    renderText(unreadMessages);
}

You can format the text message using the Java standard formatting syntax:


也可以通过 Java 标准的格式化语法来对输出的文本进行格式处理:

public static void countUnreadMessages() {
    Integer unreadMessages = MessagesBox.countUnreadMessages();
    renderText("There are %s unread messages", unreadMessages);
}

Execute a template

执行模板

If the generated content is complex, you should use a template to generate the response content.

如果生成的内容非常复杂,一般我们需要用一个模板文件来处理。

public class Clients extends Controller {
 
    public static void index() {
        render();   
    }
 
}

A template name is automatically deduced from the Play conventions. The default template path is resolved using the Controller and action names.

In this example the invoked template is:


Play 框架的惯例,模板文件名是跟 action 相对应的,默认的对应方式是使用控制器名和 action 名。

在这个例子中,模板名是:

app/views/Clients/index.html

Add data to the template scope

Often the template needs data. You can add these data to the template scope using the renderArgs object:


为模板添加数据

模板文件是需要数据来执行的,我们可以通过 renderArgs 方法来向模板输送对象:

public class Clients extends Controller {
 
    public static void show(Long id) {
        Client client = Client.findById(id);
        renderArgs.put("client", client);
        render();   
    }
 
}

During template execution, the client variable will be defined.

For example:


在模板执行过程中,client这个变量就可以直接使用。

例如:

<h1>Client ${client.name}</h1>

A simpler way to add data to the template scope

You can pass data directly to the template using render(…) method arguments:


另外一种更简单的向模板传递数据的方式是直接通过 render 方法:

public static void show(Long id) {
    Client client = Client.findById(id);
    render(client);   
}

In this case, the variables accessible by the template have the same name as the local Java variables.

使用这种方法,那么模板中可访问的变量跟action方法中的变量名一致。

You can pass more than one variable:

下面代码是向模板传递多个变量:

public static void show(Long id) {
    Client client = Client.findById(id);
    render(id, client);   
}

Important!

这里很重要!

You can only pass local variables in this way.

你只能传递 action 方法类的变量

Specify another template

指定其他的模板

If you don’t want to use the default template, you can specify your own template file during the render(…) call. Just pass it as the render method’s first parameter:

Example:


你可能不想使用默认的模板,那么可通过 render 方法来指定其他模板,例如:

public static void show(Long id) {
    Client client = Client.findById(id);
    render("Clients/showClient.html", id, client);   
}

Redirect to another URL

重定向到其他 URL 地址

The redirect(…) method emits a Redirect event that in turn generates an HTTP Redirect response.

Play 提供了一个 redirect 方法,可以将请求调整到其他的 URL 地址:

public static void index() {
    redirect("http://www.zenexity.fr");
}

Action chaining

Action 链

There is no equivalent to the Servlet API forward. An HTTP request can only invoke one action. If you need to invoke another action, you have to redirect the browser to the URL able to invoke that action. In this way, the browser URL is always consistent with the executed action, and the Back/Forward/Refresh management is much easier.

Action 链与 Servlet API 中的 forward 并不相同,一个 HTTP 请求只能调用一个 action 。如果你需要调用其他的 action ,你必须将浏览器重定向到你想要调用的 action 上。这样后退、前进和刷新操作就会简单很多。

You can send a Redirect response to any action, simply by invoking the action method in a Java way. The Java call is intercepted by the framework and the correct HTTP Redirect is generated.

For example:


你可以重定向到任意一个 action,只需要简单的调用该 action 方法而已,Play 框架会自动将这种形式的调用解析为 HTTP 的重定向。

例如:

public class Clients extends Controller {
 
    public static void show(Long id) {
        Client client = Client.findById(id);
        render(client);
    }
 
    public static void create(String name) {
        Client client = new Client(name);
        client.save();
        show(client.id);
    }
 
}

With these routes:

GET    /clients/{id}            Clients.show
POST   /clients                 Clients.create

    * The browser sends a POST to the /clients URL.
    * The Router invokes the Clients controller’s create action.
    * The action method call the show action method directly.
    * The Java call is intercepted and the Router reverse route generation creates the URL needed to invoke Clients.show with an id parameter.
    * The HTTP Response is 302 Location:/clients/3132.
    * The browser then issues GET /clients/3132.
    * …

Interceptions

拦截

A controller can define interception methods. Interceptors are invoked for all actions of the controller class and its descendants. It’s a useful way to define treatments that are common to all actions: verifying that a user is authenticated, loading request-scope information…

可以为控制器编写拦截方法,拦截器将在 action 执行的前后被调用。这个用来做用户权限的验证非常有用。

These methods have to be static but not public. You have to annotate these methods with a valid interception marker.
@Before


首先确保拦截器的方法不能定义为 public 但必须是 static 。然后可以通过 @Before 来声明这是一个拦截器方法。

Methods annotated with the @Before annotation are executed before each action call for this Controller.

被声明为 @Before 的方法将会在每个 action 方法被调用之前先执行。

So, to create a security check:

因此,要创建一个权限检查的拦截器如下代码:

public class Admin extends Application {
 
    @Before
    static void checkAuthentification() {
        if(session.get("user") == null) login();
    }
 
    public static void index() {
        List<User> users = User.findAll();
        render(users);
    }
 
    ...
 
}

If you don’t want the @Before method to intercept all action calls, you can specify a list of actions to exclude:

如果你不想拦截所有的 action 方法,那么可以指定不拦截哪些方法:

public class Admin extends Application {
 
    @Before(unless="login")
    static void checkAuthentification() {
        if(session.get("user") == null) login();
    }
 
    public static void index() {
        List<User> users = User.findAll();
        render(users);
    }
 
    ...
 
}

@After

Methods annotated with the @After annotation are executed after each action call for this Controller.

与 @Before 相反,@After 注释用来声明 action 结束调用的拦截器:

public class Admin extends Application {
 
    @After
    static void log() {
        Logger.info("Action executed ...");
    }
 
    public static void index() {
        List<User> users = User.findAll();
        render(users);
    }
 
    ...
 
}

@Finally

Methods annotated with the @Finally annotation are executed after each action result is applied from for this Controller.

@Finally 拦截器声明用来定义一个方法在结果已经生成完成后执行:

public class Admin extends Application {
 
    @Finally
    static void log() {
        Logger.info("Response contains : " + response.out);
    }
 
    public static void index() {
        List<User> users = User.findAll();
        render(users);
    }
 
    ...
 
}

Controller hierarchy

控制器的层次结构

If a Controller class is a subclass of another Controller class, interceptions are applied to the full Controller hierarchy.

如果一个控制器类是另外一个的之类,那么该类中定义的所有拦截器都会影响它所有的子类。

Adding more interceptors using the @With annotation

可使用 @With 注释来增加更多的拦截器

Because Java does not allow multiple inheritance, it can be very limiting to rely on the Controller hierarchy to apply interceptors. But you can define some interceptors in a totally different class, and link them with any controller using the @With annotation.

Example:

因为 Java 不支持多重继承,这对控制器层次是限制很大的,但你可以通过定义一些拦截器,然后使用 @With 注释来声明。

例如:

public class Secure extends Controller {
   
    @Before
    static void checkAuthenticated() {
        if(!session.containsKey("user")) {
            unAuthorized();
        }
    }
}   

And on another Controller:

@With(Secure.class)
public class Admin extends Application {
   
    ...
    
}

Session and Flash scope

会话和闪屏

If you have to keep data across multiple HTTP Requests, you can save them in the Session or the Flash scope. Data stored in the Session are available during the whole user session, and data stored in the flash scope are available to the next request only.

如果你需要在多个 HTTP 请求间保持数据,那么可以将数据保存在Session 或者是 Flash(闪屏)中。保存在 Session 中的数据在整个用户会话中都是有效的,而 Flash 就只对下一个请求有效。

It’s important to understand that Session and Flash data are not stored in the server but are added to each subsequent HTTP Request, using the Cookie mechanism. So the data size is very limited (up to 4Ko) and you can only store String values.


特别特别需要注意的时,与 J2EE 的做法不同的是,存放于 Session 和 Flash 的数据是通过 Cookie 保存在浏览器的,因此数据大小不能超过4k,而且只能存储字符串。

译者注:千万不要保存敏感数据。

Of course, cookies are signed with a secret key so the client can’t modify the cookie data (or it will be invalidated). The Play session is not aimed to be used as a cache. If you need to cache some data related to a specific session, you can use the Play built-in cache mechanism and use the session.getId() key to keep them related to a specific user session.

当然了,cookies 是通过安全方式进行签名的,因此客户端是无法修改这个数据,只能使之无效。Play 框架的 Session 不能当作是缓存来使用,如果你需要为某个Session缓存某些数据,那么可以使用 session.getId() 来作为缓存的key。

Example:

示例:

public static void index() {
    List messages = Cache.get(session.getId() + "-messages", List.class);
    if(messages == null) {
        // Cache miss
        messages = Message.findByUser(session.get("user"));
        Cache.set(session.getId() + "-messages", messages, "30mn");
    }
    render(messages);
}

The cache has different semantics than the classic Servlet HTTP session object. You can’t assume that these objects will be always in the cache. So it forces you to handle the cache miss cases, and keeps your application fully stateless.


这个缓存在语义上跟 Servlet 的 HTTP Session 是不同的,你不能猜想说缓存中的数据是会一直存在的,因此你需要处理缓存数据失效的情况,使你的应用真正的无状态化。

0 0
原创粉丝点击