介绍
所有的Java Web应用都在使用Servlets 和 Filters,因为它们是连接www网的大门,是Java EE的基础设施,也可以说是Java EE的骨架。
新规范Servlet3.0(JSR-315)马上就要出台了。规范的起草方案中确实包含了一些非常显眼的特性,但以我看来,有些似乎是错误。下面的内容我将依次介绍这些有利于编程的特性,对它们进行评论,并试着改进它门。
Servlets
自从有了Servlet2.3规范,我们就一直使用Servlet提供的接口创建自己的Servlet类。接口提供了像service()这样的重要方法,当然还有ServletRequest 和ServletResponse对象。而抽象类HttpServlet提供了对HTTP协议的支持,它含有像doGet和doPost这样的方法。
创建一个一般的Servlet,只需继承HttpServlet类,并重写你自己的doGet或doPost方法就可以了。下面是实现时的代码片段。
public class GetPostServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
当然,你需要在一个叫做web.xml的文件中,添加对这个Servlet的描述,来通知容器你新增加了一个Servlet。web.xml中包含了URL与Servlet间的映射,所以当有用户请求URL的时候,Servlet将会被调用,用来对请求做出响应。
JSR-315
Servlet 3.0 有几个提议性的改动。最大的改变就是增加了“内建”(continuations)的支持。可以通过调用新Servlet3.0API中的request.suspend()方法悬挂请求,稍后调用request.resume()方法在另一个线程接着处理请求。
第二个重要的特征是,支持即插即用的特性,Servlets能在容器运行期间,动态地被添加到容器中。 容器运行期间,你可以通过创建了一个ServletContextListener实例,调用servletContext.addServlet()和servletContext.addServletMapping()方法将Servlets动态添加到容器中。当然,这会带来一些安全隐患,因为工程里被包含的JARS可能会创建一些你不想定义的Servlets。但是新规范注意到了这些问题,并解决了它们。(比如,新规范提供了关闭自动Servlets注册的开关。)
新增加了注解是Servlet3.0的第三个特征。下面展示了新注解的使用:
@Servlet(urlMapping={"/myServlet"}, name="MyServlet")
public class PojoServlet() {
@GET
public void handleGet(HttpServletRequest req, HttpServletResponse resp) {
....
}
}
正如你看到的,新规范确实添加了些新的东西。首先现在的Servlet是简单的POJO,省去了接口和抽象类。其次,注意到@Servlet注解中的url属性了没,这样就不用在web.xml对Servlet做映射了。
接下来我们看看过滤器的例子:
@ServletFilter
@FilterMapping(urlPatter="/myFilter")
public class PojoFilter() {
public void doFilter(HttpServletRequest req, HttpServletResponse resp) {
....
}
}
过滤器也使用了新的注解,又一次在注解中做映射,省去了web.xml中的配置,POJO实现方式,没有接口没有抽象类。
问题在那里?
也许你会问,这些有利于开发的提议有什么错误呢?呵呵,也许存在一些问题。不管怎么说,首先让我们来看看有这些新注解带来了什么改变。
- 映射信息写在了Servlet中
- Servlet变成了POJO’s
- 可以定义某些方法来捕捉GET和PUT请求
好吧,首先说在Servlet中做映射是个不错的想法,尽管注解的命名有些出乎意料。 但是,为什么@Servlet注解自包含了映射信息,而@ServletFilter注解与@FilterMapping 注解分开使用?用一种统一的方式声明映射信息,恐怕要更清晰一些吧。
第二、三个改变只是提供了一点编程的便利,可以用@GET、@POST、@HEAD、@PUT声明式地描述GET和POST.一些人可以用注解声明的方式编写Servlet了而不是用以前的方法:
public class GetPostServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
handleRequest(req,res);
}
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
handleRequest(req,res);
}
/* Handle GET and POST */
public void handleRequest(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
用上注解可以省去一些代码,编程变的容易了些:
@Servlet
public class GetPostServlet {
@GET
@POST
public void handleRequest(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
是的,这里没有什么不对的。但是,我会问是否能将两个注解同时应用在一个方法上呢?这样做也许违背了规范的初衷。还有,这也给编码带来了一些问题,例如,如果按我下面这样编码:
@Servlet
public class GetPostServlet {
@GET
public void requestOne(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
@GET
public void requestTwo(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
....
}
}
这样那个方法会被调用,我不知道!这些都没有在规范里面做相应的说明,类似地问题,两个不同的Servlet映射在同一个URL上。不能预测这样做会发生什么,如何操作将依赖于容器。
这些新注解本意是方便开发者做开发,现在看来,却存在一些问题。集成开发工具不会因为POJO中添加了@Servlet注解,而意识到你在创建一个Servlet,所以它不会自动生成相应的东西。当我们使用接口或者抽象类的时候,我们只需实现或重载一些方法就可以了,有相当一部分代码是自动生成的。而这种便利性将会随着我们应用了注解而消失。
如果你仔细的观察@ServletFilter注解,会发现更糟糕的事情。当你把@ServletFilter应用于某个POJO的时候,它只是把这个POJO变成了一个过滤器,但是并没有检查是否实现了过滤器的doFilter()方法。还有,很有可能把doFilter意外地写错成别的什么东西。
@ServletListenerContext注解也会发生同样的事情,看看下面的例子吧:
@ServletContextListener
public class MyListener {
public void contextInitialized(ServletContextEvent sce) {
....
}
}
又一次,“contextInitialized“(一个非常不好写的单词,每次不检查拼写错误保证输入正确显的有些困难)不会被检查是否拼写错误,你必须人工地双击选中检查。我宁愿实现ServletContextListener让集成开发环境帮我生成要实现的方法,而不会去使用@ServletListenerContext注解。