【javaWeb 之 Play 框架】

Play Framework makes it easy to build web applications with Java & Scala.

Play is based on a lightweight, stateless, web-friendly architecture.

Built on Akka, Play provides predictable and minimal resource consumption (CPU, memory, threads) for highly-scalable applications.



 

Play 框架是一个完整的 Web 应用开发框架,覆盖了 Web 应用开发的各个方面。Play 框架在设计的时候借鉴了流行的 Ruby on Rails 和 Grails 等框架,又有自己独有的优势。使用 Play 框架可以方便和高效的开发出 Java Web 应用。通过 Play 框架提供的命令行工具,可以快速的创建出一个 Web 应用的基本骨架。它的 Java 代码动态编译机制,使得修改代码之后,不需要重启服务器就可以直接看到修改之后的结果,调试起来非常方便。它使用 JPA 规范来完成领域对象的持久化,可以很方便的使用不同的关系数据库作为后台存储。使用 Play 框架可以很容易的构建使用 REST 架构风格的应用。它使用 Groovy 作为视图层模板使用的表达式语言。模板之间的继承机制也可以避免代码的重复。总的来说,Play 框架非常适合快速 Web 应用开发。

Play 控制层

Play 框架中的控制层是模型层和视图层之间的桥梁。控制层负责接收 HTTP 请求并返回相应的响应。一般来说,控制层的典型实现是接收到 HTTP 请求之后,从请求中获取一些参数,再调用服务层对应的处理方法。服务层的方法会对领域对象进行操作,完成具体的业务逻辑。最后,某种格式的响应被返回给请求者,如 HTML 页面、JSON 数据和 XML 数据等。Play 框架的控制层实现使得完成这样的典型场景变得非常简单。

Play 框架中的每个控制器都是一个普通的 Java 类,继承自 play.mvc.Controller 类,在包 controllers 中。控制器类中的每个公开的静态方法都表示一个动作。每个动作负责完整的请求 / 响应的流程,也就是说,所有前面提到的所有请求/响应的过程都需要在每个动作中来完成。

参数绑定

在控制层实现中很繁琐但是必不可少的操作就是解析 HTTP 请求中的参数。不同的 Web 开发框架会提供自己的参数解析方式。Play 框架也提供了相应的支持。Play 框架可以解析 HTTP 请求中查询字符串和 URI 路径中包含的以及请求体中以格式编码的参数。所有这些参数都放在 params 对象中,其中包含 get()、getAll() 和 put() 等方法用来获取和设置参数的值。除了这种传统的使用方式之外,Play 框架还支持直接把参数的值绑定到动作方法的参数上面。比如一个动作方法的声明是 show(String username),那么请求中的参数 username 的值会在 show() 方法被调用时作为实际参数传递进去。Play 框架会负责完成相应的类型转换。值得一提的是对于日期类型(java.util.Date)的参数,Play 框架支持多种类型的日期格式的转换。比如动作方法的声明是 display(Date postedAt),而请求的格式可能是 /display?postedAt=2010-09-22,Play 框架会自动完成相应的类型转换。

除了常见的基本数据类型之外,Play 框架还支持直接绑定领域对象的实例。比如动作方法的声明是 create(Note note),可以在参数中直接指定对象实例的属性的值。请求的格式可能是 /create?title=Note123&content=Good。Play 框架会负责创建一个 Note 类的实例,并根据参数的值设置该实例的属性 title 和 content 的值。这种绑定方式不仅支持简单对象,还支持嵌套对象和列表。比如 /create?tags[0]=ajax&tags[1]=web 可以设置列表类型属性 tags 的值。

Play 框架的这种绑定方式还支持文件对象,使得上传文件变得非常简单。只需要在表单中添加文件上传的控件(<input type="file">)并使用 multipart/form-data编码来提交请求,在动作方法的参数中就可以获取到上传文件对应的 java.io.File 对象。比如动作方法的声明可能是 upload(File picture)。上传的文件被保存在临时目录中,在请求完成之后会被自动删除。可以在动作方法中完成对上传文件的操作。

Play 返回响应结果

