原文源自《Servlet与JSP核心编程(第2卷 第2版)》第1章,转载自点击打开链接
1.7 在不同的Web应用之间共享数据(1)
Web应用程序的一个主要功能就是保持数据和功能的独立。每一个Web应用程序都维护着它自己的session表和它自己的servlet上下文。每一个Web应用程序也使用它自己的类加载器;这种做法排除了名称冲突的问题,但同时也意味着静态方法和字段不能够在多个应用程序之间共享。然而,通过使用cookie或者使用特殊URL的ServletContext对象同样能共享数据。这些方法能够满足Web应用程序之间少量信息的共享,但如果打算进行大量数据的共享,则应该考虑让这些应用程序作为一个Web应用程序来运行。下面简要介绍两种数据共享。
cookie cookie是由浏览器而不是由服务器来维护的(cookie实际上是客户端计算机上的一些文本文件,只能保存少量的信息)。因此,只要被设置成可以应用于任何服务器地址,cookie就可以在多个Web应用程序之间共享。默认情况下,浏览器仅仅把cookie发送某些特定的网页,这些网页的URL前缀和创建这个cookie的网页URL前缀相同。例如,如果服务器的http://host/path1/SomeFile.jsp页面在本地计算机上创建了一个cookie,那么下次请求http://host/path1/SomeOtherFile.jsp和http://host/path1/path2/Anything这两个URL的时候,浏览器会自动把cookie发送给这些网页;而不会把cookie发送给http://host/path3/Anything 。因为每个Web应用程序都有其特定的URL前缀,所以默认建立的cookie是不能在不同Web应用程序之间共享的。
然而,正如我们在第1卷的第8章中讲述的那样,可以使用cookie 类的 setPath 方法来改变这个默认行为。传递一个"/"给这个方法,浏览器便会把这个cookie发送给创建此cookie的URL所在服务器的所有URL地址。
Cookie c = new Cookie("name", "value");
c.setMaxAge(...);
c.setPath("/");
response.addCookie(c);
特定URL关联的ServletContext对象 在一个servlet内部,可以通过调用servlet的getServletContext方法(继承自GenericServlet)来获得Web应用程序的servlet上下文。在JSP页面中,使用预定义的application变量。使用任何一种方法,都可以获得servlet所匹配的上下文环境或者正在处理请求的JSP页面。然而,也可以调用ServletContext的getContext方法来获得一个servlet上下文环境,这个上下文环境不一定是你的,而是一个指定的URL的。这里也用黑体字列举此方法。
ServletContext myContext = getServletContext();
String url = "/someWebAppPrefix";
ServletContext otherContext = myContext.getContext(url);
Object someData = otherContext.getAttribute("someKey");
其实,这两种数据共享的方法都不是很完美。
cookie的缺点是只能存储少量有限的数据。每一个cookie的值都是一个字符串,而且每个字符串的长度限制在4 KB以内。所以,更多的数据共享就需要数据库的支持:可以使用cookie的值作为主键值,然后把实际的值存储在数据库中。
通过servlet上下文来共享数据的首要缺点是,我们必须知道其他Web应用程序正在使用的URL前缀。作为开发人员,肯定希望在改变Web应用程序前缀的时候不需要修改任何与之相关的代码。getContext方法的使用限制了处理的灵活性。第二个缺点就是安全。服务器上的某些Web应用程序可能阻止了对ServletContext的访问。在这种情况下,调用getContext将返回null值。例如,在一些Tomcat版本中,上下文共享默认是启用的,而在其他一些版本中,却需要明确的手动启用。比如,Tomcat 5.5.7中,可以添加crossContext="true"作为tomcat_dir/conf/context.xml中Context元素的一部分,从而把启用上下文共享作为所有部署的Web应用程序的默认设置。如果忽略crossContext属性,将导致Tomcat使用其默认设置,这会阻止在Web应用程序之间共享ServletContext。
清单1.5和清单1.6的SetSharedInfo和ShowSharedInfo这两个servlet展示了这两种数据共享方法。这些servlet 被映射为清单1.7所示的部署描述文件中的URL地址。SetSharedInfo servlet在session对象和servlet上下文中创建了一些自定义的项。同时,它也设置了两个Cookie:(一个拥有默认路径,默认路径说明cookie只应该发送到与原请求的URL前缀相同的那些URL地址。另一个cookie的路径被设置为"/",说明cookie会被应用于远程主机的所有URL。最后,SetSharedInfo Servlet把客户端重定向到ShowSharedInfo servlet,这个servlet将显示所有session的属性名称、所有当前servlet上下文的属性、所有以/shareTest1为前缀的URL的servlet 上下文属性以及所有的cookie。
清单1.5 SetSharedInfo.java
package coreservlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class SetSharedInfo extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession(true);
session.setAttribute("sessionTest", "Session Entry One");
ServletContext context = getServletContext();
context.setAttribute("servletContextTest",
"Servlet Context Entry One");
Cookie c1 = new Cookie("cookieTest1", "Cookie One");
c1.setMaxAge(3600); // One hour
response.addCookie(c1); // Default path
Cookie c2 = new Cookie("cookieTest2", "Cookie Two");
c2.setMaxAge(3600); // One hour
c2.setPath("/"); // Explicit path: all URLs
response.addCookie(c2);
String url = request.getContextPath() +
"/servlet/coreservlets.ShowSharedInfo";
// In case session tracking is based on URL rewriting.
url = response.encodeRedirectURL(url);
response.sendRedirect(url);
}
}
清单1.6 ShowSharedInfo.java
package coreservlets;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
public class ShowSharedInfo extends HttpServlet {
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
String title = "Shared Info";
out.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " +
"Transitional//EN\">" +
"<HTML>\n" +
"<HEAD><TITLE>" + title + "</TITLE></HEAD>\n" +
"<BODY BGCOLOR=\"#FDF5E6\">\n" +
"<H1 ALIGN=\"CENTER\">" + title + "</H1>\n" +
"<UL>\n" +
" <LI>Session:");
HttpSession session = request.getSession(true);
Enumeration attributes = session.getAttributeNames();
out.println(getAttributeList(attributes));
out.println(" <LI>Current Servlet Context:");
ServletContext application = getServletContext();
attributes = application.getAttributeNames();
out.println(getAttributeList(attributes));
out.println(" <LI>Servlet Context of /shareTest1:");
application = application.getContext("/shareTest1");
if (application == null) {
out.println("Context sharing disabled");
} else {
attributes = application.getAttributeNames();
out.println(getAttributeList(attributes));
}
out.println(" <LI>Cookies:<UL>");
Cookie[] cookies = request.getCookies();
if ((cookies == null) || (cookies.length == 0)) {
out.println(" <LI>No cookies found.");
} else {
Cookie cookie;
for(int i=0; i<cookies.length; i++) {
cookie = cookies[i];
out.println(" <LI>" + cookie.getName());
}
}
out.println(" </UL>\n" +
"</UL>\n" +
"</BODY></HTML>");
}
private String getAttributeList(Enumeration attributes) {
StringBuffer list = new StringBuffer(" <UL>\n");
if (!attributes.hasMoreElements()) {
list.append(" <LI>No attributes found.");
} else {
while(attributes.hasMoreElements()) {
list.append(" <LI>");
list.append(attributes.nextElement());
list.append("\n");
}
}
list.append(" </UL>");
return(list.toString());
}
}
清单1.7 web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<!-- web.xml from the app-blank template Web app
from http://courses.coreservlets.com/Course-Materials/.
Includes two standard elements: welcome-file list
and a servlet-mapping to disable the invoker servlet.-->
<Web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/Web-app_2_4.xsd"
version="2.4">
<!-- Your entries go here. -->
<servlet>
<servlet-name>setSharedInfoServlet</servlet-name>
<servlet-class>coreservlets.SetSharedInfo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>setSharedInfoServlet</servlet-name>
<url-pattern>/setSharedInfo</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>showSharedInfoServlet</servlet-name>
<servlet-class>coreservlets.ShowSharedInfo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>showSharedInfoServlet</servlet-name>
<url-pattern>/showSharedInfo</url-pattern>
</servlet-mapping>
<!-- Disable the invoker servlet. -->
<servlet>
<servlet-name>NoInvoker</servlet-name>
<servlet-class>coreservlets.NoInvokerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>NoInvoker</servlet-name>
<url-pattern>/servlet/*</url-pattern>
</servlet-mapping>
<!-- If URL gives a directory but no file name, try index.jsp
first and index.html second. If neither is found,
the result is server-specific (e.g., a directory
listing).
-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</Web-app>
运行这个示例的时候,确保只调用了shareTest1应用程序的SetSharedInfo servlet。在此之后,调用shareTest1和shareTest2的ShowSharedInfo servlet。不要调用shareTest2的ShowSharedInfo servlet,因为它不能表现两个应用程序之间的数据共享。
图1.9显示了用户从前缀为/shareTest1的Web应用程序内部访问SetSharedInfo和SharedInfo所得的结果。ShowSharedInfo显示了以下内容。
自定义的会话属性。
默认servlet上下文的自定义(由SetSharedInfo这个servlet显式创建)和标准属性(由服务器自动创建)。
getContext("/shareTest1")返回的servlet上下文的自定义属性和标准属性。
两个显式创建的cookie和系统创建的用于后台session跟踪的cookie。
![]() |
图1.9 从一个Web应用访问两个servlet所得到的结果 |
图1.10显示了随后用户访问安装于前缀为shareTest2的Web应用程序中的ShowSharedInfo副本的结果。这个servlet显示了以下内容。
默认servlet上下文中包含的标准属性。
getContext("/shareTest1")返回的servlet上下文的自定义和标准属性(本例中不同于默认servlet上下文)
两个cookie:一个是显式创建的,以"/" 作为路径,一个是系统创建的,用于后台session跟踪cookie(也是以"/" 作为其路径)。
![]() |
图1.10 在一个Web应用程序中访问SetSharedInfo servlet 和在另一个不同的Web应用程序中访问ShowSharedInfo servlet的结果 |
这个servlet没有显示以下内容。
它的session对象中的任何属性。
任何默认servlet上下文中的自定义属性。
显式创建的使用默认路径的cookie。