返回介绍

Day 30: Play Framework - Java 开发者的梦想框架

发布于 2025-01-31 20:47:32 字数 9687 浏览 0 评论 0 收藏 0

今天是最后一天,我决定学习一下 Play 框架 。原本是想写关于 Scala 的,学习了几个小时之后发现在一天之内是不可能完成 Scala 的,所以今天会介绍一下 Play 框架的基本知识,然后学习如何用它开发应用。

什么是 Play 框架?

Play 是一个开源的现代 web 框架,用于编写 Java 和 Scala 的可扩展 Web 应用程序。它通过自动重载变化来提高生产力,由于设计的就是一个无状态、无阻塞的架构,所以用 Play 框架来编写横向扩展 Web 应用程序是很容易的。

为什么要用它?

我的原因是:

  1. 开发人员生产力:我已经写了 8 年的 Java,但在过去的几个月里我把更多的时间花在了 Python 和 JavaScript (Node.js) 上。用动态语言工作时最让我吃惊的,就是用它编写程序的速度是如此之快。Java EE 和 Spring 框架并不是快速原型和开发的理想选择,但在用 Play 框架时,你更改一处刷新一下页面,更新会立即出现,而且它支持热重载所有的 Java 代码、模板等,可以让你的迭代快很多。
  2. 天性使然:Play 框架是建立在 Netty 之上的,所以它支持非阻塞 I/O,这使得并行远程调用容易了很多,这一点对面向服务的架构中的高性能应用程序是很重要的。
  3. 支持 Java 和 Scala:Play 框架是一个真正的多语种 Web 框架,开发者可以在项目中同时使用 Java 和 Scala。
  4. 一流的 REST JSON 支持:它很容易编写基于 REST 的应用。对 HTTP 路由有很好的支持,HTTP 路由会将 HTTP 请求转化为具体动作;JSON 编组/解组 API 是目前的核心 API,所以没有必要加一个库来做到这一点。

应用类型案例

今天的介绍中,将开发一个社交书签应用程序,它允许用户发布和共享链接。你可以在 这里 查看正在运行的该程序,因为这个和 第 22 天 的应用是一样的,所以请参阅之以便更好地了解这个案例。

开发 Play 应用

参阅文档 以了解如何安装 Play 框架,开始应用程序的开发吧。

$ play new getbookmarks

     _
 _ __ | | __ _ _  _
| '_ \| |/ _' | || |
|  __/|_|\____|\__ /
|_|      |__/

play 2.2.1 built with Scala 2.10.2 (running Java 1.7.0_25), http://www.playframework.com

The new application will be created in /Users/shekhargulati/dev/challenges/30days30technologies/day30/blog/getbookmarks

What is the application name? [getbookmarks]
> 

Which template do you want to use for this new application? 

  1       - Create a simple Scala application
  2       - Create a simple Java application

> 2
OK, application getbookmarks is created.

Have fun!

如上键入命令后,该框架会问几个问题。首先它要求有应用程序的名称,然后问是否要创建一个 Scala 或 Java 应用程序。默认情况下,它会使用 文件夹名称 作为应用程序的名称。

上面的命令将创建一个新的目录 getbookmarks 并生成以下文件和目录:

  1. app 目录包含如控制器 (controller) 、视图 (view) 和模型 (model) 的应用程序特定代码。控制器包中有响应 URL 路由的 Java 代码,视图目录包含服务器端模板,模型目录包含应用程序的域模型。在此应用中,域 (domain) 是一个 Story 类。
  2. conf 目录包含应用程序配置和路由定义文件。
  3. project 目录包含构建脚本,构建系统是基于 SBT 的。
  4. public 包含了如 CSS、JavaScript 和 img 目录等的公共资源。
  5. test 目录包含应用测试。

通过如下命令发布 play 控制台,运行 Play 编写的默认程序。

$ cd getbookmarks
$ play
[info] Loading project definition from /Users/shekhargulati/dev/challenges/30days30technologies/day30/blog/getbookmarks/project
[info] Set current project to getbookmarks (in build file:/Users/shekhargulati/dev/challenges/30days30technologies/day30/blog/getbookmarks/)
     _
 _ __ | | __ _ _  _
| '_ \| |/ _' | || |
|  __/|_|\____|\__ /
|_|      |__/

play 2.2.1 built with Scala 2.10.2 (running Java 1.7.0_25), http://www.playframework.com

> Type "help play" or "license" for more information.
> Type "exit" or use Ctrl+D to leave this console.

[getbookmarks] $ run
[info] Updating {file:/Users/shekhargulati/dev/challenges/30days30technologies/day30/blog/getbookmarks/}getbookmarks...
[info] Resolving org.fusesource.jansi#jansi;1.4 ...
[info] Done updating.

--- (Running the application from SBT, auto-reloading is enabled) ---

[info] play - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

现在可以在 http://localhost:9000 里运行该应用了。

创建 Story 域类

该应用程序只有一个域类 (domain class),叫做 story,创建一个新的包模型和 Java 类。

package models;

import play.db.ebean.Model;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;


@Entity
public class Story extends Model{

  @Id
  private String id;

  private String url;

  private String fullname;

  private Date submittedOn = new Date();