在控制层的动作方法完成了与业务逻辑相关的处理之后,需要把响应返回给客户端。响应的结果可能是正确完成,也可能是出现错误。Play 框架提供了方便的实现用来返回不同类型的响应。使用 play.mvc.Controller 类提供的不同方法就可以生成这些响应内容。

请求正确完成,HTTP 状态代码为 200。使用 ok() 方法生成不带内容的响应。使用 render() 方法来生成使用模板的响应。使用 renderText() 方法生成 text/plain 类型的纯文本响应。使用 renderXml() 方法生成 text/xml 类型的 XML 格式的响应。使用 renderJSON() 方法生成 application/json 类型的 JSON 格式的响应。使用 renderBinary() 方法生成二进制内容的响应。

跳转到新的页面,HTTP 状态代码为 3XX。使用 redirect() 方法来跳转到新的 URL。使用 notModified() 方法来返回状态代码 304。

HTTP 状态代码 4XX。使用 unauthorized() 方法返回状态代码 401。使用 forbidden() 方法返回状态代码 403。使用 notFound() 方法返回状态代码 404。

服务器内部错误,HTTP 状态代码 5XX。使用 error() 方法返回状态代码 500。

从上面列出的方法可以看出,Play 框架使用一些有意义的方法名称替换掉了难以记忆的 HTTP 状态代码,使用起来更加方便。同时,对于常见的响应格式,包括 HTML、XML、JSON 和二进制内容,都提供了相应的方法,使得开发人员不会遗漏掉响应中 Content-Type 的声明。

Play 方法拦截

控制层的方法通常需要执行一些横切的逻辑,比如用户认证、加载通用信息和记录日志等。在 Spring 框架中,这些横切的逻辑是通过面向方面编程(AOP)的支持来实现的。Play 框架提供了更加简单易用的方法拦截支持,通过简单的标注就可以定义一些执行拦截操作的方法。这些方法必须非公开的静态方法。Play 框架支持的方法拦截标注有 @Before、@After、@Finally 和 @With 等四种。

用 @Before 标注的方法在动作方法执行之前被调用。@After 标注的方法在动作方法执行之后被调用。@Finally 标注的方法在动作方法的响应结果已经成功生成之后被调用。这三个标注都支持额外的两个属性:priority 表示标注的方法的优先级,0 为最高;unless 是一个字符串数组,表示不适用此拦截方法的动作方法的名称。如 @Before(unless="index") 表示此拦截方法不会应用在动作方法 index() 上。

如果控制器类中存在继承体系结构的话,父类中声明的拦截方法对于所有子类的动作方法都是适用的。在有些情况下,开发人员可能希望把拦截方法定义在不同的类体系结构中。由于 Java 不支持多继承,无法通过继承的方式来应用来自不同类体系结构上的拦截方法。针对这种情况,Play 框架提供了 @With 标注。在控制器类 ControllerA 中定义的拦截方法可以通过 @With 标注来应用到另外一个控制器类 ControllerB 上,而且不通过继承方式来实现。只需要在 ControllerB 中声明 @With(ControllerA.class) 即可。

Play 的HTTP 路由

在前面介绍过,Play 框架中的控制器用来接受 HTTP 请求并返回相应的响应。这个过程的重要一环就是 HTTP 请求的 URI 与控制器之间的映射关系。Play 框架提供了灵活的 HTTP 路由功能来完成这个映射。路由信息被保存在 config/routes 文件中,采用简单的方式进行声明。每条路由记录包含 3 个元素,分别是 HTTP 方法的名称、匹配的 URI 模式以及对应的控制器动作方法。路由记录表示的含义是当使用给定的 HTTP 方法来请求对应模式的 URI 的时候,控制器动作方法就会被调用。

