SCWCD之路——Servlet技术 博客分类: SCWCD Certification ServletWeb多线程应用服务器网络应用
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程序的时候需要特意注意。