  private String title;
  private String text;
  private String image;


  public Story() {

  }

  public Story(String url, String fullname) {
    this.url = url;
    this.fullname = fullname;
  }

  public Story(String url, String fullname, String image, String text, String title) {
    this.url = url;
    this.fullname = fullname;
    this.title = title;
    this.text = text;
    this.image = image;
  }

   // Getter and Setter removed for brevity
}

上述代码定义了一个简单的 JPA 实体,并使用 @Entity@Id JPA 注解,Play 用它自己的一个被称作 Ebean 的 ORM 层,而且每一个实体类必须扩展基本模型类。

Ebean 默认禁用,启用它需要打开 application.conf 并取消注释以下行。

ebean.default="models.*"

启用数据库

启动应用程序的数据库,Play 框架提供了内置的 H2 数据库的支持。要启用它,打开 application.conf 文件,并取消如下两行的注释。

db.default.driver=org.h2.Driver
db.default.url="jdbc:h2:mem:play"

刷新浏览器会看到:

点击 Apply this script now 将 SQL 的更改部署上去。

定义应用程序的路由

今天讲的应用程序和第 22 天是一样的,都有 AngularJS 后台和 REST 后端,所以可以使用 Play 框架重写 REST 后台和 AngularJS 后端,在 conf/routes 文件,复制并粘贴如下代码。

# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~

# Home page
GET     /               controllers.Assets.at(path="/public", file="/index.html")
GET     /api/v1/stories       controllers.StoryController.allStories()
POST    /api/v1/stories       controllers.StoryController.submitStory()
GET     /api/v1/stories/:storyId  controllers.StoryController.getStory(storyId)

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file    controllers.Assets.at(path="/public", file)

上述代码表示:

  1. 当用户发出一个 GET 请求到应用程序的 “/”URLindex.html 将被渲染。
  2. 当用户发出一个 GET 请求到 '/ api/v1/stories' ,将得到 JSON 格式的所有 story。
  3. 当用户发出 POST 请求到 '/ api/v1/stories' ,一个新的 story 将被创建。
  4. 当用户 GET 请求 '/ api/v1/stories/123' ,id 为 123 的 story 会被渲染。

创建 Story 控制器

在控制器包里创建一个 Java 类,将如下代码粘贴进 StoryController.java 文件里。

package controllers;


import com.fasterxml.jackson.databind.JsonNode;
import models.Story;
import play.api.libs.ws.Response;
import play.api.libs.ws.WS;
import play.db.ebean.Model;
import play.libs.Json;
import play.mvc.BodyParser;
import play.mvc.Controller;
import play.mvc.Result;
import play.mvc.Results;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;

import java.util.List;
import java.util.concurrent.TimeUnit;

public class StoryController {

  public static Result allStories(){
    List<Story> stories = new Model.Finder<String , Story>(String.class, Story.class).all();
    return Results.ok(Json.toJson(stories));
  }

  @BodyParser.Of(BodyParser.Json.class)
  public static Result submitStory(){
    JsonNode jsonNode = Controller.request().body().asJson();
    String url = jsonNode.findPath("url").asText();
    String fullname = jsonNode.findPath("fullname").asText();
    JsonNode response = fetchInformation(url);
    Story story = null;
    if(response == null){
      story = new Story(url,fullname);
    }else{
      String image = response.findPath("image").textValue();
      String text = response.findPath("text").textValue();
      String title = response.findPath("title").textValue();
      story = new Story(url,fullname, image , text , title);
    }
    story.save();
    return Results.created();
  }

  public static Result getStory(String storyId){
    Story story = new Model.Finder<String, Story>(String.class, Story.class).byId(storyId);
    if(story == null){
      return Results.notFound("No story found with storyId " + storyId);
    }
    return Results.ok(Json.toJson(story));
  }

  private static JsonNode fetchInformation(String url){
    String restServiceUrl = "http://gooseextractor-t20.rhcloud.com/api/v1/extract?url="+url;
    Future<Response> future = WS.url(restServiceUrl).get();
    try {
      Response result = Await.result(future, Duration.apply(30, TimeUnit.SECONDS));
      JsonNode jsonNode = Json.parse(result.json().toString());
      return jsonNode;
    } catch (Exception e) {
      e.printStackTrace();
      return null;
    }

  }

}

上述代码会操作:

  1. 它定义 allStories() 方法,该方法会找到数据库中所有的 story。它是使用 Model.Finder API 来做到这一点的,然后把 story 列表转换成 JSON 格式并返回结果,返回 HTTP 状态代码 200(即确定)。
  2. submitStory() 方法首先会从 JSON 读取 URL 和全名的字段,然后发送 GET 请求到 'http://gooseextractor-t20.rhcloud.com/api/v1/extract?url' ,这样就会找出标题、摘要以及已经给定 url 的主要 image。创建一个使用所有信息的 story 并保存在数据库中,返回 HTTP 状态代码 201(即创建)。
  3. getStory() 方法为给定的 storyId 获取 story,把这个 story 转换成 JSON 格式并返回响应。



可以从我的 github 仓库 下载 AngularJS 前端,用其中一个库更换公共目录。

现在可以访问 http://localhost:9000/ 看结果了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文