servlet
Servlet和JSP的一个重大的区别即是Servlet可以通过web.xml文件的配置让Servlet在Web容器启动时就自动启动Servlet。可以利用Servlet的这个特性不变化的数据事先加载到Web应用服务器中以便缓存使用。
假设,我们系统的用户在系统部署前就已经创建好,以后不常发生变化,那么我们可以在Web应用程序启动时就将其下载缓存到Web应用服务器内存中,如果用户发生变化可以手工调用这个Servlet进行刷新。下面我们就通过Servlet向导创建这个UserCacheServlet,它在Web容器启动时自动下载并缓存系统所有用户Id和用户名:
1.启动创建Servlet向导,填写Servlet名字
通过File->New...->Web->双击Standard Servlet图标启动创建Servlet向导的第一步,如下图所示:
图 2填写Servlet名字 |
在Class name中填入Servlet的名字:UserCacheServlet,在Package中填入bookstore.servlet作为包名。按Next到下一步。
2.选择Servlet所要实现的方法。
我们在前面已经介绍了Servlet通过不同的doXxx()方法的响应HTTP请求方式,你可以在向导的第2步选择需要定义哪些doXxx()方法。默认情况下doGet()方法被勾选,即通过HTTP GET请求方式访问Servlet。通过带参的URL访问Servlet时,Servlet就用doGet()方法响应这个请求。由于我们只是假设用户数据不常变动,并不是说永远不变动,所以我们在Web容器初始化时,希望通过UserCacheServlet自动加载用户数据到缓存中,当数据库表T_USER的用户数据发生变动时,我们可以手工调用UserCacheServlet,让其刷新缓存中的用户数据。
Web容器启动时自动初始化UserCacheServlet,此时init()方法被调用,我们可以通过init()方法加载用户数据,当用户通过URL请求刷新用户数据时,UserCacheServlet通过doGet()方法响应这个HTTP GET请求。也就是说,我们需要实现doGet()方法,所以我们接受向导的默认设置,如下图所示:
图 3 选择需要覆盖的Servlet响应方法 |
按Next跳过第3步到向导的第4步。
3.指定访问Servlet的路径
图 4 指定Servlet访问路径 |
·Name:usercacheservlet,Servlet在web.xml配置文件中所取的名字
·URL pattern:/usercacheservlet,访问这个Servlet的匹配路径。指定这个访问路径后,假设Web应用程序部署在http://localhost:8080/webModule下,则通过http://localhost:8080/webModule/usercacheservlet访问servlet。
直接按Finish创建Servlet。
打开web.xml文件,你可以找到关于UserCacheServlet声明和访问的部署描述信息:
·<servlet>节点:描述servlet的名字及类名。
·<servlet-mapping>节点:描述servlet访问匹配路径。
双击工程窗格资源树的webModule节点,JBuider在内容窗格中打开用于编辑web.xml文件的Web模块DD编辑器(Web Module DD editor),此时结构窗格显示出web.xml文件的结构,如下图所示:
图 5 结构窗格的web.xml文件结构树 |
带 图标的节点表示已经有配置内容,而未带 图标的节点表示暂时还没有对应的配置内容。我们展开Servlets节点,定位到usercacheservlet并双击这个节点,DD编辑器调整界面对usercacheservlet这个Servlet进行配置,如下图所示:
图 6 DD编辑器 |
Servlet可以在Web容器启动时,自动初始化。假设有多个Servlet都需要自动初始化,则可以通过web.xml的<load-on-startup>设置启动的次序。我们在DD编辑器将Load on startup值设为2, 这样UserCacheServlet将在Web容器启动后,排在顺序2初始化。
一些系统所用的Servlet,由于是Web开始服务的基础必须在顺序1初化,所以我们开发的Servlet最好在顺序2或顺序3初始化。Servlet初始化时,init()方法被调用。在进行这样的设置后web.xml将包含以下粗体所示的配置信息。
代码清单 1 web.xml有关UserCacheServlet的描述信息
1. <web-app> 2. … 3. <servlet> 4. <servlet-name>usercacheservlet</servlet-name> 5. <servlet-class>bookstore.servlet.UserCacheServlet</servlet-class> 6. <load-on-startup>2</load-on-startup> 7. </servlet> 8. <servlet-mapping> 9. <servlet-name>usercacheservlet</servlet-name> 10. <url-pattern>/usercacheservlet</url-pattern> 11. </servlet-mapping> 12. … 13. </web-app> |
注意:
当删除UserCacheServlet后,Servlet在web.xml所对应的部署描述信息并不会一起删除,你必须手工删除。
在init()初始化方法中利用UserList.fillUser()方法从数据库中下载并缓存用户记录信息,在doGet()方法中也引用了UserList.fillUser(),用户通过URL访问UserCacheServlet时,doGet()方法被调用,刷新缓存用户数据,并显示"刷新成功"提示,其代码如下所示:
代码清单 2 UserCacheServlet.java
1. package bookstore.servlet; 2. 3. import javax.servlet.*; 4. import javax.servlet.http.*; 5. import java.io.*; 6. import bookstore.UserList; 7. 8. public class UserCacheServlet 9. extends HttpServlet 10. { 11. private static final String CONTENT_TYPE = "text/html; charset=GBK"; 12. 13. //Initialize global variables 14. public void init() 15. throws ServletException 16. { 17. UserList.fillUser();//Web容器启动后调用 18. } 19. 20. //Process the HTTP Get request 21. public void doGet(HttpServletRequest request, HttpServletResponse response) 22. throws ServletException, IOException 23. { 24. UserList.fillUser();//刷新用户数据 25. response.setContentType(CONTENT_TYPE); 26. PrintWriter out = response.getWriter(); 27. out.println("<html>"); 28. out.println("<head><title>UserCacheServlet</title></head>"); 29. out.println("<body bgcolor=\"#ffffff\">"); 30. out.println("刷新成功!"); 31. out.println("</body>"); 32. out.println("</html>"); 33. out.close(); 34. } |
当然,我们要对《》专题中创建的UserList.java代码进行更改,定义fillUser()方法以供UserCacheServlet.java调用,此外,还需要调整原getUserListHTML()方法,如下所示:
代码清单 3 调整后的UserList.java代码
2. package bookstore; 3. 4. import java.sql.*; 5. import java.util.*; 6. 7. public class UserList 8. { 9. private static Map userMap;//用户ID和用户名的Map 10. //将用户数据缓存到Map中 11. public static void fillUser() 12. { 13. if (userMap == null) 14. { 15. userMap = new HashMap(); 16. } else 17. { 18. userMap.clear(); 19. } 20. Connection conn = null; 21. StringBuffer sBuf = new StringBuffer(); 22. try 23. { 24. conn = DBConnection.getConnection(); 25. PreparedStatement pStat = conn.prepareStatement( 26. "select USER_ID,USER_NAME from T_USER"); 27. ResultSet rs = pStat.executeQuery(); 28. while (rs.next()) 29. { 30. userMap.put(rs.getString(1), rs.getString(2)); 31. } 32. } catch (SQLException ex) 33. { 34. ex.printStackTrace(); 35. } finally 36. { 37. try 38. { 39. if (conn != null) 40. { 41. conn.close(); 42. conn = null; 43. } 44. } catch (SQLException ex1) 45. { 46. ex1.printStackTrace(); 47. } 48. } 49. } 50. 51. //获取HTML下拉框的用户列表代码 52. public static String getUserListHTML() 53. { 54. StringBuffer sBuf = new StringBuffer(); 55. Set set = userMap.keySet(); 56. Iterator iter = set.iterator(); 57. while (iter.hasNext()) 58. { 59. Object item = (Object) iter.next(); 60. sBuf.append("<option value=’" + item + "’>" + 61. userMap.get(item) + "</option>\n"); 62. } 63. return sBuf.toString(); 64. } 65. } |
首先,我们在第9行定义了一个静态的userMap对象用以缓存用户信息,这个Map以userId为键保存UserName的值。第11~49行的静态fillUser()方法从数据库中获取用户的信息填充到userMap中。当用户访问login.jsp生成用户下拉框时,用户数据直接从userMap缓存中读取,而不再从数据库中读取,你可以从第54~56行的代码中看到这种获取用户数据方式的改变。当添加或删除用户时,可以通过http://localhost:8080/webModule/usercacheservlet刷新缓存数据。
实战经验:
使用缓存保存不常变动而频繁访问的数据是提高系统性能一个很重要的途径。如果你的Web服务器没有实现集群,刷新缓存数据非常简单,只需通过一个Servlet在发生数据变动时刷新就可以了。如果Web服务器使用了集群,问题就变得复杂了,因为每一台Web服务器的JVM是独立的,所以每台Web服务器都有一份独立的缓存数据,当数据发生变动时,必须对每台Web服务器中对缓存进行刷新。这里有两种解决方案,其一是使用独立于Web服务器的专用共享资源机器缓存数据,集群中所有的Web服务器通过JNDI等方式访问缓存数据。另外的一种方法则是通过同步机制在任意一台服务器缓存数据发生变动时同步到集群中的其他机器上。Apache的Turbine项目的JCS子项目专门为解决这一企业级的问题而开发,你可以通过http://jakarta.apache.org/jcs/index.html了解JCS的更多内容。 |