技术开发 频道

类路径冲突的鉴别

  【IT168 技术文章】

        引言

  开放源代码软件主要优势之一就是它能让您“即装即用(out of the box)”,这在以前需要靠您自个花上数周甚至数月的时间。Apache 协会在这一点上成就显著,在 Apache Jakarta 程序中的大多数开放源代码程序都成为行业内事实上的标准。

  事实上,由于这些程序如此成功并被一致受到好评,IBM 开始在 WebSphere 应用服务器(WebSphere Application Server )内引入它们。例如,众所周知 WebSphere 应用服务器 V5.0(WebSphere Application Server V5.0)和 WebSphere 应用服务器 V5.1(WebSphere Application Server V5.1)的管理控制台都是使用 Apache Struts 构建的。同样地,WebSphere Application Server 在它的 Web 容器内使用的是 Jasper JSP 编译程序。然而,在 WebSphere Application Server V5.0 和 5.1 中使用的还不仅仅是这些 Apache 开放源代码程序。这样,在 Web 或 EJB™程序 所含的 JAR 文件不同版本之间的类路径冲突常常引起细微又难于调试的错误。以下将向您描述在团队中如何开展检测工作以及帮助解决通用工具开发过程中的这些错误。

  对类路径问题的诊断

  最近,开发者团队开始将 WebSphere Application Server V3.5 中的 Web 应用程序迁移至 WebSphere Application Server V5.1。该应用程序利用了 Apache Jakarta ORO 2.0.7 框架中的正规表达式类。这些类的使用范围之一就是在连接到 CICS 以进行实际身份验证之前确认用户名的正确格式。

  当团队完成迁移工作时,他们发现在 WebSphere Application Server V5.1 下,有效的用户名会被 ORO PatternMatcher 拒绝。奇怪的是同样的代码能在版本 3.5 上运行,也能在 WebSphere Studio Application Developer Version 5.1 JVM 下用“main”方法进行单元测试时运行。失败的原因和 servlet 容器有关。

  冥思苦想之后,该团队开始怀疑 servlet 容器是不是在自身的类路径里包含了其它版本的 ORO 类。我们最初决定使用 JAR 工具列出 JAR 的各种运行时间的目录并查找出现问题的类。然而,WebSphere Application Server V5.1 使用的几种 JAR 在运行时通过不同的目录分散开来。这样一来就太困难了,团队于是考虑在运行时是否存在一种方式可以及时对 JAR 文件所包含的出现问题的类进行查询。

  当情况出现时,java.security 包中的 ProtectionDomain 和 CodeSource 类可以指出类的源位置。基本的语法是: 

1 Class.forName(className).getProtectionDomain().getCodeSource()
2

  我们很快地添加一些日志代码将 CodeSource 转储为

1 org.apache.oro.text.regex.Perl5Compiler

  其结果是 C:/Program Files/IBM/WebSphere Studio/Application Developer/v5.1.1/runtimes/base_v51/lib/jython.jar ,而不是我们期望的 WEB-INF/lib/jakarta-oro-2.0.7.jar 。显然, jython.jar 里的 ORO 类并没有按照 2.0.7 中的方式运作。

  解决类路径问题的常规工具

  在讨论 WebSphere Application Server 中类路径冲突的发现问题上,我们需要一种简单的且无干扰的方式,可以用来查询 WebSphere 运行时特殊的类正从哪个 JAR 文件里装载。这样,当我们觉察到类路径冲突时,只需简单查询并确定从 JAR 文件装载的类是否和我们料想到的 JAR 文件相同。具有该功能的 servlet 如下所示:

  1 package com.ibm.servlet;
  2 import java.io.IOException;
  3 import java.io.PrintWriter;
  4 import java.security.CodeSource;
  5 import java.security.ProtectionDomain;
  6 import javax.servlet.ServletException;
  7 import javax.servlet.http.HttpServlet;
  8 import javax.servlet.http.HttpServletRequest;
  9 import javax.servlet.http.HttpServletResponse;
