`
superzhouych
  • 浏览: 21807 次
  • 性别: Icon_minigender_1
  • 来自: 广州
最近访客 更多访客>>
社区版块
存档分类
最新评论

SCWCD之路——Servlet技术

阅读更多

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

1
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics