2014
08-11

Servlet

Servlet是一个运行在服务器端Java小程序,通过HTTP协议(通常情况)接收和响应客户端的请求。

1、Servlet接口

Sun公司在其API中提供了一个Servlet接口(javax.servlet包中),该接口定义了所有Servlet程序都必须要实现的方法,如init()、service()、destroy()、getServletConfig()等。

init()、service()、destroy() 方法被称为Servlet的生命周期方法(后面具体讲),service()是Servlet接口中定义的一个非常重要的方法,每次客户端请求时都会被执行,该方法的声明为:void service(ServletRequest req , ServletResponse res) throws Xxx

ServletRequst req:请求对象,该对象中有HTTP协议的请求部分的所有内容。它的实例是由服务器创建的,封装数据也是服务器来做的。

ServletResponse res:响应对象,该对象中为HTTP协议的响应部分。它的实例由服务器创建的,但封装数据是由我们完成的,即res对象是由我们写数据进去。

2、编写第一个Servlet程序:Hello world!

一般不直接去实现一个Servlet接口,而是继承GenericServlet类(或者HttpServlet,后面会讲)来编写一个Servlet程序。GenericServlet是一个抽象类,它实现了Servlet接口,里面有一个唯一的抽象方法service(),因此只要实现其中的service()方法即可。

1)手工编写

① 在webapps目录下新建一个文件夹hello,并创建Web工程的目录结构。

② 在classes目录下编写一个类HelloServlet继承GenericServlet,并实现service()方法,方法体为:res.getWriter().write(“Hello world!”);

③ 编译(需要在classpath中添加servlet的jar包)

④ 映射Servlet:在web.xml中添加下面的元素:

06. Servlet929

⑤ 启动Tomcat,访问http://localhost:8080/hello/helloServlet

2)用MyEclipse编写

新建web工程hello — 新建Servlet ,一次填写类名、父类名、Servlet名、映射URI等……在此不再赘述。

3)Servlet的执行过程(☆)

① 客户端发出请求http://localhost:8080/hello/helloServlet

② 服务器找到hello工程和该工程的web.xml

③ 根据web.xml的配置,找到<url-pattern>子元素的值为”/hello”的<servlet-mapping>元素

④ 由servlet-name找到该Servlet类名(org.flyne.HelloServlet)

⑤ 到hello/WEB-INF/classes/org/flyne目录下找到HelloServlet.class文件

⑥ 执行service()方法,并将结果返回给用户。

4、HttpServlet抽象类

除了Servlet接口,Sun公司还定义了两个实现类:

javax.servlet.GenericServlet:通用的

javax.servlet.http.HttpServlet:与HTTP协议有关的(接收和响应HTTP请求)

HttpServlet在实现Servlet接口时,覆写了service方法,该方法体内的代码会自动判断用户的请求方式,如为GET请求,则调用HttpServlet的doGet方法,如为Post请求,则调用doPost方法。因此,开发人员在编写Servlet时,通常只需要覆写doGet或doPost方法,而不要去覆写service方法。

Servlet接口、GenericServlet类和HttpServlet类的关系如下:

06. Servlet1708

5、Servlet的生命周期

Servlet的生命周期指的是内存中的Servlet对象从被创建到被销毁的过程,大致有如下几个时间节点:

创建:对应init()方法,用户第一次访问时,由容器创建Servlet的实例。(只被调用一次)

活着:对应service()方法,一旦Servlet实例创建就一直驻留内存,对一个Servlet的每次访问请求都导致Servlet引擎调用一次Servlet的service()方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。

销毁:对应destroy()方法,应用被卸载(undeploy)或者Tomcat被关闭。

如果在<servlet>元素中配置一个<load-on-startup>元素,那么Web应用在启动时,就会装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。

06. Servlet2211

6、Servlet的一些小细节

1)Servlet的线程安全问题

内存中的Servlet对象只有一份,当多个用户线程同时访问时,如果在service()方法中访问了同一个资源,就有可能导致Servlet的线程安全问题。

要避免Servlet的线程安全问题,最好的解决方式就是在编写Servlet时尽量使用局部变量,不要使用实例变量。

 2)<url-pattern>写法相关

在Servlet映射中,需要在web.xml为一个Servlet指定一个<url-pattern>,它定义了一个Servlet的对外访问路径。

① 同一个Servlet可以被映射到多个URL上,可以为一个Servlet写多个<servlet-mapping>,也可以在<servlet-mapping>中写多个<url-pattern>,如下的两种方式是等价的:

06. Servlet2584