10 /**
11 * This servlet will attempt to load a user-specified class and, if successful,
12 * query the classes' CodeSource for the location of the class.
13 *
14 * @author <a href="mailto:kendrick@us.ibm.com">Shannon Kendrick</a>
15 * @version $Id$
16 */
17 public class ClassFinderServlet extends HttpServlet {
18     //~ Static fields/initializers ---------------------------------------------
19     private static final String CLASS_NAME = "className";
20     //~ Methods ----------------------------------------------------------------
21     /**
22      * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
23      *         javax.servlet.http.HttpServletResponse)
24      */
25     protected void doGet(
26         HttpServletRequest request,
27         HttpServletResponse response)
28         throws ServletException, IOException {
29         processRequest(request, response);
30     }
31     /**
32      * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
33      *         javax.servlet.http.HttpServletResponse)
34      */
35     protected void doPost(
36         HttpServletRequest request,
37         HttpServletResponse response)
38         throws ServletException, IOException {
39         processRequest(request, response);
40     }
41     /**
42      * Process the request.
43      *
44      * @param request
45      * @param response
46      *
47      * @throws ServletException
48      * @throws IOException
49      */
50     protected void processRequest(
51         HttpServletRequest request,
52         HttpServletResponse response)
53         throws ServletException, IOException {
54         String error = null;
55         String classLocation = null;
56         String className = request.getParameter(CLASS_NAME);
57         if ((className != null)
58             && ((className = className.trim()).length() != 0)) {
59             // Attempt to load class and get its location.
60             try {
61                 ProtectionDomain pd =
62                     Class.forName(className).getProtectionDomain();
63                 if (pd != null) {
64                     CodeSource cs = pd.getCodeSource();
65                     if (cs != null) {
66                         classLocation = cs.toString();
67                     } else {
68                         error = "No CodeSource found!";
69                     }
70                 } else {
71                     error = "No ProtectionDomain found!";
72                 }
73             } catch (Throwable t) {
74                 error = t.toString();
75                 log("error=" + t, t);
76             }
77         }
78         // Set content type and get the writer.
79         response.setContentType("text/html");
80         PrintWriter out = response.getWriter();
81         // Write out content.
82         out.println(
83             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">");
84         out.println("<html>");
85         out.println("<head>");
86         out.println("<title>Servlet Container Class Finder</title>");
87         out.println("</head>");
88         out.println("<body bgcolor=\"#d1d7b3\">");
89         out.println(
90             "<h2><font color=\"#0000a0\">Servlet Container Class Finder</font></h2>");
91         out.println(
92             "<p><font color=\"#000000\">Enter the fully-qualified name of a Java class");
93         out.println(
94             "(e.g. org.apache.oro.text.regex.Perl5Compiler) in the field below. The");
95         out.println(
96             "servlet will attempt to load the class and, if successful, query the");
97         out.println(
98             "classes' <em>java.security.CodeSource</em> for the location of the class");
99         out.println("using the following methods: <pre>");
100         out.println(
101             "Class.forName(className).getProtectionDomain().getCodeSource()");
102         out.println("</pre> </font></p>");
103         out.println(
104             "<form method=\"post\" action=\""
105                 + request.getRequestURI()
106                 + "\">");
107         out.println(
108             "<p>Class Name: <input type=\"text\" name=\"" + CLASS_NAME + "\"");
109         out.println(
110             "\tvalue=\""
111                 + ((className != null) ? className : "")
112                 + "\" size=\"40\" /> <input type=\"submit\"");
113         out.println("\tvalue=\"Submit\" /></p>");
114         out.println("</form>");
115         if ((classLocation != null) || (error != null)) {
116             out.println("<table border=\"1\" bgcolor=\"#8080c0\">");
117             out.println(
118                 "\t<caption align=\"top\"><font color=\"#000000\">Search Results</font></caption>");
119             out.println("\t<tbody>");
120             out.println("\t\t<tr>");
121             out.println(
122                 "\t\t\t<td align=\"right\"><font color=\"#000000\">Class Name:</font></td>");
123             out.println(
124                 "\t\t\t<td><font color=\"#000000\">"
125                     + className
126                     + "</font></td>");
127             out.println("\t\t</tr>");
128             out.println("\t\t<tr>");
129             if (error != null) {
130                 out.println(
131                     "\t\t\t<td align=\"right\"><font color=\"#a00000\">Error:</font></td>");
132                 out.println(
133                     "\t\t\t<td><font color=\"#a00000\">"
134                         + error
135                         + "</font></td>");
136             } else {
137                 out.println(
138                     "\t\t\t<td align=\"right\"><font color=\"#000000\">Class Location:</font></td>");
139                 out.println(
140                     "\t\t\t<td><font color=\"#000000\">"
141                         + classLocation
142                         + "</font></td>");
143             }
144             out.println("\t\t</tr>");
145             out.println("\t</tbody>");
146             out.println("</table>");
147         }
148         out.println("</body>");
149         out.println("</html>");
150         // Finished.
151         out.flush();
152         out.close();
153     }

  怎样使用 servlet

  我们提供了本文用到的 WAR 文件中 Java 源代码形式和 compiled .class 形式的 servlet 的下载。但是,怎样安装该 servlet 将取决于包含开放源代码 JAR 文件的机制。如果 JAR 文件包含在 EAR 文件的根目录下(该类项目大都这样建议),那么,您可以直接将整个 WAR 文件包含在 EAR 文件内。然而,如果您将 JAR 文件存储于 WAR 文件的 /lib 目录下,那么您需要将 servlet .class 文件包含在 WAR 文件的 /bin 目录下,并更新 WAR 文件的 web.xml 文件,以使其包含我们提供的 web.xml 对 servlet 的引用。请记住这只是一个程序调试期的调试工具而已。为安全起见,您应该在将应用程序配置到产品中之前,确保该 servlet 已在 WAR 或者 EAR 文件中删除。

  解决类路径冲突

  一旦您确定类正在从 WebSphere Application Server 提供的 JAR 文件装载,而不是从您的 J2EE 组件提供的 JAR 文件装载,下一步要做的就是确定怎样装载类的正确版本。有时最好的办法只需删除应用程序的类路径(例如,通过删除从 EAR 文件中复制的开放源代码 JAR 文件)。通常非常好的解决方案是在 WebSphere 下设定类装载器(classloader)以使用 PARENT_LAST classloader 模式而不是默认的 PARENT_FIRST 模式。PARENT_LAST classloader 模式会导致类装载器在装载到父类之前首先尝试从自身的类路径装载类。该方案允许应用程序类装载器优先于父类,并提供存于父类中的自身的类版本。

  结束语

  在本文中,我们向您说明在处理开放源代码 Java 软件时,类路径冲突是很常见的。并且向您介绍了类路径问题发生时如何确认的通用方法。鉴别这些冲突正是解决它们的第一步,本方法为您解决这些问题提供了简易的途径。

  代码下载:classfinder.ZIP

0
相关文章