写在前面
第一次接触Java Servlet是在之前的软件工程大作业中学习构建一个在线课程平台,浏览器与服务器之间的响应是通过Servlet来完成的,后来根据老师的需要完成一个在线问卷调查开发。有必要系统的整理一下Servlet知识,不然总是遇到问题就上网查,查来查去也就这么点东西,太零碎了。所以开辟这个分类系统整理一下。
首先,这第一篇博客将主要对Servlet API的概览,从而实现对Servlet 的整体认识。
Servlet API概览
Servlet API有四个Java包:
- javax.servlet, 其中包含定义Servlet和Servlet容器之间契约的类和接口。
- javax.servlet.http, 其中包含定义HTTP Servlet和Servlet容器之间契约的类和接口。
- javax.servlet.annotation, 其中包含标注Servlet、Filter、 Listener的标注。 它还为被标注元件定义元数据。
- javax.servlet.descriptor, 其中包含提供程序化登录web应用程序的配置信息的类型。
Servlet技术的核心是Servlet, 它是所有Servlet类必须直接或间接实现的一个接口。 在编写实现Servlet的Servlet类时, 直接实现它。 在扩展实现这个接口的类时, 间接实现它。
Servlet接口定义了Servlet与Servlet容器之间的契约。 这个契约归结起来就是, Servlet容器将Servlet类载入内存, 并在Servlet实例上调用具体的方法。 在一个应用程序中, 每种Servlet类型只能有一个实例。
用户请求致使Servlet容器调用Servlet的Service方法, 并传入一个ServletRequest实例和一个ServletResponse实例。 ServletRequest中封装了当前的HTTP请求, 因此, Servlet开发人员不必解析和操作原始的HTTP数据。 ServletResponse表示当前用户的HTTP响应, 使得将响应发回给用户变得十分容易。
对于每一个应用程序, Servlet容器还会创建一个ServletContext实例。 这个对象中封装了上下文(应用程序) 的环境详情。 每个上下文只有一个ServletContext。每个Servlet实例也都有一个封装Servlet配置的ServletConfig。
Servlet
Servlet接口中定义了以下5个方法:
- void init(ServletConfig config) throws ServletException
- void service(ServletRequest request, ServletResponse response) throws ServletException, java.io.IOException
- void destroy()
- java.lang.String getServletInfo()
- ServletConfig getServletConfig()
注意, 编写Java方法签名的惯例是, 对于与包含该方法的类型不处于同一个包中的类型, 要使用全类名。正因为如此, 在Service方法javax.servlet.ServletException的签名中(与Servlet位于同一个包中) 是没有包信息的, 而java.io.Exception则是编写完整的名称。
生命周期方法
- init, 当该Servlet第一次被请求时, Servlet容器会调用这个方法。 这个方法在后续请求中不会再被调用。 我们可以利用这个方法执行相应初始化工作。调用这个方法时, Servlet容器会传入一个ServletConfig。 一般来说, 你会将ServletConfig赋给一个类级变量, 因此这个对象可以通过Servlet类的其他点来使用。
- Service, 每当请求Servlet时, Servlet容器就会调用这个方法。 编写代码时, 是假设Servlet要在这里被请求。 第一次请求Servlet时, Servlet容器调用init方法和Service方法。 后续的请求将只调用Service方法。
- destroy, 当要销毁Servlet时, Servlet容器就会调用这个方法。 当要卸载应用程序, 或者当要关闭Servlet容器时, 就会发生这种情况。 一般会在这个方法中编写清除代码。
非生命周期方法
- getServletInfo, 这个方法会返回Servlet的描述。 你可以返回有用或为null的任意字符串。
- getServletConfig, 这个方法会返回由Servlet容器传给init方法的ServletConfig。 但是, 为了让getServletConfig返回一个非null值, 必须将传给init方法的ServletConfig赋给一个类级变量。
注意线程安全性。 Servlet实例会被一个应用程序中的所有用户共享, 因此不建议使用类级变量, 除非它们是只读的, 或者是java.util.concurrent.atomic包的成员。
第一个Servlet程序
我将尝试按照书《Servlet、JSP和Spring MVC初学指南》来实现第一个Servlet程序。编写一个名为app01a的Servlet应用程序。 最初, 它会包含一个Servlet, 即MyServlet, 其效果是向用户发出一条问候。
要运行Servlets,需要一个Servlet容器,我采用的是Tomcat,它是一个开源的Servlet容器。安装教程网上有很多,我就不罗列了。
搞定Tomcat之后,可以编写和编译第一个Servlet类,按照惯例,Servlet类的名称要以Servlet作为后缀。代码如下:
package app01a;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "MyServlet", urlPatterns = { "/my" } )
public class MyServlet implements Servlet {
private transient ServletConfig servletConfig;
@Override
public void init(ServletConfig servletConfig)
throws ServletException {
this.servletConfig = servletConfig;
}
@Override
public ServletConfig getServletConfig() {
return servletConfig;
}
@Override
public String getServletInfo() {
return "My Servlet";
}
@Override
public void service(ServletRequest request,ServletResponse response)
throws ServletException,IOException {
String servletName = servletConfig.getServletName();
response.setContentType("text/html");
PrintWriter writer = response.getWriter();
writer.print("<html><head></head>"+ "<body>Hello YidaZhang from " + servletName + "</body></html>");
}
@Override
public void destroy() {
}
}
这里面还是有很多东西,对于我这种还没有系统学过Java的人颇具挑战的,我们一条一条来看,参考常见的Java标注/Java注解和各种符号:
- @WebServlet标注类型用来声明一个Servlet。 命名Servlet时, 还可以暗示容器, 是哪个URL调用这个Servlet。 name属性是可选的, 如有, 通常用Servlet类的名称。 重要的是urlPatterns属性, 它也是可选的, 但是一般都是有的。 在MyServlet中, urlPatterns告诉容器, /my样式表示应该调用Servlet。
- @Override:表示重写的方法,编译器会验证@Override下面的方法名是否是父类中存在的,如果不存在则会报错。
- URL样式必须用一个正斜杠开头。URL是( Uniform Resource Locator )统一资源定位符的缩写。
URL完整格式
参考URL格式:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
- 传送协议。
- 层级URL标记符号(为[//],固定不变)
- 访问资源需要的凭证信息(可省略)
- 服务器。(通常为域名,有时为IP地址)
- 端口号。(以数字方式表示,若为HTTP的默认值“:80”可省略)
- 路径。(以“/”字符区别路径中的每一个目录名称)
- 查询。(GET模式的窗体参数,以“?”字符为起点,每个参数以“&”隔开,再以“=”分开参数名称与数据,通常以UTF8的URL编码,避开字符冲突的问题)
- 片段。以“#”字符为起点
回到刚才的程序,Servlet的init方法只被调用一次, 并将private transient变量ServletConfig设为传给该方法的ServletConfig对象:
调用Servlet
要测试这个Servlet, 需要启动或者重启Tomcat, 并在浏览器中打开下面的URL(假设Tomcat配置为监听端口8080, 这是它的默认端口) :
http://localhost:8080/app01a/my
输出结果如下: