Play2 for Java(七:global object)

Module 7 Global Object

本模块,我们将研究global object,它是控制应用程序中的全局事件的一个hook(钩子)。我们将从全局对象的高级特性开始。
1.应用程序声明周期hook
    启动、停止
    Error处理     
2.Request/Reponse 声明周期hook
    拦截请求
    处理404错误
    处理请求错误
3.其他的全局特性

7.1 全局对象

在Play!中的全局对象是一个可选的类,它提供了处理应用程序和请求处理的各种方法.如果您没有指定您自己的全局对象实现,那Play!就使用它自己的默认实现,在大多数情况下,它只是委托给框架的其他部分。您可以把它看作一个公共事件聚合器,为可能在应用程序范围内发生的事件提供hook(钩子)。那么它到底是什么,为什么重要,以及你为什么要使用它?
global object是由Play的设计者所提出来。他们希望提供一种方法,让开发人员能够更好地控制他们的request/response生命周期,如果这是需要的话。但是许多应用程序并不需要这个,因此全局对象被设计为完全可选的。只有当您真正创建自己的定义,并开始执行它提供的各种钩子和事件时,它才会生效。
那么什么时候会用到全局对象呢?其实使用全局对象也是有很多常见的原因的。例如,Play!为应用程序的“Error state(错误的状态码)”提供了一些不错的视图,如页面未发现的404或内部服务器错误或者路由错误。这些在开发过程中是有用的,但是对于系统的使用者来说,会产生一种相当不一致的体验,因为你的页面不太可能看起来像Play!的风格。通常,开发人员希望为这些错误状态提供自己的视图,而全局对象则是控制这个的地方。
另一个使用全局对象的原因是定制Play!框架。例如,许多开发人员喜欢使用Spring和谷歌的Guice之类的技术来提供依赖注入。默认情况下,Play!并没有提供这种功能,但是通过全局对象,将这些技术集成到Play!中是完全有可能的。
创建全局对象很简单,但是Play!的版本更迭太快,Play2.5以及之前的版本是可以使用全局对象的,但是在之后的版本,已经移除了GlobalSettings,将全部的全局对象已经打散了。
如果你是Play2.5版本之前,可以在app文件夹下,如果我们创建一个名为Global的类,并允许它扩展Play!的GlobalSettings类,这足以提供我们自己的入口点,并在application.conf配置一下global即可,配置的格式如下:

这里写图片描述

如果你是用的是新版的Play2,那么全局对象已经被移除了,所以我们只能按照其他方式来分别设置。
下面的各个小节就分别来看如果在新版Play中实现以往的GlobalSetting问题

7.2 GlobalSettings.beforeStart 和GlobalSettings.onStart

首先解释下这以前的两个方法的作用:
beforeStart :该方法将在应用程序启动之前执行,而且执行顺序还在evolutions或者plugin之前。这是设置您在应用程序中设置外部或人工服务的好地方。
onStart:该方法在应用程序启动时执行,但是在数据库evolutions和plugins运行之后。这是执行某些操作的好地方,例如保存数据库或实例化您将与之交互的任何全局对象。

回到新版本,目前,在启动过程中需要发生的任何事情现在都应该发生在依赖注入类的构造函数中。当依赖注入框架加载它时,类将执行它的初始化,就完成了onStart的作用。如果您需要eager初始化(例如,您需要在应用程序实际启动前执行一些代码,其实就是beforeStart的场景),那么你可能需要去定义一个eager bingding.
先介绍Guice的注入,然后再讲eager binding:
Guice-Step1:定义接口及其多个实现,我这里是定义的服务,该服务有两个实现。

这里写图片描述
这里写图片描述
这里写图片描述

代码很简单,都只是纯粹的java代码
Guice-Step2:配置绑定

这里写图片描述

注意,这里的Module是放在根路径下,别放错了地方,虽然位置可以自定义,但是如果你放在其他文件夹下,需要在application.conf中加入下面这一行
//这是将Module放在了modules文件夹下了
play.modules.enabled += "modules.Module"
Guice-Step3:依赖注入

这里写图片描述

采用构造器注入的方式,我们在入参前以@Named注解指定了这里所需的HelloService的类型。

eager binding:如果你只是想初始化某个对象一次,原因可能是创建成本太大,那么你可以试试@Singleton。但是如果你不仅想创建一次,而且希望他们能在每次项目启动时候都快速的创建它们,那么就需要使用Guice的eager bingding啦。使用很简单,如下:

这里写图片描述

为了加强理解,附上Guice的文档一份:

这里写图片描述

必要时候需要根据是否需要懒加载来选择不同的策略。懒加载启动的效率会快,但是早晚还是要加载的。

7.3 onError

onError:当未处理的异常冒泡出现在应用程序堆栈上时执行该方法。这对于提供中央日志记录或提供自定义错误页面非常有用。
新版本中,我们创建一个从HttpErrorHandler继承的类,并移动您的GlobalSettings的onError实现到HttpErrorHandler内部的onServerError方法。
HttpErrorHandler接口有两个方法,分别是:
    >onClientError:发生客户端方的错误时候调用,4XX错误
    >onServerError:发生服务器端方的错误时候调用,5XX错误
①我们首先看如何处理客户端的错误:
如果使用BuiltInComponents构建应用程序,请重写httpRequestHandler方法以返回一个你的自定义的handler的实例。
如果使用运行时依赖注入(例如Guice),则可以在运行时动态加载错误处理程序。最简单的方法是在根包中创建一个名为ErrorHandler的类,它实现HttpErrorHandler,例如:

这里写图片描述

