Java编程极限考验:ClassLoader类装载策略

04-07-26 banq
         

个人认为,Java编程中极限考验是Classloader机制的掌握和灵活运用,特别是在复杂的系统,如存在动态类装载,Reflect,EJB,AOP等环境。

CLass.forName()

和Thread.currentThread().getContextClassLoader())

是否一样?

在很多文章中,都认为两者是一致的,如Java研究组织中一篇文章,被我从google搜索到的:

http://www.javaresearch.org/article/showarticle.jsp?column=31&thread=10178

文中说"这个方法可以用Class.forName()代替",在一般简单情况是可以替代,但实际上有时候是不能替代的。

Classloader存在下面问题:

在一个JVM中可能存在多个ClassLoader,每个ClassLoader拥有自己的NameSpace。一个ClassLoader只能拥有一个class对象类型的实例,但是不同的ClassLoader可能拥有相同的class对象实例,这时可能产生致命的问题。如ClassLoaderA,装载了类A的类型实例A1,而ClassLoaderB,也装载了类A的对象实例A2。逻辑上讲A1=A2,但是由于A1和A2来自于不同的ClassLoader,它们实际上是完全不同的,如果A中定义了一个静态变量c,则c在不同的ClassLoader中的值是不同的。

因此,研究JBoss的ClassLoader策略,对于更好地实现EJB组件拼装是用好处的,因为,一个项目中可能要用其他项目的EJB组件,如何实现运行时EJB组件共享,如何实现EJB组件打包是很重要的。

为了说明ClassLoader对于复杂架构是至重关键,列举开源Portal产品Exo中ServivesManager类内容。

该类是Exo利用PicoCOntainer实现功能性Service JavaBeans初始化,在将那些Service性质的JavaBeans加载到pico中时,需要使用到Classloader,

Exo专门设立一个ServiceContext类:

public class ServiceContext {

  private ClassLoader cl;  //包含Classloader信息
  private Services services;

  public ServiceContext(ClassLoader cl, Services services) {
    this.cl = cl;
    this.services = services;
  }

  public ClassLoader getCl() {
    return cl;
  }

  public Services getServices() {
    return services;
  }

}
<p>

在ServicesManager中,有:

private ClassLoader updatedClassLoader;

它的初始值是:

Thread.currentThread().getContextClassLoader();

如果,这里写Class.forName 那么简单,那么你头疼去吧。

但是这样不够:

在addService方法中,根据加入的不同ServiceContext实现类装载:

 public void addService(ServiceContext context) {
    Services servicesToAdd = context.getServices();
    String name = servicesToAdd.getName();
    URLClassLoader cl = null;
    if(context.getCl() instanceof URLClassLoader) {
      cl = (URLClassLoader) context.getCl();
    } else {
      cl = URLClassLoader.newInstance(new URL[]{}, context.getCl());
    }

    updatedClassLoader = new URLClassLoader(cl.getURLs(), updatedClassLoader);
    synchronized (servicesContext) {
      servicesContext.put(name, context);
      reloadContainer();
    }
  }

其实向Picocontainer中加入一个服务很简单,上述方法的主要代码是处理Classloader,考虑到Classloader有嵌套关系,上述代码小心使用这个Service服务的父Classloader,使用父Classloader装载服务Service。

希望有兴趣者一起讨论。

         

3
banq
2004-07-26 15:35

下面一段代码 是在Web容器中,实现Servlet类或Jsp类的动态装载,一般Servlet类部署到Web容器,是由Web容器监测Web.xml是否变化,<br>以决定是否装载,这是非常被动的,使用下面代码通过ClassLoader可以在自己程序中主动实现装载:<br>

public class WrapperServlet extends Servlet {
   public void init(ServletConfig config) {
     m_config = config; // stored as you don't want this to change
   }

   public void doGet(...) throws ServletException {
     getServlet().doGet(...);
   }