Play 框架支持的 HTTP 方法有 GET、POST、PUT、DELETE 和 HEAD。使用通配符 *可以匹配任何方法。在 URI 模式的声明中可以使用正则表达式来表示复杂的映射规则。URI 模式中还可以使用 {...} 来声明动态的部分。每个动态部分都是有名称的,可以在控制器动作方法中通过 params 对象来获取。比如,/notes/home 这样的 URI 模式会匹配 /notes/home,但是 /notes/{id} 可以匹配 /notes/123 和 /notes/abc,而且 URI 模式中 /notes/ 后面的部分可以作为参数 id 的值被获取到。URI 模式 /notes/{<[0-9]+>id} 使用了正则表达式,只会匹配 /notes/后面紧跟的全是数字的情况。在声明控制器的动作方法的时候,需要使用带名称空间的全名,如 myapp.Notes.show。有些动作方法是带参数的,可以在声明的时候预先绑定一些参数值,这样可以方便的添加一些 URI 别名。比如动作方法 Notes.show() 有一个参数 id 用来指明要显示的内容的 ID。如果参数 id 的值为 0,则会显示所有内容的一个列表。这样的话,就可以定义一个类似 GET /notes/all Notes.show(id:0) 的路由声明。这样暴露出来的 URI 更加简洁和易于记忆。

在路由文件中的路由声明是按照从上到下的优先级来进行匹配的。比较具体的 URI 模式应该放在比较通用的模式之前。对于静态文件,可以通过一个特殊的动作方法 staticDir 进行声明。比如 GET /files staticDir:files 就声明了 files 目录中包含的是静态文件。

无状态的体系结构

HTTP 协议本身就被设计成无状态的,采用请求 / 响应的模式。不同的请求之间并不存在相互关系。但是这种架构模式在开发某些 Web 应用的时候不是很方便。有些应用要求用户进行认证登录之后才能进行某些操作。同样的 URL,认证和未认证用户看到的内容是不同的。而且用户认证成功之后,他应该在一段时间内保持这种认证状态。否则的话,用户每次都需要输入用户名和密码才能访问受限的内容。对于这种情况,很多 Web 开发框架提供了会话的支持,允许应用保存一些与会话相关的数据。Java Servlet 规范中的 javax.servlet.http. HttpSession 就是一种会话的接口。应用的服务器会负责维护每个会话相关的数据。这些数据可以通过一个会话 ID 来进行标识。这个标识会利用浏览器的 cookie 机制保存在浏览器端,也可以作为请求 URL 的参数来传递。服务器端通过此标识来识别每个会话。在处理相应的请求的时候,就可以根据会话 ID 来获取保存在服务器端上的会话数据。会话机制的问题是会影响应用的可伸缩性。如果一个应用使用多台服务器的话,就需要额外的机制来保证同一用户在不同机器上面的会话是同步的。而无状态的实现则不存在这个问题,对于某一个请求,由不同机器来处理的结果都是相同的。

Play 框架的设计架构就是无状态的。它没有提供服务器端的机制用来维护跨多个请求的数据。如果确实需要保存这样的数据的话,可以考虑下面几种方案:

保存在 Session 或 Flash 作用域中。Play 框架中仍然有会话的机制,但是并没有提供在服务器端保存会话数据的能力。会话数据是保存在浏览器的 cookie 中的,由浏览器在每次请求的时候自动发送。通过这种方式来达到维护会话数据的目的。由于会话数据是保存在 cookie 中,其大小是有限制的,一般不能超过 4K 字节,而且只能保存字符串类型的数据。Flash 作用域和会话一样,也是通过 cookie 来保存的。所不同的是,Flash 作用域中的数据只在下次请求中是有效的。

保存在持久化的数据存储中,如数据库中。如果需要在多个请求中使用同一个领域对象的话,可以把这个对象的 ID 保存在 Session 或 Flash 作用域中,而在控制器动作方法中使用此 ID 来从数据库中查询相应的对象。

保存在暂时性数据存储中,如缓存中。Play 框架内置了缓存的支持,通过调用类 play.cache.Cache 就可以对缓存进行操作。与使用持久化存储类似,缓存中的键的值可以保存在 Session 或 Flash 作用域中。

对于熟悉了 Java Servlet 规范的开发人员来说,需要一些时间来适应 Play 框架的这种无状态的体系结构。不过这种结构对于应用的可伸缩性来说,确实是非常有好处的。

猜你喜欢

转载自gaojingsong.iteye.com/blog/2324286