如果你想讲该类放到其他的路径下,而不是根路径下的话,那么需要配置application.conf,加入下这一行:
play.http.errorHandler = "com.xxx.ErrorHandler"
②扩展默认错误处理程序
Play的默认的Error处理器提供了许多有用的功能。例如,在dev模式中,当发生服务器错误时,Play将尝试定位并呈现应用程序中导致异常的代码段,这样您就可以快速地查看和识别问题。您可能希望在生产过程中提供定制的服务器错误,同时仍然保持开发中的功能。为了便于实现这一点,Play提供了一个DefaultHttpErrorHandler,它有一些您可以重写的便利方法,以便您可以将自定义逻辑与Play的现有行为混合在一起。
例如,仅在生产中提供一个自定义服务器错误消息,使开发错误消息保持不变,并且您还希望提供一个特定的禁用错误页面:

这里写图片描述

建议:参考DefaultHttpErrorHandler的完整API文档,看看可以使用哪些方法来覆盖,以及如何利用它们。

7.4 onRequest和onRouteRequest

onRequest和onRouteRequest都提供处理请求的低级hook.一般的的应用程序开发人员不会使用这些,他们更适合于插件开发人员使用,并且可能被完全重写,例如,提供完全不同自定义实现的路由器。
在新版本那种,创建一个类继承自DefaultHttpRequestHandler,并将以往你缩写在onRequest的代码,现在转移到DefaultHttpRequestHandler.createAction方法上。
然而,对于onRouteRequest,目前还没有方案~~
如果确实需要的话,把Play的版本降下来吧。

7.5 onHandlerNotFound

onHandlerNotFound:用于处理未能匹配路由定义中的任何路由的路由。这通常用于返回自定义404页面。
在新版本的Play中的话,创建一个类继承自HttpErrorHandler,为HttpErrorHandler.onClientError提供一个实现。 
注意,HttpErrorHandler.onClientError在参数中使用statusCode,因此您的实现应该类似于如下的模板:
if(statusCode == play.mvc.Http.Status.NOT_FOUND) {
  // move your implementation of `GlobalSettings.onHandlerNotFound` here
}
因为我们在上面讲过HttpErrorHandler 了,还有截图,所以这里就不继续截图啦。

7.6 onBadRequest

当路由匹配时,但是路由器不能完全解析请求,会调用onBadRequest方法。例如,如果您提供一个包含字母和符号的字符串,但是方法的预期类型是Long。
在新版本Play中,与onHandlerNotFound处理方式一样,都是检索statusCode的值,作出相应的响应即可。

7.7 filters 过滤器

用过MVC的,对过滤器并不陌生吧,我就不露拙了~~

在Play新版中,我们需要创建一个从继承自httpfilter的类,并为HttpFilter.filters提供一个实现。
但是在学习过滤器之前,不得不引入一个新概念,即EssentialAction。它是Play的HTTP api所使用的底层函数类型。它与Java中的Action类型是有区别的,它接受一个Context,并返回一个CompletionStage<Result>。大多数情况下,您不需要在Java应用程序中直接使用EssentialAction,但在编写过滤器或与其他低级的Play api进行交互时,它可能是有用的。
为了理解EssentialAction我们需要理解Play的架构。
Play的核心是非常小的,围绕着大量有用的api、服务和结构,使Web编程任务变得更容易。
基本上,Play的操作API抽象地有以下类型:
RequestHeader -> byte[] -> Result 
如上,首先是请求头RequestHeader,然后将请求体作为byte[],并最终生成Result。
现在这种假定类型将请求体完全放入内存(或磁盘),即使你只是想计算出它的值,或者更好的把它转发给像Amazon S3这样的存储服务。
我们更希望接收请求体块作为一个流,并能够在必要时逐步处理它们。
我们需要更改的是第二个箭头,让它以块的方式接收它的输入,并最终产生结果。这里有一个这样的类型,它叫做Accumulator,它有两个类型参数。
Accumulator<E,R>是一种类型的箭头,它会以E类型的块为输入,并最终返回R。对于我们的API,我们需要一个Accumulator,它接收ByteString(本质上是一个更有效的字节数组包装器)的块,最后返回一个Result。所以我们稍微修改了类型为:
RequestHeader -> Accumulator<ByteString, Result>
最终,我们的Java类型是:
Function<RequestHeader, Accumulator<ByteString, Result>>
这应该读为:获取请求头RequestHeader,获取表示请求主体的ByteString块,并最终返回Result。这就是EssentialAction的apply方法的定义:
    public abstract Accumulator<ByteString, Result> apply(RequestHeader requestHeader);
另一方面,Result类型可以抽象地认为是响应头和响应的主体:
Result(ResponseHeader header, ByteString body)
但是,如果我们想要将响应体逐步发送到客户机,而不完全将其填充到内存中呢?我们需要改进我们的类型。我们需要将响应体的类型从ByteString改为一种可以产生ByteString块的东西。
我们已经有了一个类型,叫做Source<E, ?>,在我们的 Source<ByteString, ?>例子中的,意味着它它能够产生E类型的块。
    Result(ResponseHeader header, Source<ByteString, ?> body)
如果我们不需要逐步发送响应,我们仍然可以将整个响应提作为一个数据块发送。在实际的API中,Play支持使用HttpEntity包装器类型支持不同类型的实体,该类型支持流、块和严格实体。
SO:
HTTP API的基本操作非常简单:
RequestHeader -> Accumulator<ByteString, Result>
可以像这样解读:先接收RequestHeader,然后将ByteString做成块,并返回一个响应。响应包括响应头ResponseHeaders 和一个响应体,这个响应体是Source<E, ?> 类型的块的值,他可以转换为ByteString被写到socket 中去。



Filter创建与使用
只需要实现Filter即可。

猜你喜欢

转载自blog.csdn.net/qq_31179577/article/details/78743438