2005年9月26日,Sun公司和JSR154的专家组发布Servlet API的一个新的版本。在一般情况下,一个JSR的新版本仅仅包括对以前少数有名无实的规范进行去除更新。但这次,新版本中增加新的特征和变化,他们对Servlets的产生重要影响,使得Servlet的版本升到了2.5。
在这篇文章里,我主要谈谈Servlet2.5版本中的新特征。描述每一个变化,阐述那些必要变化产生的背景,并展示如何在基于Servlet的项目中利用这些变化。
事实上,这是我为JavaWorld提供的第六篇关于Servlet API更新资料的文章。这篇文章意在两个目的:从眼前来看,向你介绍Servlet的新特征。从长远来看,是展现Servlet变化的历史概要,这样当你基于老的Servlet API版本进行编码的时候,你可以正确地决定哪些特征和功能你可以使用,而哪些特征和功能你不应该使用。你可以参考我先前写的关于Servlet的文章。
注意:当你想实践这些Servlet的新特征和功能时,你要知道的是:并不是所有的Servlet容器和Java企业级应用服务器都能立即适用于最新版的Servlet API,写这篇文章时(2006年1月2日),Jetty 6 server和Sun公司的GlassFish server是公认最好的支持Servlet2.5的容器,而Apache Tomcat5.5和Jboss 4.0目前只支持Servlet2.4。
Servlet2.5一些变化的介绍:
1) 基于最新的J2SE 5.0开发的。
2) 支持注释。
3) web.xml中的几处配置更加方便。
4) 去除了少数的限制。
5) 优化了一些实例
J2SE 5.0的产物:
从一开始,Servlet 2.5 规范就列出J2SE 5.0 (JDK 1.5) 作为它最小的平台要求。它使得Servlet2.5只能适用基于J2SE 5.0开发的平台,这个变动意味着所有J2SE5.0的新特性可以保证对Servlet2.5程序员有用。
传统意义上,Servlet和JEE版本一直与JDK的版本保持同步发展,但是这次,Servlet的版本跳过1.4版本。专家组认为版本的加速增长是正当的,因为J2SE5.0提出一个引人注目的,Servlet和JEE规范都要利用的特征??注释。
注释:
注释是作为JSR175的一部分提出的(一种为Java语言设计提供便利的Metadata)一种新的语言特色。它是利用Metadata为Java编码结构(类,方法,域等等)装饰的一种机制。它不能像代码那样执行,但是可以用于标记代码,这个过程是基于Metadata信息的代码处理机,通过更新他们的事件行为来实现的。
我们可以凭借不同的技巧来注释类和方法,例如连续地标记接口或者是@deprecated Javadoc评论。这种新式的Metadata可以便利地提供了一种标准的机制来实现注释功能,以及通过库来创建用户自己的注释类型的变量。
下面是一个简单的Web service 注释例子:
import javax.jws.WebService;
import javax.jws.WebMethod;
@WebService
public class HelloWorldService {
@WebMethod
public String helloWorld() {
return "Hello World!";
}
}
@WebService和@WebMethod这两个注释类型,在JSR181(为Java平台提供的Web ServicesMetadata)有详细说明,可以像类一样的引用,标记这个类作为一个Web service并且标记它的helloWorld()方法做为一个Web service方法。对于他们本身来说,注释只是写在那里并没有什么作用,好像在岗位上做记录一样,但是,一个容器一旦加载这个类并对那些注释进行二进制编码,就可以把这个类连到Web service上。
注释可以接受属性/值这些参数。它保存着参数的信息并且可以利用这些参数来更改被请求的事件行为。例如下面更高级的注释例子:
@WebService(
name = "PingService",
targetNamespace=http://acme.com/ping
)
@SOAPBinding(
style=SOAPBinding.Style.RPC,
use=SOAPBinding.Use.LITERAL
)
public class Ping {
@WebMethod(operationName = "Foo")
public void foo() { }
}
一旦加载了这个类,一个正确配置的容器就会识别出注释及其参数,并将这个做为一个PingService通过利用remote-procedure-call/literal的编码方式与一个Foo operation相连。实际上,注释便指明了类和类的容器之间的联系。
Java本身的规范(JSR175)仅仅有少量的注释类型变量。而这些有趣的注释类型变量主要来自于其他的JSRs:
•JSR 250: Java平台的公共注释
•JSR 220: 企业级JavaBeans 3.0
•JSR 224: 基于XML的Java API Web Services (JAX-WS) 2.0
•JSR 181: Java平台的Web Services Metadata
Servlet2.5中的注释:
回到Servlet2.5上来,一种新的规范描述了几种注释在Servlet环境中是如何工作的。功能弱的Servlet容器忽略了这些规范,然而JEE容器中的Servlet却严格遵守这些规范。
有的注释提供了在XML注册的可选择性,否则就要注册在配置文件web.xml中。有的作为容器的请求来执行其任务,否则就由Servlet亲自来执行。还有的注释两者都具备。
注释准确的定义不是完全固定的,因为Servlet本身并没有定义注释。它仅仅解释了它们如何影响Servlet环境,下面是注释的一个简要的概述,你可以看到在JEE5中它们的用途:
and @Resources:@Resource位于类或变量中以对Servlet容器进行“资源注入”。当容器识别出这个注释时,它会在获得服务地位之前,用适当的值实现带注释的变量的重新注入。通过使用这种注释,你不必利用JNDI来查找命令和在配置文件web.xml中手动声明资源。服务器通过Servlet的自我调整来执行它的任务。变量的名称和类型由映像机制自动确定,尽管你可以利用注释的参数来超越这一限制。一个注入的资源可以是数据源,Java信息服务目的文件或者是环境设置的标量。下面是一个例子:
@Resource javax.sql.DataSource catalog;
public getData() {
Connection con = catalog.getConnection();
}
现在,在这段Servlet代码变成服务之前,容器会定位JNDI变量,并对于目录变量进行手动分配。
为了效率,仅仅某些类支持资源注入,这些类有:Servlets,Servlet过滤器,Servlet事件监听器,JSP标签操作器,JSP库事件监听器,用于处理beans的JSF,以及一些与Serlvets无关的类。
注释与@Resource相似,但是它用于一组@Resource注释。它们都来自JSR250,是Java平台的公共注释。
and @PreDestroy:可以使方法成为带有生命周期的方法。@PostConstruct方法用于资源注入初始化之后。@PreDestroy方法用于Servlet脱离服务并释放注入的资源的时候。回收的方法必须是事实的方法,返回void并且不可以抛出任何异常。这些注释本质上使得任何方法都成为init()和destroy()的子方法,这些特征也来自与JSR250。
:类似于@Resource,设计用于注入企业级的JavaBeans。比起@Resource,它略有不同,在于@EJB的参数特定设计用来定位EJB的参数。这个注释来自EJB3.0的规范。
与@Resource 和 @EJB相似,设计用于注入Web service参数。来自于JAX-WS2.0规范。
@PersistenceContexts, @PersistenceUnit, and @PersistenceUnits:这些注释来自EJB3.0规范来支持Java对象的持久化。
定义应用程序中安全角色的使用。当定义一个Servlet类时,在配置文件web.xml中<security-role>标签中对它进行设置,来自JSR250。
• @RunAs:用于声明哪个类应该执行。当定义一个Servlet类时,在配置文件web.xml中<run-as>标签中对它进行设置。来自于JSR250。注释的执行:
不论你使用注释与否??尤其在你不使用时??它对于理解服务器上程序的执行有着重要意义。为了让服务器识别类中的注释,它必须加载这些类,这就意味着服务器必须是启动着的,服务器通过WEB-INF/classes目录下和WEB-INF/lib目录下的所有类文件来查找注释。(每个规范下,服务器不必查找这两个目录以外的目录。)你可以通过下面的方法指明<web-app>根的属性而不必使用如何进行注释:
<web-app xmlns=" version="2.5" full="true">
</web-app>
web.xml的便利:
Servlet2.5对于web.xml引入几个小的变动,使得它更加方便。
Servlet名称的通配符化:
首先,当你写<filter-mapping>,你现在可以在<Servlet-name>标签中使用*号来代表所有的Servlets。而以前,你必须一次把一个Servlet绑定到过滤器上,像这样:
<filter-mapping>
<filter-name>Image Filter</filter-name>
<Servlet-name>ImageServlet</Servlet-name>
</filter-mapping>
现在,你可以一次绑定所有的Servlets:
<filter-mapping>
<filter-name>Image Filter</filter-name>
<Servlet-name>*</Servlet-name> <!?新特征 -->
</filter-mapping>
这有着很大用途,例如:
<filter-mapping>
<filter-name>Dispatch Filter</filter-name>
<Servlet-name>*</Servlet-name>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
映射的复合模式:
其次,当我们写<Servlet-mapping> 或者 <filter-mapping>时,你现在可以在同一的标签中采用复合匹配的标准。以前一个<Servlet-mapping>只支持一个<url-pattern>元素,现在它不只支持一个,例如:
<Servlet-mapping>
<Servlet-name>color</Servlet-name>
<url-pattern>/color/*</url-pattern>
<url-pattern>/colour/*</url-pattern>
</Servlet-mapping>
同样地,以前<filter-mapping>也是只支持一个<url-pattern> 或者一个 <Servlet-name>。现在它对于每个元素都可以支持任意多个:
<filter-mapping>
<filter-name>Multipe Mappings Filter</filter-name>
<url-pattern>/foo/*</url-pattern>
<Servlet-name>Servlet1</Servlet-name>
<Servlet-name>Servlet2</Servlet-name>
<url-pattern>/bar/*</url-pattern>
</filter-mapping>
HTTP方法名:
最近,你可以将合法的HTTP/1.1方法名放进<http-method>元素中。当你使用这些方法时,<http-method>将指明<security-constraint>标记里的方法应该被应用。从以前来看,它仅限于HTTP/1.1的7个标准方法:GET,POST,PUT,DELETE,HEAD,OPTIONS和TRACE。但是,HTTP/1.1允许对方法进行扩展,WebDAV是用于这种扩展的普遍技术。在Servlet2.5中,你可以安全地约束任何可能的HTTP方法名,标准及扩展,包括WebDAV方法,例如:LOCK,UNLOCK,COPY及MOVE。
如果你写一个WebDAV的Servlet,你不必使用doLock()和doCopy()方法。你必须写自己的service()方法及分派request.getMethod()方法。正由于这种变化,你不必管理系统的安全性。
去除限制:
Servlet2.5去除了关于错误处理和回话跟踪的一些限制。对于错误处理,Servlet2.5之前,配置在<error-page>中的错误处理页面不能通过调用setStatus()方法来修改触发他们的错误代码,而Servlet2.5减弱了这一规范。这样的规范的产生于这样的观点,就是错误页面的工作是指出每个错误而不是修改错误。但是,实际使用中,错误页面不只是用于指出错误,而是还能做更多的事情,或许可以代替在线帮助来帮助用户解决问题。这个规范将不再限制错误页面所产生的反馈信息。
对于会话跟踪,Servlet2.5之前,调用RequestDispatcher.include()的Servlet不能设置响应的标题头,而Servlet2.5减弱了这一规范。原规范的目的是使内部的Servlets限制在自己的页面空间中,不可以影响外部的页面。现在这个规范已经减弱,允许在内部的Servlet中使用request.getSession()命令,这个命令可以悄悄地创建一个会话跟踪cookie的标题头。逻辑上要求限制内部的资源,但逻辑上也要求这些限制不应该取消其启动session的这一功能。这个变动对于Portlet规范来说显得尤其重要。作用是:如果响应已经有效,则getSession()命令就会抛出一个IllegalStateException(异常),而在此之前,就没有这个功能。
优化:
最近,新的规范优化了一些实例,使得Servlets更加方便而且保证了更好地按要求工作。
终止响应:
第一处优化细小又深奥,但做为规范中的一个例子还有蛮有趣的。Servlet2.4规范规定响应在这几种情况下应该是有效的,包括:在响应的setContentLength方法中内容已经明确说明,以及内容已经写进了响应中。这种情况只有你的代码像下面这样才可以使响应重新定向:
response.setHeader("Host", "localhost");
response.setHeader("Pragma", "no-cache");
response.setHeader("Content-Length", "0");
response.setHeader("Location", "");
Servlet技术忽略特定区域的标题头,因为内容满足0字节长度,响应就会立即生效。而在它开始之前,响应就已失效了!Servlet容器通常拒绝执行这种行为,而Servlet2.5版本增加了“长度必须大于0”这个原则。
实例编码:
Servlet2.4规范规定必须在调用request.getReader()方法之前调用request.setCharacterEncoding()方法。但是,如果你忽略这个原则而在其之后去调用request.setCharacterEncoding()方法,那么会产生什么后果,这个问题规范里并没有说。为了简便,现在消除这种情况!
Cross-context sessions(不同上下文目录间的会话):
最近,关于Cross-context会话处理的规则已经明确说明。当Servlets指派从一个上下文到其他上下文的请求时,这个规则就发挥了作用??在目标调用过程中,包括哪些会话。这个版本的出现使得一个上下文目录的主页里的portlets可以通过几种内部的命令来对别的上下文目录里的portlets起作用。Servlet2.5明确指出一个上下文目录里的资源可以访问其他上下文目录的session(会话),而不用考虑这个请求从哪里开始的。这意味着portlets可以脱离主页的范围而在自己的范围里运行,而且这个规范还会应用在不兼容的Serlvet容器中。
期待:
由于Servlet2.5版本要保持一些旧的性质,几个大的概念不得不延后到下一个阶段。它们包括:
•新的输入/输出(NIO)支持:使NIO通道更有利于Servlets进行客户端通信成为可能。
•过滤器wrap-under或wrap-over语义:有时用过滤器包装请求,和/或者响应对象去修改方法行为或者启用新的方法。当把这种包装和服务器对请求和响应的包装结合起来时,又应该怎么包装在一起?
•用于欢迎的Servlets文件:做为索引应该充当欢迎作用的文件吗?在此之前,这个回答是肯定的。但是规范没有明确说明如何使用这个功能,尤其在没有索引的情况下。
•用于欢迎的文件的分派规则:如何分派欢迎文件,这个细节并没有完全说明,而是遗留了一些开放的缺口来应对不兼容问题。
•登陆后选择默认页面:如果用户通过他们的书签访问Servlet的登陆页面,那么在成功登陆后页面应该转向哪里呢?这个问题至今尚未明确说明。
•用户的主题日志:在通过网站正确地注册之后,不通过传统地登陆方式没有办法使Servlet信任用户。
结束语:
如果抛开注释来看Servlet2.5的变化,可见在配置文件web.xml中去除了一些限制,是有利的,同时又优化了实例行为使其更适合更便于开发Web系统(网页)。
Servlet2.5中注释的作用更加戏剧化。Servlets本身不能声明注释类型的变量,甚至性能弱的Servlet容器都不支持注释。然而在JEE5环境下的Servlets编写者可以看到,通过公共的注释及EJB3.0和JAX-WS2.0规范而引入的注释类型会对代码产生很大变化,并且这也将对Servlet如何管理外部资源,对象的持久化及EJB的构成产生重大影响。