Scala显然是一种有趣的语言,非常适合展示语言理论和创新方面的精妙新思想,但是到最后,由于它具有任何“实际”用途,因此它必须能够在中间遇到实践中的开发人员。并在“现实世界”中具有一定的适用性。
既然我们已经研究了该语言的一些核心功能,可以认识到Scala的某些语言灵活性,并亲眼目睹了Scala在创建DSL方面的实际行动,那么该是时候开始接触实际应用程序使用的环境并展示Scala的方式了适合。 我们将从大多数Java™应用程序的核心:Servlet API开始,开始本系列的新阶段。
servlet回顾
回想一下,如果您想从Servlet 101类和教程中了解到,Servlet环境的核心本质上是使用HTTP协议通过套接字(通常是端口80)进行客户端-服务器交换。 客户端可以是任何“ User-Agent”(由HTTP规范定义),而服务器是Servlet容器,该容器可以在我编写的类上查找,加载和执行方法,这些类最终必须实现javax.servlet.Servlet
接口。
通常,实践Java开发人员不会编写直接实现该接口的类。 因为最初的servlet规范期望为HTTP以外的其他协议提供通用API,所以servlet命名空间分为两部分:
- 一个“通用”包(
javax.servlet
) - 特定于HTTP的一个(
javax.servlet.http
)
结果,在通用包中的抽象基类javax.servlet.GenericServlet
实现了一些基本功能。 然后,在派生类javax.servlet.http.HttpServlet
实现了其他特定于HTTP的功能,该类通常充当Servlet实际“肉”的基类。 HttpServlet
提供了Servlet
的完整实现,将GET
请求委托给了一个将被覆盖的doGet
方法,将POST
请求委托给了一个将被覆盖的doPost
方法,依此类推。
您好,斯卡拉。 您好,Servlet。
自然,任何人编写的第一个servlet都是无处不在的“ Hello,World” servlet。 Scala的第一个servlet示例应该没有什么不同。 回想一下很多年前的servlet入门教程,基本的Java“ Hello,World” servlet仅打印了清单1中HTML响应:
清单1.预期HTML响应
<HTML>
<HEAD><TITLE>Hello, Scala!</TITLE></HEAD>
<BODY>Hello, Scala! This is a servlet.</BODY>
</HTML>
在Scala中,编写一个简单的servlet来做到这一点几乎是非常容易的,并且看起来与Java等效,几乎如清单2所示:
清单2.您好,Scala servlet!
import javax.servlet.http.{HttpServlet,
HttpServletRequest => HSReq, HttpServletResponse => HSResp}
class HelloScalaServlet extends HttpServlet
{
override def doGet(req : HSReq, resp : HSResp) =
resp.getWriter().print("<HTML>" +
"<HEAD><TITLE>Hello, Scala!</TITLE></HEAD>" +
"<BODY>Hello, Scala! This is a servlet.</BODY>" +
"</HTML>")
}
注意,我使用了一些放置适当的导入别名来缩短请求和响应类型的类型名称。 除此之外,它看起来几乎与Java servlet版本相同。 进行编译时,切记要包含对servlet-api.jar的引用(该引用通常随servlet容器一起提供;在Tomcat 6.0版本中,它隐藏在lib子目录中)或找不到servlet API类型。
还没有准备好使用。 根据Servlet规范,必须使用web.xml部署描述符将其部署到Web应用程序目录(或.war文件)中,该描述符描述了该Servlet应使用的URL。 对于这样一个简单的示例,最简单的方法是使用一个非常简单的URL进行访问,如清单3所示:
清单3. web.xml,部署描述符
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>helloWorld</servlet-name>
<servlet-class>HelloScalaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>helloWorld</servlet-name>
<url-pattern>/sayHello</url-pattern>
</servlet-mapping>
</web-app>
从现在开始,我将假定读者将根据需要调整/修改部署描述符,因为它实际上与Scala无关。
毫不奇怪,格式良好HTML看起来很像格式良好的XML。 这是Scala的XML文字支持可以使编写此servlet变得非常简单的地方。 (参见文章“Scala和XML” 相关主题 。)而不是直接在字符串中嵌入所述消息传递给HttpServletResponse
,Scala中可以通过使用这种支持的实现逻辑和表示的(简单的,也许是过于左右)分离将消息本身放入XML实例并将其传递回:
清单4.您好,Scala servlet!
import javax.servlet.http.{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) =
resp.getWriter().print(message)
}
由于涉及XML文字的Scala的内联表达式评估工具,这意味着使servlet更加有趣变得更加容易。 例如,将当前日期添加到消息中就像将Calendar表达式添加到XML本身一样容易,大致如下:
{ Text(java.util.Calendar.getInstance().getTime().toString() )
}
或者,如果感觉有点太冗长,则类似于清单5:
清单5.您好,定时Scala servlet!
import javax.servlet.http.{HttpServlet,
HttpServletRequest => HSReq, HttpServletResponse => HSResp}
class HelloScalaServlet extends HttpServlet
{
def message =
<HTML>
<HEAD><TITLE>Hello, Scala!</TITLE></HEAD>
<BODY>Hello, Scala! It's now { currentDate }</BODY>
</HTML>
def currentDate = java.util.Calendar.getInstance().getTime()
override def doGet(req : HSReq, resp : HSResp) =
resp.getWriter().print(message)
}
本质上,Scala编译器将XML对象消息缝合到单个scala.xml.Node
,然后在传递给响应的Writer
的print
方法时将其转换为String。
不要轻描淡写-实际上,这是将表示与逻辑完全分离在一堂课中的强大分离。 XML消息将在编译时检查,以确保它在语法上正确且格式正确,这是我们使用标准servlet(就此而言,还是JSP)所没有的。 由于Scala具有类型推断特性,因此可以省略围绕message
和currentDate
的实际类型信息,这几乎像Groovy / Grails这样的动态语言一样读取。 对于第一次郊游,这不是一个坏结果。
当然,只读servlet非常无聊。
您好,斯卡拉。 这些是参数。
大多数servlet不仅传递诸如静态内容或当前日期和时间之类的简单消息,还传递一些简单的消息。 他们采用POST
ed形式的内容,检查内容,并做出相应的响应。 例如,也许Web应用程序希望知道使用它的个人的身份,并要求提供名字和姓氏:
清单6.挑战!
<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规范,数据将存储在一个名称/值对集合中,该集合通过HttpServletRequest.getParameter()
API调用获得,我们将FORM
元素的名称作为参数传递给API调用。
像清单7中的servlet一样,从Java代码进行直接转换相对容易:
清单7.响应! (v1)
class NamedHelloWorldServlet1 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 =
java.util.Calendar.getInstance().getTime()
override def doPost(req : HSReq, resp : HSResp) =
{
val firstName = req.getParameter("firstName")
val lastName = req.getParameter("lastName")
resp.getWriter().print(message(firstName, lastName))
}
}
但是,这开始摆脱了我之前引用的消息分离的一些优点,因为现在消息的定义必须显式地使用参数firstName
和lastName
。 如果响应中使用的元素数量大于三个或四个,则可能会变得笨拙。 另外, doPost
方法必须自己提取每个参数,然后再将它们传递给消息以进行显示-这种编码很繁琐,并且很容易出错。
一种解决方法是将参数的提取和doPost
方法本身的调用分解为基类,例如清单8中的版本2:
清单8.响应! (v2)
abstract class BaseServlet extends HttpServlet
{
import scala.collection.mutable.{Map => MMap}
def message : scala.xml.Node;
protected var param : Map[String, String] = Map.empty
protected var header : Map[String, String] = Map.empty
override def doPost(req : HSReq, resp : HSResp) =
{
// Extract parameters
//
val m = MMap[String, String]()
val e = req.getParameterNames()
while (e.hasMoreElements())
{
val name = e.nextElement().asInstanceOf[String]
m += (name -> req.getParameter(name))
}
param = Map.empty ++ m
// Repeat for headers (not shown)
//
resp.getWriter().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 = java.util.Calendar.getInstance().getTime()
}
该版本使实际的显示servlet相对简单(与其前身相比),并具有param
和header
映射不变的附加优点。 (请注意,我们可以将param
定义为引用请求对象的方法,但该请求对象必须已被定义为字段,这将在很大程度上引入并发性问题,因为servlet容器本质上考虑了每个do
方法都必须重新输入。)
当然,错误处理通常是处理Web应用程序FORM
的重要部分,而Scala作为一种功能语言,认为所有东西都是表达式,这意味着我们可以将消息写为结果页(假设我们喜欢输入内容)或错误页面(如果我们不喜欢)。 因此,用于检查firstName
和lastName
的非空状态的验证函数可能类似于清单9:
清单9.响应! (v3)
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 don't 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 (v.contains("e")) return false
case (_, _) => ()
}
true
}
def currentTime = java.util.Calendar.getInstance().getTime()
}
请注意模式匹配的方式(具有绑定到原始值(例如前一种情况)或绑定到局部变量(例如我们要排除名称中带有“ e”的任何人的情况,例如上一条注释中的情况)的能力)使编写相当简单的验证规则变得容易。
显然,这里可以做更多的事情。 困扰Java Web应用程序的经典问题之一是SQL注入攻击,这种攻击是通过未转义SQL命令字符传递的,这些字符通过FORM
传递并与包含SQL结构的原始字符串连接在一起,然后再对数据库执行。 可以使用scala.regex包中的正则表达式支持甚至本系列最后三篇文章中讨论的一些解析器组合器思想来验证FORM
输入是否正确。 实际上,可以使用默认的验证实现将整个验证过程提升到基类中,该实现默认情况下仅返回true。 (因为Scala是一种功能语言,所以我们不要忽视良好的对象设计方法。)
结论
尽管它的功能不像其他Java Web框架那样完整,但是我在这里创建的这个很小的Scala servlet框架却有两个基本目的:
- 为了演示可以通过一些有趣的方式利用Scala的功能,从而简化JVM的编程。
- 作为一个温和的介绍了如何使用Scala的Web应用程序,的想法这自然导致一个参考的“升降机”的框架,在参考相关主题部分。
这是本期的内容; 直到下一次,尽情享受!
翻译自: https://www.ibm.com/developerworks/java/library/j-scala12228/index.html