欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

SCWCD之路——Servlet技术 博客分类: SCWCD Certification ServletWeb多线程应用服务器网络应用 

程序员文章站 2024-02-27 21:37:39
...

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程序的时候需要特意注意。