② * 通配符:<url-pattern>中*通配符有两种固定的格式:“*.扩展名”和“/*”,且“/*”优先级略高。例子:

06. Servlet2649

3)默认Servlet(了解):默认Servlet负责处理用户的请求URL找不到匹配Servlet的情况,默认Servlet映射路径为<url-pattern>/</url-pattern>,这个不需要用户自己配。

在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。

7、ServletConfig:Servlet的参数配置

ServletConfig代表web.xml中servlet的初始化参数配置,如下图所示:

06. Servlet3001

当servlet配置了初始化参数后,web容器在创建servlet实例对象时,会自动将这些初始化参数封装到ServletConfig对象中,并在调用servlet的init方法时,将ServletConfig对象传递给servlet。进而,程序员通过ServletConfig对象就可以得到当前servlet的初始化参数信息。

1)在Servlet中获得ServletConfig对象:ServletConfig config = getServletConfig();过程如下:

06. Servlet3244

因此,在编写Servlet时,如果要覆写init()方法时,一定要覆写不带参数的init(),否则getServletConfig()失效。

2)ServletConfig中的常用方法:

① String getInitParameter(name)、Enumeration<String> getInitParameterNames()

② ServletContext getServletContext():返回ServletContext对象。

8、ServletContext:整个web应用

ServletContext代表当前应用,Web容器在启动时会为每个Web应用都创建一个对应的ServletContext对象。

一个应用中的所有Servlet共享同一个ServletContext对象,所以各个Servlet之间可以通过ServletContext对象实现通讯。

1)在servlet中获取ServletContext对象:

正常获取:getServletConfig().getServletContext(),不过在GenericServlet中getServletContext()方法封装了这一过程,因此在Servlet中直接调用getServletContext()即可获得代表当前应用的ServletContext对象。

2)ServletContext的应用

① 多个Servlet通过ServletContext对象实现数据共享:setAttribute()、getAttribute()

② 获取Web应用的初始化参数,该参数在web.xml中设置:

06. Servlet3945

参数的获取同Servlet的初始化参数一样。(ServletContext提供了两个方法:getInitParameterNames() 和getInitParameter()

③ 实现Servlet的转发(服务器端跳转):下图是/context/demo4所请求的Servlet中的service()方法,当用户请求/context/demo4时,用户请求将会被转发给/context/demo3对应的Servlet。

06. Servlet4159

注:传给ServletContext对象的地址必须以”/”开头,该”/”就代表当前应用的访问路径。

④ 实现文件下载(通过ServletContext对象的getRealPath()获取文件绝对路径)

public void doGet(HttpServletRequest request, HttpServletResponse response)	throws ServletException, IOException {
	//当用户访问此Servlet的时候,提示用户下载src目录下的“霉女.jpg”图片
	ServletContext sc = getServletContext();
	//获得“霉女.jpg”的真实路径,用于构建输入流
	String realPath = sc.getRealPath("/WEB-INF/classes/霉女.jpg");
	String imgName = realPath.substring(realPath.lastIndexOf(File.separator) + 1);

	InputStream in = new FileInputStream(realPath);

	//告知客户端以下载的方式打开:Content-Disposition=attachment;filename=霉女.jpg
	//在消息头中传输中文时,需要用URL编码,浏览器会自动解析获得中文
	response.setHeader("Content-Disposition", "attachment;filename=" +
			URLEncoder.encode(imgName, "UTF-8"));
	response.setHeader("Content-Type", "application/octet-stream");

	//开始往客户端写数据
	OutputStream out = response.getOutputStream();
	int length = -1;
	byte[] b = new byte[1024];
	while((length = in.read(b)) != -1){
		out.write(b, 0, length);
	}
	out.close();
	in.close();
}

9、附加:读取资源文件的3种方式(☆)

首先要明白实际开发中资源文件所在的位置有三个地方,如下图所示的a、b、c三个属性文件:

06. Servlet5232

1)利用ServletContext读取文件a、b、c。

ServletContext中的getRealPath()方法可以读取应用中任何位置上的资源,使用限制:只能在Web应用中使用。

//String path = getServletContext().getRealPath("/c.properties");
//String path = getServletContext().getRealPath("/WEB-INF/classes/b.properties");
String path = getServletContext().getRealPath("/WEB-INF/classes/org/flyne/sources/a.properties");

InputStream in = new FileInputStream(path);
Properties props = new Properties();
props.load(in);
System.out.println(props.getProperty("who"));

2)利用ResourceBundle(资源包)读取文件a、b,不能读c,使用限制:只能读取properties的文件。

//ResourceBundle rb = ResourceBundle.getBundle("b");
ResourceBundle rb = ResourceBundle.getBundle("org.flyne.sources.a");
System.out.println(rb.getString("who"));

3)使用类加载器读取文件a、b,不能读c,使用限制:只能读取classes或者类路径中的任意资源,也不适合读取特别大的资源。

ClassLoader cl = ServletContextDemo7.class.getClassLoader();//得到类加载器
//URL url = cl.getResource("org/flyne/sources/a.properties");
URL url = cl.getResource("b.properties");
String path = url.getPath();

InputStream in = new FileInputStream(path);
Properties props = new Properties();
props.load(in);
System.out.println(props.getProperty("who"));

留下一个回复

你的email不会被公开。