Servlet介绍(一)

Servlet介绍(一)

Servlet是一个执行在webserver上的小的Java程序,它通过接收和响应webclient的请求。在tomcatserver中有已经帮我们实现好了Servlet接口的实现类:javax.servlet.GenericServlet和javax.servlet.http.HttpServlet类。

HttpServlet指能够处理HTTP请求的servlet,它在原有的Servlet接口上加入了一些与HTTP协议处理方法。同一时候覆写了service()方法,该方法体内的代码会自己主动推断用户的请求方法,如为GET请求,则调用HttpServlet的doGet()方法。如为POST请求,则调用HttpServlet的doPost()方法,因此,开发者在编写Servlet时。通常仅仅须要覆写doGet()或doPost()方法,而不须要覆写service方法。其比Servlet接口功能更为强大,因此 我们仅仅需重写其方法就可以。

关于ServletAPI的介绍例如以下:

public interface Servlet

Defines methods that all servlets must implement.

A servlet is a small Java program that runs within a Web server. Servlets receive and respond to requests from Web clients, usually across HTTP, the HyperText Transfer Protocol.

To implement this interface, you can write a generic servlet that extends javax.servlet.GenericServlet or an HTTP servlet that extends javax.servlet.http.HttpServlet.

This interface defines methods to initialize a servlet, to service requests, and to remove a servlet from the server. These are known as life-cycle methods and are called in the following sequence:

The servlet is constructed, then initialized with the init method.

Any calls from clients to the service method are handled.

The servlet is taken out of service, then destroyed with the destroy method, then garbage collected and finalized.

当中与生命周期相关的方法(life-cycle methods)指的是在其创建到销毁过程中一定会执行的方法,如init()、service()以及destroy() 方法。

例1:

我们首先创建一个FirstServlet:

package com;

import java.io.IOException;
import java.io.OutputStream; import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse; public class FirstServlet extends GenericServlet{ /**
*
*/
private static final long serialVersionUID = 1L; @Override
public void service(ServletRequest req, ServletResponse
res)throws ServletException, IOException {
OutputStream out=res.getOutputStream();
out.write("Hello Servlet!".getBytes());
}
}

然后在web.xml中配置FirstServlet的映射关系:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>com.FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<url-pattern>/FirstServlet</url-pattern>
</servlet-mapping>
</web-app>

最后把应用部署到Tomcatserver上,用浏览器訪问http://localhost:8080/TestServlet/FirstServlet 就可以显示Hello Servlet!

其调用图例如以下:

Created with Raphaël 2.1.2浏览器浏览器webserverwebserverFirstServletFirstServlet1. 连接上webserver2. 发送http请求3.解析出想訪问的主机名4.解析出想訪问的web应用5.解析想訪问的web资源6.第一次訪问时创建Servlet实例对象7.调用Servlet对象的init()方法完毕初始化8.调用Servlet对象的service()方法响应客服端请求调用service()前创建代表请求的request和代表响应的response对象9.service()方法执行,向代表client响应的response对象写入数据。

10.service()方法返回11.server从response对象中取出数据,构建一个http响应。回写给客户机。12.回写HTTP响应浏览器解析HTTP响应。提取数据显示

注:可能因为编辑器的缺陷,此时序图在浏览器下显示不完整

须要注意一点的是,仅仅有在首次訪问server才会创建目标Servlet,此后该目标Servlet一直驻留在内存中。直到关闭server。调用destroy()方法。

关于Servlet的一些细节:

同一个Servlet能够被映射带多个URL上,即多个<servlet-mapping>元素的<servlet-name>的子元素值能够是同一个Servlet的注冊名。

如在上述的web.xml文件里配置添加下面内容:

<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<url-pattern>/aa.html</url-pattern>
</servlet-mapping>

此时若訪问http://localhost:8080/TestServlet/aa.html 与上述訪问结果一样,表面上看訪问的石一个静态html网页。实际上訪问的还是动态资源。这种网页被称为“伪静态”网页。