   private Servlet getServlet() throws ServletException {
     if ((m_servlet == null) || haveClassesChanged()) {
         创造一个独立的classloader 确保它的父是我们的自己的ClassLoader<br>
        path_to_separate_classes是将被装载的路

       URLClassLoader loader = new URLClassLoader(new URL[] {
         new URL("path_to_separate_classes");
       }, WrapperServlet.class.getClassLoader());

       从被装载的路径使用自己装载器来装载Servlet类       
       Class servletClass =
         loader.loadClass(m_config.getInitParameter("servletClass");
       m_servlet = (Servlet)servletClass.newInstance();
       m_servlet.init(m_config);
     }
     return m_servlet;
   }

   private boolean haveClassesChanged() {
     // determine whether classes have changed somehow
   }

   private ServletConfig m_config;
   private Servlet m_servlet = null;
}
<p>

flyisland
2004-07-27 10:13

以前写的一篇文章:http://www.blogbus.com/blogbus/blog/diary.php?diaryid=153255,可供参考。

cats_tiger
2004-07-27 10:32

嗯,难道需要编写通用的ClassLoader查询类?我写了一个...

// 摘自javax.xml.parsers.FactoryFinder
public final class ClassLoaderFinder {
  private ClassLoaderFinder() {
  }

  /**
   * Figure out which ClassLoader to use.  For JDK 1.2 and later use the
   * context ClassLoader if possible.  Note: we defer linking the class
   * that calls an API only in JDK 1.2 until runtime so that we can catch
   * LinkageError so that this code will run in older non-Sun JVMs such
   * as the Microsoft JVM in IE.
   */
  public static ClassLoader findClassLoader() throws ConfigurationError {
    ClassLoader classLoader;
    try {
      // Construct the name of the concrete class to instantiate
      Class clazz = Class.forName(ClassLoaderFinder.class.getName()
                                  + "$ClassLoaderFinderConcrete");
      ContextClassLoaderFinder clf = (ContextClassLoaderFinder) clazz.
          newInstance();
      classLoader = clf.getContextClassLoader();
    }
    catch (LinkageError le) {
      // Assume that we are running JDK 1.1, use the current ClassLoader
      classLoader = ClassLoaderFinder.class.getClassLoader();
    }
    catch (ClassNotFoundException x) {
      // This case should not normally happen.  MS IE can throw this
      // instead of a LinkageError the second time Class.forName() is
      // called so assume that we are running JDK 1.1 and use the
      // current ClassLoader
      classLoader = ClassLoaderFinder.class.getClassLoader();
    }
    catch (Exception x) {
      // Something abnormal happened so throw an error
      throw new ConfigurationError(x.toString(), x);
    }
    return classLoader;
  }

  static class ConfigurationError
      extends Error {
    private Exception exception;

    /**
     * Construct a new instance with the specified detail string and
     * exception.
     */
    ConfigurationError(String msg, Exception x) {
      super(msg);
      this.exception = x;
    }

    Exception getException() {
      return exception;
    }
  }

  /*
   * The following nested classes allow getContextClassLoader() to be
   * called only on JDK 1.2 and yet run in older JDK 1.1 JVMs
   */

  private static abstract class ContextClassLoaderFinder {
    abstract ClassLoader getContextClassLoader();
  }

  static class ClassLoaderFinderConcrete
      extends ContextClassLoaderFinder {
    ClassLoader getContextClassLoader() {
      return Thread.currentThread().getContextClassLoader();
    }
  }

  //Sample
  public static void main(String args[]) {
    try {

      final String CLASS_NAME = "oracle.jdbc.driver.OracleDriver";
      Class spiClass;
      ClassLoader classLoader = ClassLoaderFinder.findClassLoader();
      if (classLoader == null) {
        System.out.println("From Class.forName");
        spiClass = Class.forName(CLASS_NAME);
      }
      else {
        System.out.println("From ClassLoaderFinder");
        spiClass = classLoader.loadClass(CLASS_NAME);
        spiClass.newInstance();
      }

      java.sql.Connection conn = java.sql.DriverManager.getConnection(
          "jdbc:oracle:thin:@localhost:1521:ORCL",
          "scott",
          "tiger");
      java.sql.Statement stmt = conn.createStatement();
      java.sql.ResultSet rs = stmt.executeQuery("select * from cat");
      while (rs.next()) {
        System.out.println(rs.getString(2));
      }
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

}
<p>

asdlcj
2004-07-27 10:53

大家还是看深入java虚拟机那本书把

说的很详细了

我不想再多说了!

12Go 1 2 3 4 ... 12 下一页