为了让一门语言适用于 现实并且使其 辉煌起来该语言必须能够服务于现实环境和应用程序在这一期的 面向 Java 开发人员的 Scala 指南 系列中Ted Neward 将介绍 Scala 在现实环境中的使用即解释 Scala 如何与核心 Servlet API 交互甚至可能会对其进行一些改正
Scala 显然是一门有趣的语言很适合体现语言理论和创新方面的新思想但最终它要用在 现实 环境中它必须能满足开发人员的某些需求并在 现实 环境中有一定的实用性
了解 Scala 语言的一些核心功能之后就能认识到 Scala 语言的一些灵活性并能放心使用 Scala 创建 DSL现在我们进入实际应用程序使用的环境看看 Scala 如何适应环境在本系列的新阶段中我们将首先讨论大部分 Java? 应用程序的核心Servlet API
Servlet 回顾
回忆一下 Servlet 课程和教程servlet 环境的核心实际上就是通过一个套接字(通常是端口 )使用 HTTP 协议的客户机服务器交换客户机可以是任何 用户代理(由 HTTP 规范定义)服务器是一个 servlet 容器servlet 容器在我编写的一个类上查找加载和执行方法该类最终必须实现 javaxservletServlet 接口
通常实际的 Java 开发人员不会编写直接实现接口的类因为最初的 servlet 规范是用于为 HTTP 之外的其他协议提供一个通用 API所以 servlet 命名空间被分为了两部分
一个 通用 包(javaxservlet)
一个特定于 HTTP 的包(javaxservlethttp)
这样将在一个称为 javaxservletGenericServlet 的抽象基类的通用包中实现一些基本的功能然后在派生类 javaxservlethttpHttpServlet 中实现其他特定于 HTTP 的功能该类通常用作 servlet 实际 内容 的基类HttpServlet 提供了一个 Servlet 的完整实现将 GET 请求委托给一个将要被覆盖的 doGet 方法将 POST 请求委托给一个将要被覆盖的 doPut 方法依此类推
关于本系列
Ted Neward 将和您一起深入探讨 Scala 编程语言在这个新的 developerWorks 系列 中您将了解有关 Scala 的所有最新讨论并在实践中看到 Scala 的语言功能在进行相关比较时Scala 代码和 Java 代码将放在一起展示但是(您将发现)Scala 与 Java 中的许多东西都没有直接的关联这正是 Scala 的魅力所在!如果用 Java 代码就能够实现的话又何必再学习 Scala 呢?
Hello Scala 与 Hello Servlet
显然任何人编写的第一个 servlet 都是普遍的 Hello World servletScala 的第一个 servlet 示例也是如此回忆一下许多年之前介绍的 servlet 教程当时基本的 Java Hello World servlet 只是输出清单 所示的 HTML 响应
清单 预期的 HTML 响应
<HTML>
<HEAD><TITLE>Hello Scala!</TITLE></HEAD>
<BODY>Hello Scala! This is a servlet</BODY>
</HTML>
用 Scala 编写一个简单的 servlet 来实现这个操作非常简单而且这个 servlet 与其相应的 Java 形式几乎一样如清单 所示
清单 Hello Scala servlet!
import javaxservlethttp{HttpServlet
HttpServletRequest => HSReq HttpServletResponse => HSResp}
class HelloScalaServlet extends HttpServlet
{
override def doGet(req : HSReq resp : HSResp) =
respgetWriter()print(<HTML> +
<HEAD><TITLE>Hello Scala!</TITLE></HEAD> +
<BODY>Hello Scala! This is a servlet</BODY> +
</HTML>)
}
注意我使用了一些适当的导入别名来缩短请求的类型名称和相应类型除此之外这个 servlet 几乎与其 Java servlet 形式一样编译时请记得在 servletapijar(通常随 servlet 容器一起发布在 Tomcat 发行版中它隐藏在 lib 子目录中)中包含一个引用否则将找不到 servlet API 类型
这还准备得不够充分根据 servlet 规范它必须使用一个 webxml 部署描述符部署到 Web 应用程序目录中(或一个 war 文件中)该描述符描述 servlet 应该与哪个 URL 结合对于这样一个简单的例子使用一个相当简单的 URL 来配合它最容易如清单 所示
清单 部署描述符 webxml
<!DOCTYPE webapp
PUBLIC //Sun Microsystems Inc//DTD Web Application //EN
app__dtd>
<webapp>
<servlet>
<servletname>helloWorld</servletname>
<servletclass>HelloScalaServlet</servletclass>
</servlet>
<servletmapping>
<servletname>helloWorld</servletname>
<urlpattern>/sayHello</urlpattern>
</servletmapping>
</webapp>
从这里开始我假设读者会在必要时调整/修改部署描述符因为这跟 Scala 没有关系
当然格式良好的 HTML 与格式良好的 XML 非常相似鑒于这一点Scala 对 XML 字面值的支持使编写这个 servlet 简单得多(参阅 参考资料 中的 Scala 和 XML 一文)Scala 不是在传递给 HttpServletResponse 的 String 中直接嵌入消息它可以分离逻辑和表示形式(非常简单)方法是利用此支持将消息放在 XML 实例中然后再传递回去
清单 Hello Scala servlet!
import javaxservlethttp{HttpServlet
HttpServletRequest => HSReq HttpServletResponse => HSResp}
class HelloScalaServlet extends HttpServlet
{
def message =
<HTML>
<HEAD><TITLE>Hello Scala!</TITLE></HEAD>
<BODY>Hello Scala! This is a servlet</BODY>
</HTML>
override def doGet(req : HSReq resp : HSResp) =
respgetWriter()print(message)
}
Scala 的内联表达式求值工具使用 XML 字面值这意味着能够轻松地使 servlet 更有趣例如将当前日期添加到消息中与将 Calendar 表达式添加到 XML 中一样简单不过增加了几行 { Text(javautilCalendargetInstance()getTime()toString() ) }这似乎显得有点冗长如清单 所示
清单 Hello timed Scala servlet!
import javaxservlethttp{HttpServlet
HttpServletRequest => HSReq HttpServletResponse => HSResp}
class HelloScalaServlet extends HttpServlet
{
def message =
<HTML>
<HEAD><TITLE>Hello Scala!</TITLE></HEAD>
<BODY>Hello Scala! Its now { currentDate }</BODY>
</HTML>
def currentDate = javautilCalendargetInstance()getTime()
override def doGet(req : HSReq resp : HSResp) =
respgetWriter()print(message)
}
实际上Scala 编译器与 XML 对象消息一起整合到一个 scalaxmlNode 中然后在将它传递给响应的 Writer 的 print 方法时将其转换为一个 String
不要小看这一点 — 表达形式从逻辑中分离出来完全在一个类内部进行这条 XML 消息将进行编译时检查以确保语法正确和格式良好并获得一些标准 servlet(或 JSP)不具备的好处由于 Scala 可以进行类型推断因此可以省略有关 message 和 currentDate 的实际类型消息使得这就像动态语言 Groovy/Grails 一样初次使用效果不错
当然只读 servlet 相当麻烦
Hello Scala这些是参数
大多数 servlet 不会只返回类似静态内容或者当前日期和时间的简单消息它们带有 POST 形式的内容检查内容并进行相应的响应例如可能 Web 应用程序需要知道使用者的身份并询问其姓名
清单 挑战!
<HTML>
<HEAD><TITLE>Who are you?</TITLE></HEAD>
<BODY>
Who are you? Please answer:
<FORM action=/scalaExamples/sayMyName method=POST>
Your first name: <INPUT type=text name=firstName />
Your last name: <INPUT type=text name=lastName />
<INPUT type=submit />
</FORM>
</BODY>
</HTML>
这个方法不会在任何用户界面设计大赛中夺冠但是它达到了目的这是一个 HTML 表单它会将数据发送给一个新的 Scala servlet(绑定到 sayMyName 的相对 URL)这些数据将根据 servlet 规范存储在一个名称值对集合中可通过 HttpServletRequestgetParameter() API 调用获得在此调用中我们将 FORM 元素的名称作为一个参数传递给 API 调用
从 Java 代码直接转换相对容易一些如清单 中的 servlet 所示
清单 响应(v)
class NamedHelloWorldServlet extends HttpServlet
{
def message(firstName : String lastName : String) =
<HTML>
<HEAD><TITLE>Hello {firstName} {lastName}!</TITLE></HEAD>
<BODY>Hello {firstName} {lastName}! It is now {currentTime}</BODY>
</HTML>
def currentTime =
javautilCalendargetInstance()getTime()
override def doPost(req : HSReq resp : HSResp) =
{
val firstName = reqgetParameter(firstName)
val lastName = reqgetParameter(lastName)
respgetWriter()print(message(firstName lastName))
}
}
但这缺少了我之前提到的消息分离的一些好处因为现在消息定义必须显式使用参数 firstName 和 lastName如果响应 get 中使用的元素个数超过 个或 个情况就会变得比较复杂此外doPost 方法在将参数传递给消息进行显示之前必须自行提取每一个参数 — 这样的编码很繁琐并且容易出错
一种解决方法是将参数提取和 doPost 方法本身的调用添加到一个基类如清单 中的版本 所示
清单 响应(v)
abstract class BaseServlet extends HttpServlet
{
import llectionmutable{Map => MMap}
def message : scalaxmlNode;
protected var param : Map[String String] = Mapempty
protected var header : Map[String String] = Mapempty
override def doPost(req : HSReq resp : HSResp) =
{
// Extract parameters
//
val m = MMap[String String]()
val e = reqgetParameterNames()
while (ehasMoreElements())
{
val name = enextElement()asInstanceOf[String]
m += (name > reqgetParameter(name))
}
param = Mapempty ++ m
// Repeat for headers (not shown)
//
respgetWriter()print(message)
}
}
class NamedHelloWorldServlet extends BaseServlet
{
override def message =
<HTML>
<HEAD><TITLE>Hello {param(firstName)} {param(lastName)}!</TITLE></HEAD>
<BODY>Hello {param(firstName)} {param(lastName)}! It is now {currentTime}
</BODY>
</HTML>
def currentTime = javautilCalendargetInstance()getTime()
}
这个版本使 servlet 显示变得比较简单(相对上一版本而言)而且增加了一个优点即 param 和 header 映射保持不变(注意我们可以将 param 定义为一个引用请求对象的方法但这个请求对象必须已经定义为一个字段这将引发大规模的并发性问题因为 servlet 容器认为每一个 do 方法都是可重入的)
当然错误处理是处理 Web 应用程序 FORM 的重要部分而 Scala 作为一种函数性语言保存的内容都是表达式这意味着我们可以将消息编写为结果页面(假设我们喜欢这个输入)或编写为错误页面(如果我们不喜欢这个输入)因此检查 firstName 和 lastName 的非空状态的验证函数可能如清单 所示
清单 响应(v)
class NamedHelloWorldServlet extends BaseServlet
{
override def message =
if (validate(param))
<HTML>
<HEAD><TITLE>Hello {param(firstName)} {param(lastName)}!
</TITLE></HEAD>
<BODY>Hello {param(firstName)} {param(lastName)}!
It is now {currentTime}</BODY>
</HTML>
else
<HTML>
<HEAD><TITLE>Error!</TITLE></HEAD>
<BODY>How can we be friends if you dont tell me your name?!?</BODY>
</HTML>
def validate(p : Map[String String]) : Boolean =
{
p foreach {
case (firstName ) => return false
case (lastName ) => return false
//case (lastName v) => if (ntains(e)) return false
case (_ _) => ()
}
true
}
def currentTime = javautilCalendargetInstance()getTime()
}
注意模式匹配可以使编写比较简单的验证规则变得很容易利用模式匹配绑定到原始值(比如上一个例子)或者绑定到一个本地变量(比如我们要排除任何姓名中有 e 的人比如上一个注释)
显然还有事情需要做!困扰 Web 应用程序的典型问题之一是 SQL 注入攻击它由通过 FORM 传入的未转义 SQL 命令字符引入并且在数据库中执行之前连接到包含 SQL 结构的原始字符串使用 scalaregex 包中的正则表达式支持或者一些解析器组合子(在本系列最后三篇文章中讨论)可以确认 FORM 验证是否正确事实上整个验证过程会提交给使用默认验证实现的基类该验证实现默认情况下只返回 true(因为 Scala 是函数性语言所以不要忽略好的对象设计方法)
结束语
虽然 Scala servlet 框架的功能不像其他一些 Java Web 框架的那样完整但是我这里创建的这个小 Scala servlet 有两个基本用途
● 展示以有趣的方式利用 Scala 的功能使 JVM 编程更简单
● 简单介绍将 Scala 用于 Web 应用程序这自然会引入 lift 框架(参见 参考资料 小节)
下载描述名字大小下载本文的样例 Scala 代码jscalazipKB点击
参考资料
● 您可以参阅本文在 developerWorks 全球网站上的 英文原文
● Scala 和 XML(developerWorks 年 月)说明如何使用 Scala 导航和处理解析的 XML并详述了对内置到该语言中的 XML 的良好支持
● wiki on the Scala lift framework这是用于编写 Web 应用程序的框架它演示了框架的着名理论(SeasideRailsDjango 和 Wicket 等)这是一本 入门教程