Servlet技术介绍
Servlet又被称为Java服务器小程序,它是用Java编写的服务器端程序,是由服务器端调用和执行的、按照Servlet自身的规范编写的Java类。有的时候可以把Servlet看作是用Java编写的CGI,但是它们的实际功能比CGI要强得多。
下面是一个简单的Servlet程序
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 一个简单的 Servlet 程序
* @author zhouych
*/
public class HelloWorldServlet extends HttpServlet {
protected void processRequest(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=gb2312");
PrintWriter out = response.getWriter();
try {
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet HelloWorldServlet</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet HelloWorldServlet at " + request.getContextPath () + "</h1>");
out.println("</body>");
out.println("</html>");
} finally {
out.close();
}
}
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Short description";
}
}
编写完Servlet程序后,还需要去配置web.xml属性文件,如下(节选)
<web-app> <servlet> <servlet-name>HelloWorldServlet</servlet-name> <servlet-class>HelloWorldServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/HelloWorldServlet</url-pattern> </servlet-mapping> </web-app>
Servlet的生命周期
Servlet部署在容器中(如Tomcat中),它的生命周期由容器负责管理,具体可以分成5个部分:
1)装载Servlet类。
2)创建一个Servlet的实例(Servlet实际就是一个Java类,在使用前需要被创建其类对象)。
3)调用Servlet的init()方法(Servlet容器会对在web.xml文件中写的初始化参数进行初始化)。
4)调用Servlet的service()方法对收到的请求进行服务响应(service()方法由容器自动调用,程序员不应该去修改该方法,而只需要 修改诸如doGet()和doPost()等方法让service()根据请求自动调用相应方法即可)。
5)调用Servlet的destory()方法来销毁该实例。
Servlet中常用的类及接口
考试中对Servlet的内容比较多,Servlet中常用的类及接也比较多,这里不多介绍,有兴趣的可以查阅其他资料。
Servlet中的监听程序
我们可以创建一些特殊的Servlet类,这些类可以用来监听Servlet的上下文信息、Servlet中HTTP的会话信息和Servlet的请求信息,通过这些监听程序,可以在后台自动执行一些程序。常考的监听类型有:
1)Servlet上下文监听:在Web应用中可以部署一些监听程序,这些程序能够监听ServletContext的信息,比如ServletContext的查和删除,ServletContext属性的增加、删除和修改等。下面给出一个简单的例子程序
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
/**
* 一个用来监听 ServletContext 及其属性的程序
* @author zhouych
*/
public class MyServletContextListener implements ServletContextListener,
ServletContextAttributeListener {
private ServletContext context = null;
@Override
public void contextInitialized(ServletContextEvent sce) {
this.context = sce.getServletContext();
print("ServletContext被创建");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
print("ServletContext被销毁");
this.context = null;
}
@Override
public void attributeAdded(ServletContextAttributeEvent scab) {
print("增加了一个属性: attributeAdded(" + scab.getName() + ", " + scab.getValue() + ")");
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scab) {
print("删除了一个属性: attributeRemoved(" + scab.getName() + ", " + scab.getValue() + ")");
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scab) {
print("修改了一个属性: attributeReplaced(" + scab.getName() + ", " + scab.getValue() + ")");
}
private void print(String message) {
PrintWriter pw = null;
try {
pw = new PrintWriter(new FileOutputStream("E:/log.txt", true));
pw.println(new Date().toString() + "::Form ContextListener; " + message);
pw.close();
} catch (Exception e) {
pw.close();
e.printStackTrace();
}
}
}
之后,需要在web.xml属性文件中添加内容如下(节选)
<web-app>
<listener>
<listener-class>listener.MyServletContextListener</listener-class>
</listener>
<web-app>
可以在JSP页面中添加如下代码来测试
<%
out.println("add attribute");
getServletContext().setAttribute("user", "zhouych");
out.println("replace attribute");
getServletContext().setAttribute("user", "cheng");
out.println("remove attribute");
getServletContext().removeAttribute("user");
%>
2) Servlet会话监听:在Web应用中还可以监听HTTP会话活动情况、HTTP会话属性的设置情况和HTTP会话的active、passivate情况等。可以通过HttpSessionListener接口监听HTTP会话的创建、销毁的信息;通过HttpSessionBindingListener接口监听HTTP会话中对象的绑定信息;通过HttpSessionAttributeListener接口监听HTTP会话中属性的设置请求。下面给出一个简单的例子
import java.util.Hashtable;
import java.util.Iterator;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 一个用来监听 HTTP 会话的程序
* @author zhouych
*/
public class MySessionListener implements HttpSessionListener {
// 保存所有的登录信息
static Hashtable table = new Hashtable();
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSession session = se.getSession();
table.put(session.getId(), session);
System.out.println("创建session: " + session.getId());
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
System.out.println("销毁session: " + session.getId());
table.remove(session.getId());
}
static public Iterator getSet() {
return table.values().iterator();
}
static public HttpSession getSession(String id) {
return (HttpSession) table.get(id);
}
}
考试的时候经常会考查HttpSessionAttributeListener和HttpSessionBindingListener这两个接口的区别。这两个接口虽然都能用来监听会话过程属性的改变,但是两者又有不同。HttpSessionAttributeListener需要在部署描述符中定义,由Servlet容器创建一个实现类的实例,一般用来跟踪应用程序上所有的会话状态;而HttpSessionBindingListener则不需要在部署描述符中定义,仅当对象被添加进会话或从会话中删除时,Servlet容器会调用实现此监听接口的对象上的方法,一般用来处理一定类型对象被添加进会话或从会话中被删除的状况。
3)Servlet的请求监听:在Web应用中可以监听客户端的请求,比如可以从请求中获取客户端的地址并进行处理,一般使用ServletRequestListener接口和ServletRequestAttributeListener接口来处理,下面给出一个例子
import java.io.FileOutputStream;
import java.io.PrintWriter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestAttributeEvent;
import javax.servlet.ServletRequestAttributeListener;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
/**
* 一个用来建监听客户端的请求的程序
* @author zhouych
*/
public class MyRequestListener implements ServletRequestListener,
ServletRequestAttributeListener{
@Override
public void requestDestroyed(ServletRequestEvent sre) {
print("销毁request");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
print("创建request");
ServletRequest req = sre.getServletRequest();
if (req.getRemoteAddr().startsWith("127")) {
req.setAttribute("isLogin", new Boolean(true));
} else {
req.setAttribute("isLogin", new Boolean(false));
}
}
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
print("attributeAdded(" + srae.getName() + ", " + srae.getValue() + ")");
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
print("attributeRemoved(" + srae.getName() + ", " + srae.getValue() + ")");
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
print("attributeReplaced(" + srae.getName() + ", " + srae.getValue() + ")");
}
private void print(String message) {
PrintWriter pw = null;
try {
pw = new PrintWriter(new FileOutputStream("E:/log.txt", true));
pw.println(message);
pw.close();
} catch (Exception e) {
pw.close();
e.printStackTrace();
}
}
}
Servlet中的过滤程序
在Web应用中过滤器能截取从客户端进来的请求,并做出处理的答复。在这里,过滤器可以验证客户是否来自可信的网络,可以对客户提交的数据进行重新编码、可以从系统里获得配置的信息、可以过滤掉某些词汇、可以记录系统日志、可以验证客户端的浏览器是否支持当前应用等等。程序员可以为一个Web应用部署多个过滤器,这些过滤器组成一个过滤链,每个过滤器只负责一个任务。下面给出一个例子
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 一个对用户进行访问认证的过滤器程序,如果认证通过,那么允许
* 访问指定的资源,否则把请求转向其他地方(如转向认证页面)
* @author zhouych
*/
public class SignonFilter implements Filter {
public static final String LOGIN_PAGE = "login.jsp";
protected FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest filterReq = (HttpServletRequest) request;
HttpServletResponse filterRes = (HttpServletResponse) response;
HttpSession session = filterReq.getSession();
String isLogin = "";
try {
isLogin = (String) session.getAttribute("isLogin");
if (isLogin.equals("true")) {
System.out.println("验证通过");
chain.doFilter(request, response);
} else {
filterRes.sendRedirect(LOGIN_PAGE);
System.out.println("拦截了一个未认证的请求");
}
} catch (Exception e) {
}
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
然后在web.xml中配置过滤器,如下(节选)
<web-app> <filter> <filter-name>auth</filter-name> <filter-class>filter.SignonFilter</filter-class> </filter> <filter-mapping> <filter-name>auth</filter-name> <url-pattern>/admin/*</url-pattern> </filter-mapping> </web-app>
Servlet中的线程安全问题
Servlet体系结构建立在Java多线程机制上,它的生命周期由Servlet容器负责。当客户端第一次请求某一个Servlet时,Servlet容器会根据部署描述符文件来实例化这个Servlet类。当有新的客户端再次请求该Servlet时,Servlet容器默认不会再实例化该Servlet类,默认以多线程模式来运行该实例。
这个时候问题来了,当有多个线程同时访问一个Servlet时,可能会发生多个线程同时访问同一资源的情况,这个时候就会发生一系列的安全性问题。
解决的方法也比较简单,主要有以下几种:
1)让Servlet类实现SingleThreadModel接口,这个接口会告诉容器如何处理对同一个Servlet的调用。一旦Servlet继承了该接口,则其里面的service()方法将不会有两个线程同时执行,保证了线程安全。
2)对共享数据进行同步化处理,比如可以使用synchronized关键字来同步共享数据。
3)在JSP页面中使用page指令的isThreadSafe来将该页面声明为线程安全。
4)避免使用实例变量。
需要提醒的是如果一个Servlet实现了SingleThreadModel接口,那么Servlet容器会为每个新的请求创建一个单独的Servlet实例,这将会引起系统的大量开销。同样如果使用同步代码,会导致同一时刻只能有一个线程在执行,而其他用户则被迫处于阻塞状态,同时为了保证主存内容和线程的工作内存中的数据一致,需要频繁地使用缓存,这同样会大大地影响系统的性能。所以最好的方法是避免使用实例变量,如果实在无法避免使用,那么应该使用同步代码来保护要使用的实例变量,但是注意一定要尽量使同步代码最小化。
Servlet的线程安全问题只有在大量的并发访问的时候才会显现出来,很难被发现,所以在编写Servlet程序的时候需要特意注意。