在Servlet映射到的URL中能够使用通配符,可是仅仅能由两种固定的格式:一种是“* .扩展名”,另一种是以正斜杠(/)开头并以“/*”结尾。

Servlet是一个供其它Java程序(Servlet引擎)调用的Java类,它不能独立执行,它的执行全然由Servlet引擎来控制和调度。

在Servlet的整个生命周期内,Servlet的init方法仅仅被调用一次。

而对一个Servlet的每次訪问请求都导致Servlet引擎调用一次servlet的service方法。对于每次訪问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和HttpServletResponse响应对象,然后将这两个对象作为參数传递给它调用的Servlet的service方法。service方法再依据请求方式分别调用doXXX方法。

每一次请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和HttpServletResponse响应对象,当请求结束时,HttpServletRequest请求对象和HttpServletResponse响应对象马上被销毁(其生命周期非常短),所以,即使訪问量非常大,仅仅要不是高并发,其server压力不会太大。

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

在上述web.xml中为FirstServlet配置<load-on-startup>属性:

<load-on-startup>2</load-on-startup>

则Servlet对象随着WEBserver的启动而启动。并调用init方法完毕初始化。

假设某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。凡是在web.xml中找不到匹配的<servlet-mapping>元素的URL。它们的訪问路径都将交给缺省的Servlet处理,也就是说。缺省Servlet用于处理其它Servlet都不处理的请求訪问。在tomcat的安装文件夹\conf\web.xml(此配置文件被所以web应用共享)文件里,注冊了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置成为缺省的Servlet。当訪问tomcatserver中的某个静态HTML文件和图片时。实际上是在訪问这个缺省的Servlet。

当多个client并发訪问同一个Servlet时。webserver会为每一个client的訪问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内假设訪问了同一个资源的话,就有可能 引发线程安全问题。

与Servlet相关的对象

在server调用servlet时,会传递一些对象给servlet:

Created with Raphaël 2.1.2浏览器浏览器webserverwebserverservletservlethttp请求request、response、servletcofigservletContext、session、cookie传送上述对象给servlet

ServletConfig对象

在Servlet的配置文件里,能够使用一个或者多个<init-param>标签为servlet配置一些初始化參数。

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

其主要作用就是用来封装servlet的配置信息。

例2:

在上述的配置文件的<servlet>标签中的加上:

<init-param>
<param-name>name</param-name>
<param-value>siege</param-value>
</init-param>

改动FirstServlet 类,加上下列两行代码:

        ServletConfig config=this.getServletConfig();
System.out.println(config.getInitParameter("name"));

重新启动server,訪问该servlet,则控制台会显示siege。

应用场景:像字符编码、连接数据库的名称,密码、配置文件等不适合在程序中写死的,适合在web.xml中进行配置。

ServletContext对象

webserver在启动的时候,它会为每一个web应用程序创建一个相应的ServletContext对象,它代表当前的web应用。当server停止此应用,ServletContext对象被销毁。

ServletConfig对象中维护了ServletContext对象的引用。在编写servlet时,能够通过ServletConfig.getServletContext方法来获得ServletContext对象.

如今以面向对象的思想来思考ServletContext对象可能具有的方法,既然其代表web应用,则其应该具有与web相关的全局方法。

像getContext代表获取当前server其它的web应用对象,getAttribute代表获取web全局属性。

因为一个web应用中的全部Servlet共享同一个ServletContext对象,所以多个servlet通过ServletContext对象实现数据共享。ServletContext对象通常也被称为context域对象。

应用场景:获取web应用的初始化參数,设置web应用全局属性(如连接数据库的名称,密码),servlet的转发。

使用ServletContext对象实现Servlet的转发

在FirstServlet的doGet方法中进行Servlet的转发

this.getServletContext().setAttribute("data", "hello");
this.getServletContext().getRequestDispatcher("/dispacher.jsp").forward(req, resp);

在WebContent下建立dispacher.jsp文件:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<font color="red">
<%
String data=(String)application.getAttribute("data");
out.write(data);
%>
</font>
</body>
</html>

重新启动服务就可以看到结果。当然,此处不应把数据放在ServletContext(即application域)中。因为easy引起线程安全问题,应使用request域。

使用ServletContext对象实现资源文件的读取

读取资源文件时要写明文件的路径,当中“/”代表web应用,若文件在src文件夹下,则路径为/WEB-INF/classes/db.properties,若在WebRoot(或WebContent)下。则路径为/db.properties,配置文件db.properties例如以下:

url=jdbc:myql://localhost:3306/test
uername=root
password=root

在doGet方法里写入例如以下代码:

InputStream in=this.getServletContext().getResourceAsStream("/WEB-INF/classes/db.properties");
Properties prop=new Properties();
prop.load(in);
String url=(String) prop.get("url");
System.out.println(url);

在web应用中用FileInputStream读取文件因为路径的问题(其採用相对路径,相对于java虚拟机的启动文件夹,即tomcat的启动文件夹),变得不可行,因此最好採用ServletContext对象读取资源文件。

若非要採用FileInputStream来读取资源文件,首先须要获取文件的绝对路径,採用此方法的优点能够得到文件的名称。

因採用下面方式:

String  path=this.getServletContext().getRealPath("WEB-INF/classes/db.properties");
FileInputStream in=new FileInputStream(path);
Properties prop=new Properties();
prop.load(in);
String url=(String) prop.get("url");
System.out.println(url);

无论如何。都须要借助ServletContext对象。

可是。当Servlet调用普通类(比方DAO),此时在普通类中读取配置文件时就无法使用ServletContext对象了,当然,能够将ServletContext对象传入要调用的方法。可是。这个方案不好,原因在于web层侵入了数据訪问层。添加了耦合性,不符合软件设计思想。故不要如此设计。

因此,当读取资源文件的程序不是servlet的话。就仅仅能通过类装载器来读取了。

比如。当servlet调用UserDao中的update方法,则在此方法获取资源文件:

package dao;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties; public class UserDao { public void update(){
InputStream in=UserDao.class.getClassLoader().getResourceAsStream("db.properties");
Properties prop=new Properties();
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(prop.getProperty("password"));
}
}

通过类装载器读取文件是依照类的读取方式读取配置文件,因此配置文件不能太大。否则会导致内存溢出。当再次读取配置文件,若配置文件发生变化,因为类装载器像载入类一样仅仅载入一次配置文件,此时其仅仅在内存中读取资源文件,不会又一次读取已经改动过的文件了。若要再次读取改动后的配置文件,仅仅需通过类装载器获取到文件的路径就可以。因此下面的代码会更好一点。

package dao;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.Properties; public class UserDao { public void update(){
URL url=UserDao.class.getClassLoader().getResource("db.properties");
String path=url.getPath();
FileInputStream in = null;
try {
in = new FileInputStream(path);
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
Properties prop=new Properties();
try {
prop.load(in);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(prop.getProperty("password"));
}
}

Request对象和Response对象

webserver收到client的http请求,会针对每一次请求,分别创建一个用于代表请求的request对象、和代表响应的response对象。

request和response对象既然代表请求和响应。那么我们要获取client提交过来的数据,仅仅须要找request对象就可以了。要向客户机输出数据,仅仅须要找response对象就可以了。

HttpServletResponse对象中封装了向client发送数据、发送响应头、发送响应状态码的方法。

void addCookie(Cookie cookie)

void addHeader(java.lang.String name, java.lang.String value)

void setStatus(int sc)

PrintWriter getWriter()

ServletOutputStream getOutputStream()

Response对象的常见应用

当我们通过Response向浏览器写入中文时,在doGet()方法中:

resp.getOutputStream().write("中国".getBytes("UTF-8"));

若浏览器以别的码表打开时。则会出现乱码。因此我们能够设置响应头的Content-Type来让浏览器以什么方式打开编码表。

resp.setHeader("Content-Type", "text/html;charset=GBK");
resp.getOutputStream().write("中国".getBytes("GBK"));

这当中有一个细节,当我们将第一行中text/html;charset=GBK分号误写为逗号。其结果在IE下变成了下载文件,在chrome下正常。

或我们通过<meata>标签来模拟浏览器响应头:

    resp.getOutputStream().write("<meta http-equiv='content-type' content='text/html;charset=UTF-8'>".getBytes());
resp.getOutputStream().write("中国".getBytes("GBK"));

注:上述代码经測试在chrome中已经无效。在IE中有效。

当我们使用response的getWriter方法向response中写中文数据时,在浏览器中会出现“??”字符,其原因是response的默认查找的码表是ISO-8859,当我们向response中写入中文时,其找不到码表相应的数字,就将“?”相应的数字写给浏览器了,浏览器再将其解释为“??”,这样信息就已经丢失了。

解决方法就是设置response的码表,再控制浏览器以相应的码表打开。

resp.setCharacterEncoding("UTF-8");
resp.setHeader("Content-Type", "text/html;charset=UTF-8");
resp.getWriter().write("中国");

在response中另一个方法:setContentType。它不仅把Content-Type信息设为给定的參数,同一时候也将response的编码方式也设为该參数了,即上述代码可改写为:

resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("中国");

为什么通过response的getOutputStream方法写数据不须要设置编码方式呢,究其原因,是因为getOutputStream是以字节的方式写数据到response对象中,而getWriter是以字符的方式写数据到response对象中的。故而要设置编码表,并且字符流的本质是字节流与编码表的组合。

我们还用response对象进行下载,首先依据项目中的文件获取其绝对地址,利用FileInputStream将其封装,往response对象的OutputStream对象中写入就可以,同一时候,要注意,假设文件名称包括中文。还用对其名称进行编码。

其在doGet中的代码例如以下:

    File file=new File(this.getServletContext().getRealPath("download\\视频说明.txt"));
try {
resp.setHeader("Content-disposition","attachment;filename="+URLEncoder.encode(file.getName(),"UTF-8"));
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
InputStream in=null;
OutputStream out=null;
byte[] buf=new byte[1024]; try {
in = new FileInputStream(file);
out = resp.getOutputStream();
int len = 0;
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}

response另一个作用就是通过设置http头进行定时刷新:

resp.setHeader("refresh", "3");

其主要应用场景就是当用户注冊或者登陆成功,几秒后就进行刷新。若要指定跳转到某个页面,仅仅需在參数后面加上url就可以:

resp.setHeader("refresh", "3;url='/TestServlet/xxx.jsp'");

或者我们像之前的那样利用<meta>标签头来模拟请求头:

resp.getOutputStream().write("<meta http-equiv='refresh'  content='3;url=/TestServlet/dispacher.jsp'>".getBytes());

注:经測试,此方法在IE11下没有问题,可是在chrome下不可行,,原因是chrome在meta标签外加入了pre标签。

应慎用 。

response还能够用来操作浏览器缓存:

resp.setDateHeader("expires", System.currentTimeMillis()+1000*3600);
try {
resp.getOutputStream().write("hello".getBytes());
} catch (IOException e) {
e.printStackTrace();
}

其设置缓存1个小时后过期。当然,当用户刷新浏览器此时缓存不起作用。若要禁止缓存,仅仅需设置头:

resp.setHeader("cache-control", "no-cache");
resp.setDateHeader("expires", 0);
resp.setHeader("pragma", "no-cache");

上述方法仅仅需使用一个就可以。

response能够实现请求重定向,所谓请求重定向指的是一个web资源收到client请求后,通知client去訪问另一个web资源,其应用场景就是用户登陆。

response实现重定向的本质向client发送一个状态码SC_MOVED_TEMPORARILY(302)。然后设置location头:

    resp.setStatus(302);
resp.setHeader("location", "/TestServlet/dispacher.jsp");

为了使编写更简洁,Servlet帮我们将上述代码封装成sendRedirect方法。

try {
resp.sendRedirect("/TestServlet/dispacher.jsp");
} catch (IOException e) {
e.printStackTrace();
}

在此过程中,client向server发送了两次请求,第一次请求Servlet,servlet‘告诉’客户机去找dispacher.jsp,然后客户机就去找dispacher.jsp了,同一时候浏览器的地址栏会发生变化。因为client向server发生了两次请求。产生了两对response和request对象,这样会加重server的负担,因此要慎用,可是像某些场景,比方用户登陆,显示购物车。则必须要使用重定向,这样告诉用户自己的位置。

response对象不能同一时候调用getOutputStream和getWriter方法,若必须要同一时候调用,採用重定向就可以。

Servlet程序向ServletOutputStream或PrintWriter对象中写入的数据将被Servlet引擎从response里面获取。Servlet引擎将这些数据当作响应消息的正文,然后再与响应状态行和个响应头组合后输出到client。

Servlet方法的service方法结束后。Servlet引擎将检查getWriter或getOutputStream方法返回的输出流对象是否已经调用close方法。假设没有。Servlet引擎将调用close方法关闭输出流对象。也就是说。我们通过response对象获取的流对象不须要我们自己手动关闭,server会替我们关闭的,可是我们自己创建的流对象还是要自己关闭的。

Request对象的常见应用

HttpServletRequest对象代表client的请求,当client通过HTTP协议訪问server时,HTTP请求头中的全部信息都封装在这个对象中,仅仅需通过调用这个对象的方法,就能够获得client的信息。

比方获取请求头(getHeader、getHeaders),获取请求參数(getParameter、getParameterMap、getParameterNames、getParameterValues),文件上传(getInputStream),获取客户机信息(getRequestURI(可用作权限拦截、统计页面訪问次数)、getRequestURL)。

一般来说,有两种方式带数据给server,第一种是通过超链接后附上数据,另一种就是通过表单提交。

上一篇:【转】AD常用端口


下一篇:Configure the max limit for concurrent TCP connections(转)