惨淡人生,平淡生活

The Feature Is Stupid

2008年5月29日 #

实现web服务的三个误区 读后感

我的消息吃了我的服务器!Kyle指出,通常,Web服务开发者开始经历“内存溢出”的错误或者奇怪的“性能问题”时,总是会发现服 务器拥有极高的处理负载,CPU使用率接近100%,以及较低的吞吐量和高网络延迟。导致这些症状的典型原因是非常大的(有时会达到50 MB或者更大)消息。而且,这些大消息往往包含了非常大的、作为XML消息主体的、采用base-64编码的二进制编码信息。导致其发生的原因通常是:

……开发者不理解技术的局限性:XML处理对解决许多问题都有用,但是你必须认识到消息是要被解析的——并且在大多数……产品中,这就意味着许多或者所有的消息都会驻留在内存中。

Kyle建议采用如下方法来改善这种情况:

  • 不要发送冗余信息。在许多情况下,发送二进制数据时,你可能会发现消息高度重复。如果是这样,你可能就要考虑在HTTP层面使用压缩技术来改善你的网络延迟。虽然这不会帮助你处理负载,但可能有助于减轻其中一个问题。
  • 在XML消息体中,根本不要嵌入二进制信息。这是较好的解决方法,还有几种不同的途径可以实现这一效果。比如,你可以使用带有附件的SOAP或者消息传输优化机制(MTOM)绕过解析开销,尽管这无助于网络延迟问题。
  • ……还有一个更好的办法,使用SOAP根本不发送大的二进制blob。替代方法,通过受控的文件传输系统,使用一个“带外数据”传输……或者“声明标签(claim Check,参见《EIP模式》或这里)”模式,避免在SOAP和HTTP上发送大的二进制文件。




任何一种技术都有它使用的环境,在做架构设计的时候一定要避免因为个人的偏好,无意识的舍弃某些选择。 一种简单的方法论是,根据需要达到的目的,列出所有可能的实现方案,最后做出决定。


不好意思,你的数据正在显示。根据Kyle所说,另一个典型的Web服务的“性能问题” 是,使用Web服务的层面非常、非常低——通常Web服务跟一个SQL语句相关,这是因为:

误解了SOA架构原则。一个优秀SOA架构的关键原则是你的服务应该具有高复用性。

根据Kyle所说,这些情况通常发生在:

……如果设计是根据现有代码“自上而下”衍生出服务,这类服务就会出现;通常,开发者会看着他们现有的架构图并且决定将架构中的每一层(包括表现层)转变成服务集。

相反,在SOA架构的正确位置使用粗粒度的Web服务会更好。再次强调,检查一个架构的标准分层模型,通常在架构中会有一个明确定义的地方已经封装 了系统业务逻辑。可以使用“远程门面模式(Remote Facade Pattern)”来包装这些服务,以便用合适的方式来暴露基于模型的服务。



同样是可以利用方法论来避免问题,但对于粒度的把握就是一个经验的问题。




模式(Schema)?我们不需要任何发臭的模式! Kyle指出,通常开发者试图重用现有代码来生成和解析作为Web服务实现基础的XML。这些实现通常使用XML解析器来编组/解组消息,同时使用 Java HTTP类来发送和接收XML文档。使用Web服务时,通用的方法是,创建使用模式元素的WSDL文档,使XML不受阻地通过,然后在现有代码中对它们进 行解析。

这个问题的症状是组织没有看到SOA承诺的好处,而且维护他们的解决方案似乎比以前使用Web服务的时候更难(而不是更容易)

简单的解决方案是,每当写Web服务时,不管使用WS-*标准还是使用REST方法,都要确保你创建了代表你文档结构的完整准确的XML模式。

如果你正在构建WS-* Web服务,那么这个XML应该被包含在描述你的Web服务的WSDL之中。即使你在使用REST方法,拥有易于访问的XML模式将鼓励你的服务被重用。






posted @ 2009-03-16 14:55 季失羽 阅读(209) | 评论 (0)编辑 收藏

如何看懂Java混淆后的反编译代码(转)

如何看懂Java混淆后的反编译代码

作者:dozb

一般情况下Java应用的开发者为了保护代码不被别人抄袭,在生成class文件的时候都java文件进行了混淆,这种class文件用反编译工具得到的结果很难看懂,并且不能进行编译。本文从研究的角度,浅析如何读懂这种反编译过来的文件。

例子一:赋值
反编译过来的代码如下:
        Node node;
        Node node1 = _$3.getChildNodes().item(0);
        node1;
        node1;
        JVM INSTR swap ;
        node;
        getChildNodes();
        0;
        item();
        getChildNodes();
        0;
        item();
        getNodeValue();
        String s;
        s;
原始语句:
        Node node;
        Node node1 = currDocument.getChildNodes().item(0);
 node = node1;
        String s = node.getChildNodes().item(0).getChildNodes().item(0).getNodeValue();
注解:
        JVM INSTR swap ; //赋值语句
练习:
        String s1;
        String s8 = node.getChildNodes().item(1).getChildNodes().item(0).getNodeValue();
        s8;
        s8;
        JVM INSTR swap ;
        s1;
        10;
        Integer.parseInt();
        int i;
        i;

   
例子二:不带参数创建对象
反编译过来的代码如下:
        JVM INSTR new #244 <Class CrossTable>;
        JVM INSTR dup ;
        JVM INSTR swap ;
        CrossTable();
        CrossTable crosstable;
        crosstable;

原始语句:
        CrossTable crosstable = new CrossTable();
注解:
练习:
        JVM INSTR new #246 <Class Database>;
        JVM INSTR dup ;
        JVM INSTR swap ;
        Database();
        Object obj;
        obj;

例子三:带参数创建对象
反编译过来的代码如下:
        JVM INSTR new #262 <Class StringBuffer>;
        JVM INSTR dup ;
        JVM INSTR swap ;
        String.valueOf(s2);
        StringBuffer();
        s.substring(j, i);
        append();
        s6;
        append();
        toString();
        s2;
 
原始语句:
 s2 = (new StringBuffer(String.valueOf(s2))).append(s.substring(j, i)).append(s6).toString();
注解:
 此语句实际上是:s2 += s.substring(j, i) + s6;
练习:

例子四:for循环
反编译过来的代码如下:
        int k = 0;
          goto _L4
_L8:
 ...
 k++;
_L4:
        if(k < as.length) goto _L8; else goto _L7

原始语句:
 for(int k=0;k < as.length;k++)
 {
     ...
 }
注解:

例子五:while循环
反编译过来的代码如下:
        String s1 = "";
          goto _L1
_L3:
        JVM INSTR new #262 <Class StringBuffer>;
        JVM INSTR dup ;
        JVM INSTR swap ;
        String.valueOf(s1);
        StringBuffer();
        _$2(resultset, s, l);
        append();
        toString();
        s1;
_L1:
        if(resultset.next()) goto _L3; else goto _L2

原始语句:
 String s1 = "";
 while(resultset.next())
 {
  s1 = s1 + resultSetToString(resultset, s, l);

 }

posted @ 2009-03-03 09:59 季失羽 阅读(1632) | 评论 (0)编辑 收藏

[翻译]走出ClassLoader的迷宫

[说明]几个关键字将不翻译

  1. ClassLoader
  2. System
  3. Context
  4. Thread

走出ClassLoader的迷宫

                                                               System、Current和Context ClassLoader?分别在何种情形下使用?


1、问题:在何种情形下使用thread.getcontextclassloader()?

尽管没经常遇到这个问题,但是想获得准确的答案并不那么容易,特别是在开发应用框架的时候,你需要动态的加载一些类和资源,不可避免的你会被此困扰。一般来说,动态载入资源有三种ClassLoader可以选择,System ClassLoader(也叫App ClassLoader)、当前类的ClassLoader和CurrentThread的Context ClassLoader。那么, 如何选择使用?

首先可以简单排除的是System ClassLoader,这个ClassLoader负责从参数-classpath、-cp、和操作系统CLASSPATH中载入资源。并且,任何ClassLoader的getSystemXXX()方法都是有以上几个路径指定的。我们应该很少需要编写直接使用ClassLoader的程序,否则你的代码将只能在命令行运行,发布你的代码成为ejb、web应用或者java web start应用,我肯定他们会崩溃!

接下来,我们只剩下两个选择了:当前ClassLoader和Thread Context ClassLoader

Current ClassLoader:当前类所属的ClassLoader,在虚拟机中类之间引用,默认就是使用这个ClassLoader。另外,当你使用Class.forName(), Class.getResource()这几个不带ClassLoader参数的方法是,默认同样适用当前类的ClassLoader。你可以通过方法XX.class.GetClassLoader()获取。

Thread Context ClassLoader,没一个Thread有一个相关联系的Context ClassLoader(由native方法建立的除外),可以通过Thread.setContextClassLoader()方法设置。如果你没有主动设置,Thread默认集成Parent Thread的 Context ClassLoader(注意,是parent Thread 不是父类)。如果 你整个应用中都没有对此作任何处理,那么 所有的Thread都会以System ClassLoader作为Context ClassLoader。知道这一点很重要,因为从web服务器,java企业服务器使用一些复杂而且精巧的ClassLoader结构去实现诸如JNDI、线程池和热部署等功能以来,这种简单的情况越发的少见了。

这篇文章中为什么把Thread Context ClassLoader放在首要的位置,别人并没有大张旗鼓的介绍它?很多开发者都对此不甚了解,因为sun没有提供很好的说明文档。

事实上,Context ClassLoader提供一个突破委托代理机制的后门。虚拟机通过父子层次关系组织管理ClassLoader,没有个ClassLoader都有一个Parent ClassLoader(BootStartp不在此范围之内),当要求一个ClassLoader装载一个类是,他首先请求Parent ClassLoader去装载,只有parent ClassLoader装载失败,才会尝试自己装载。

但是,某些时候这种顺序机制会造成困扰,特别是jvm需要动态载入有开发者提供的资源时。就以JNDI为例,JNDI的类是由bootstarp ClassLoader从rt.jar中间载入的,但是JNDI具体的核心驱动是由正式的实现提供的,并且通常会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?parent可以通过获得当前调用Thread的方法获得调用线程的Context ClassLoder 来载入类。

顺带补充一句,JAXP从1.4之后也换成了类似JNDI的ClassLoader实现,嘿嘿,刚刚我说什么来着,SUN文档缺乏 ^_^

介绍完这些之后,我们走到的十字路口,任一选择都不是万能的。一些人认为Context ClassLoader将会是新的标准。但是 一旦你的多线程需要通讯某些共享数据,你会发现,你将有一张极其丑陋的ClassLoader分布图,除非所有的线程使用一样的Context ClassLoader。并且委派使用当前ClassLoder对一些方法来说是默认继承来的,比如说Class.forName()。尽管你明确的在任何你能控制的地方使用Context ClassLoader,但是毕竟还有很多代码不归你管(备注:想起一个关于UNIX名字来源的笑话)。

某些应用服务器使用不同的ClassLoder作为Context ClassLoader和当前ClassLoader,并且这些ClassLoader有着相同的ClassPath,但没有父子关系,这使得情况更复杂。请列位看官,花几秒钟时间想一想,为什么这样不好?被载入的类在虚拟机内部有一个全名称,不同的ClassLoader载入的相同名称的类是不一样的,这就隐藏了类型转换错误的隐患。(注:奶奶的 俺就遇到过,JBOSSClassLoader机制蛮挫的)

这种混乱事实上在java类中也有,试着去猜测任何一个包含动态加载的java规范的ClassLoader机制,以下是一个清单:

  • JNDI uses context classloaders
  • Class.getResource() and Class.forName() use the current classloader
  • JAXP uses context classloaders (as of J2SE 1.4)
  • java.util.ResourceBundle uses the caller's current classloader
  • URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only
  • Java Serialization API uses the caller's current classloader by default

而且关于这些资源的类加载机制文档时很少。

java开发人员应该怎么做?

如果你的实现是利用特定的框架,那么恭喜你,实现它远比实现框架要简单得多!例如,在web应用和EJB应用中,你仅仅只要使用 Class.getResource()就足够了。

其他的情形下,俺有个建议(这个原则是俺工作中发现的,侵权必究,抵制盗版。),

下面这个类可以在整个应用中的任何地方使用,作为一个全局的ClassLoader(所有的示例代码可以从download下载):

 1 public abstract class ClassLoaderResolver {
 2 /**
 3 * This method selects the best classloader instance to be used for
 4 * class/resource loading by whoever calls this method. The decision
 5 * typically involves choosing between the caller's current, thread context,
 6 * system, and other classloaders in the JVM and is made by the
 7 * {@link IClassLoadStrategy} instance established by the last call to
 8 * {@link #setStrategy}.
 9 *
10 @return classloader to be used by the caller ['null' indicates the
11 * primordial loader]
12 */
13 public static synchronized ClassLoader getClassLoader() {
14 final Class caller = getCallerClass(0);
15 final ClassLoadContext ctx = new ClassLoadContext(caller);
16 
17 return s_strategy.getClassLoader(ctx);
18 }
19 
20 public static synchronized IClassLoadStrategy getStrategy() {
21 return s_strategy;
22 }
23 
24 public static synchronized IClassLoadStrategy setStrategy(
25 final IClassLoadStrategy strategy) {
26 final IClassLoadStrategy old = s_strategy;
27 s_strategy = strategy;
28 
29 return old;
30 }
31 
32 /**
33 * A helper class to get the call context. It subclasses SecurityManager to
34 * make getClassContext() accessible. An instance of CallerResolver only
35 * needs to be created, not installed as an actual security manager.
36 */
37 private static final class CallerResolver extends SecurityManager {
38 protected Class[] getClassContext() {
39 return super.getClassContext();
40 }
41 
42 // End of nested class
43 
44 /*
45 * Indexes into the current method call context with a given offset.
46 */
47 private static Class getCallerClass(final int callerOffset) {
48 return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET
49 + callerOffset];
50 }
51 
52 private static IClassLoadStrategy s_strategy; // initialized in <clinit>
53 
54 private static final int CALL_CONTEXT_OFFSET = 3// may need to change if
55 // this class is
56 // redesigned
57 private static final CallerResolver CALLER_RESOLVER; // set in <clinit>
58 
59 static {
60 try {
61 // This can fail if the current SecurityManager does not allow
62 // RuntimePermission ("createSecurityManager"):
63 
64 CALLER_RESOLVER = new CallerResolver();
65 catch (SecurityException se) {
66 throw new RuntimeException(
67 "ClassLoaderResolver: could not create CallerResolver: "
68 + se);
69 }
70 
71 s_strategy = new DefaultClassLoadStrategy();
72 }
73 // End of class.
74 
75 
76 


通过ClassLoaderResolver.getClassLoader()方法获得一个ClassLoader的引用,并且利用正常的ClassLoader的api去加载资源,你也可以使用 ResourceLoader API作为备选方案

 1 public abstract class ResourceLoader {
 2 
 3 /**
 4  * @see java.lang.ClassLoader#loadClass(java.lang.String)
 5  */
 6 public static Class loadClass (final String name)throws ClassNotFoundException{
 7 
 8 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
 9 
10 return Class.forName (name, false, loader);
11 
12 }
13 
14 /**
15 
16 @see java.lang.ClassLoader#getResource(java.lang.String)
17 
18 */    
19 
20 
21 public static URL getResource (final String name){
22 
23 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
24 
25 if (loader != null)return loader.getResource (name);
26 else return ClassLoader.getSystemResource (name);
27 }
28  more methods 
29 
30 // End of class

而决定使用何种ClassLoader策略是由接口实现的,这是一种插件机制,方便变更。

public interface IClassLoadStrategy{
ClassLoader getClassLoader (ClassLoadContext ctx);
// End of interface

它需要一个ClassLoader Context 对象去决定使用何种ClassLoader策略。
 1 public class ClassLoadContext{
 2 
 3 public final Class getCallerClass (){
 4 return m_caller;
 5 }
 6 
 7 ClassLoadContext (final Class caller){
 8 m_caller = caller;
 9 
10 }
11 
12 private final Class m_caller;
13 
14 // End of class

ClassLoadContext.getCallerClass()返回调用者给ClassLoaderResolver 或者 ResourceLoader,因此能获得调用者的ClassLoader。需要注意的是,调用者是不会变的 (注:作者使用的final修饰字)。俺的方法不需要对现有的业务方法做扩展,而且可以作为静态方法是用。而且,你可以根据自己的业务场景实现独特的ClassLoaderContext。

看出来没,这是一种很熟悉的设计模式,XD ,把获得ClassLoader的策略从业务中独立出来,这个策略可以是"总是用ContextClassLoader"或者"总是用当前ClassLoader"。想预先知道那种策略是正确的比较困难,那么这种模式可以让你简单的改变策略。

俺写了一个默认的实现,基本可以对付95%的场景(enjoy yourself)

 1 public class DefaultClassLoadStrategy implements IClassLoadStrategy{
 2 
 3 public ClassLoader getClassLoader (final ClassLoadContext ctx){
 4 
 5 final ClassLoader callerLoader = ctx.getCallerClass ().getClassLoader ();
 6 
 7 final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
 8 
 9 ClassLoader result;
10 // If 'callerLoader' and 'contextLoader' are in a parent-child
11 // relationship, always choose the child:
12 if (isChild (contextLoader, callerLoader))result = callerLoader;
13 else if (isChild (callerLoader, contextLoader))result = contextLoader;
14 else{
15 // This else branch could be merged into the previous one,
16 // but I show it here to emphasize the ambiguous case:
17 result = contextLoader;
18 }
19 final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();
20 
21 
22 // Precaution for when deployed as a bootstrap or extension class:
23 if (isChild (result, systemLoader))result = systemLoader;
24 return result;
25 }
26 
27 
28 
29  more methods 
30 
31 // End of class
32 


上面的逻辑比较简单,如果当前ClassLoader和Context ClassLoader是父子关系,那就总选儿子,根据委托原则,这个很容易理解。

如果两人平级,选择正确的ClassLoader很重要,运行时不允许含糊。这种情况下,我的代码选择Context ClassLoader(这是俺个人的经验之谈),当然也不要担心不能改变,你能随便根据需要改变。一般而言,Context ClassLoader比较适合框架,而Current ClassLoader在业务逻辑中用的更多。

最后,检查确保选中的ClassLoader不是System ClassLoader的parent,一旦高于System ClassLoader ,请使用System ClassLoader(你的类部署在Ext路径下面,就会出现这种情况)。

请注意,俺故意没关注被载入资源的名称。Java XML API 成为java 核心api的经历告诉我们,根据资源名称过滤是很不cool的idea。而且 我也没有去确认到底哪个ClassLoader被取得了,因为只要清楚原理,这很容易被推理出来。(哈哈,俺是强淫)

尽管讨论java 的ClassLoader不是一个很cool的话题(译者注,当年不cool,但是现在很cool),而且Java EE的ClassLoader策略越发的依赖各种平台的升级。如果这没有一个更好的设计的话,将会变成一个大大的问题。不敢您是否同意俺的观点,俺尊重你说话的权利,所以请给俺分享您的意见经验。

作者介绍:

Vladimir Roubtsov,曾经使用多种语言有超过13年的编程经历(恩 现在应该超过15年了 hoho),95年开始接触java(hoho 俺是99年看的第一本java书)。现在为Trilogy in Austin, Texas开发企业软件。



翻译完了,MMD 翻译还是很麻烦的。 XD ........

43 Things :

posted @ 2008-05-30 18:22 季失羽 阅读(5277) | 评论 (0)编辑 收藏

ClassLoader的几个概念、类和对象的解释

首先,转载一篇文章,个人认为是看到过了讲得最清楚的 XD
 

当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:

        bootstrap classloader
                 |
        extension classloader
                 |
        system classloader

bootstrap classloader - 引导(也称为原始)类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用 -D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:
    URL[] urls=sun.misc.Launcher.getBootstrapClassPath().getURLs();
    
for (int i = 0; i < urls.length; i++) {
      System.out.println(urls.toExternalForm());
    }

在我的计算机上的结果为:
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/dom.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/sax.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xalan-2.3.1.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xercesImpl-2.0.0.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xml-apis.jar
file:/C:/j2sdk1.4.1_01/jre/lib/endorsed/xsltc.jar
file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar
file:/C:/j2sdk1.4.1_01/jre/lib/i18n.jar
file:/C:/j2sdk1.4.1_01/jre/lib/sunrsasign.jar
file:/C:/j2sdk1.4.1_01/jre/lib/jsse.jar
file:/C:/j2sdk1.4.1_01/jre/lib/jce.jar
file:/C:/j2sdk1.4.1_01/jre/lib/charsets.jar
file:/C:/j2sdk1.4.1_01/jre/classes
这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。

extension classloader - 扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类 包。这为引入除Java核心类以外的新功能提供了一个标准机制。因为默认的扩展目录对所有从同一个JRE中启动的JVM都是通用的,所以放入这个目录的 JAR类包对所有的JVM和system classloader都是可见的。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。所以当大家执行以下代码时:

    System.out.println(System.getProperty(
"java.ext.dirs"));
    ClassLoader extensionClassloader
=ClassLoader.getSystemClassLoader().getParent();
    System.out.println(
"the parent of extension classloader : "+extensionClassloader.getParent());

结果为:
C:"j2sdk1.4.1_01"jre"lib"ext
the parent of extension classloader : null
extension classloader是system classloader的parent,而bootstrap classloader是extension classloader的parent,但它不是一个实际的classloader,所以为null。

system classloader - 系统(也称为应用)类加载器,它负责在JVM被启动时,加载来自在命令java中的-classpath或者java.class.path系统属性或者 CLASSPATH操作系统属性所指定的JAR类包和类路径。总能通过静态方法ClassLoader.getSystemClassLoader()找 到该类加载器。如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。执行以下代码即可获得:
    System.out.println(System.getProperty("java.class.path"));
输出结果则为用户在系统属性里面设置的CLASSPATH。
classloader 加载类用的是全盘负责委托机制。所谓全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有 Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;委托机制则是先让parent(父)类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必 须重新启动JVM才能生效的原因。


每个ClassLoader加载Class的过程是:
1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

其中5.6步我们可以通过覆盖ClassLoader的findClass方法来实现自己的载入策略。甚至覆盖loadClass方法来实现自己的载入过程。

类加载器的顺序是:
先 是bootstrap classloader,然后是extension classloader,最后才是system classloader。大家会发现加载的Class越是重要的越在靠前面。这样做的原因是出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类, 也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的。大家可以执行一下以下的代码:
    System.out.println(System.class.getClassLoader());
将会看到结果是null,这就表明java.lang.System是由bootstrap classloader加载的,因为bootstrap classloader不是一个真正的ClassLoader实例,而是由JVM实现的,正如前面已经说过的。

下面就让我们来看看JVM是如何来为我们来建立类加载器的结构的:
sun.misc.Launcher,顾名思义,当你执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,执行下来代码:
   System.out.println("the Launcher's classloader is "+sun.misc.Launcher.getLauncher().getClass().getClassLoader());
结果为:
   the Launcher's classloader is null (因为是用bootstrap classloader加载,所以class loader为null)
Launcher 会根据系统和命令设定初始化好class loader结构,JVM就用它来获得extension classloader和system classloader,并载入所有的需要载入的Class,最后执行java命令指定的带有静态的main方法的Class。extension classloader实际上是sun.misc.Launcher$ExtClassLoader类的一个实例,system classloader实际上是sun.misc.Launcher$AppClassLoader类的一个实例。并且都是 java.net.URLClassLoader的子类。

让我们来看看Launcher初试化的过程的部分代码。

Launcher的部分代码:
 1 public class Launcher  {
 2     public Launcher() {
 3         ExtClassLoader extclassloader;
 4         try {
 5             //初始化extension classloader
 6             extclassloader = ExtClassLoader.getExtClassLoader();
 7         } catch(IOException ioexception) {
 8             throw new InternalError("Could not create extension class loader");
 9         }
10         try {
11             //初始化system classloader,parent是extension classloader
12             loader = AppClassLoader.getAppClassLoader(extclassloader);
13         } catch(IOException ioexception1) {
14             throw new InternalError("Could not create application class loader");
15         }
16         //将system classloader设置成当前线程的context classloader(将在后面加以介绍)
17         Thread.currentThread().setContextClassLoader(loader);
18         
19     }
20     public ClassLoader getClassLoader() {
21         //返回system classloader
22         return loader;
23     }
24 }
25 

extension classloader的部分代码:
 1 static class Launcher$ExtClassLoader extends URLClassLoader {
 2 
 3     public static Launcher$ExtClassLoader getExtClassLoader()
 4         throws IOException
 5     {
 6         File afile[] = getExtDirs();
 7         return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
 8     }
 9    private static File[] getExtDirs() {
10         //获得系统属性“java.ext.dirs”
11         String s = System.getProperty("java.ext.dirs");
12         File afile[];
13         if(s != null) {
14             StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
15             int i = stringtokenizer.countTokens();
16             afile = new File;
17             for(int j = 0; j < i; j++)
18                 afile[j] = new File(stringtokenizer.nextToken());
19 
20         } else {
21             afile = new File[0];
22         }
23         return afile;
24     }
25 }


system classloader的部分代码:
 1 static class Launcher$AppClassLoader extends URLClassLoader
 2 {
 3 
 4     public static ClassLoader getAppClassLoader(ClassLoader classloader)
 5         throws IOException
 6     {
 7         //获得系统属性“java.class.path”
 8         String s = System.getProperty("java.class.path");
 9         File afile[] = s != null ? Launcher.access$200(s) : new File[0];
10         return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
11     }
12 }


看 了源代码大家就清楚了吧,extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。

这里怎么又出来一个context classloader呢?它有什么用呢?我们在建立一个线程Thread的时候,可以为这个线程通过setContextClassLoader方法来 指定一个合适的classloader作为这个线程的context classloader,当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的 Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的 classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例 如web应用中的servlet就是用这种机制加载的.


好了,现在我们了解了classloader的结构和工作原理,那么我们 如何实现在运行时的动态载入和更新呢?只要我们能够动态改变类搜索路径和清除classloader的cache中已经载入的Class就行了,有两个方 案,一是我们继承一个classloader,覆盖loadclass方法,动态的寻找Class文件并使用defineClass方法来;另一个则非常 简单实用,只要重新使用一个新的类搜索路径来new一个classloader就行了,这样即更新了类搜索路径以便来载入新的Class,也重新生成了一 个空白的cache(当然,类搜索路径不一定必须更改)。噢,太好了,我们几乎不用做什么工作,java.netURLClassLoader正是一个符 合我们要求的classloader!我们可以直接使用或者继承它就可以了!

这是j2se1.4 API的doc中URLClassLoader的两个构造器的描述:
URLClassLoader(URL[] urls)
          Constructs a new URLClassLoader for the specified URLs using the default delegation parent ClassLoader.
URLClassLoader(URL[] urls, ClassLoader parent)
          Constructs a new URLClassLoader for the given URLs.
其中URL[] urls就是我们要设置的类搜索路径,parent就是这个classloader的parent classloader,默认的是system classloader。


好,现在我们能够动态的载入Class了,这样我们就可以利用newInstance方法来获得一个Object。但我们如何将此Object造型呢?可以将此Object造型成它本身的Class吗?

首先让我们来分析一下java源文件的编译,运行吧!javac命令是调用“JAVA_HOME/lib/tools.jar”中的“com.sun.tools.javac.Main”的compile方法来编译:

    
public static int compile(String as[]);

    
public static int compile(String as[], PrintWriter printwriter);

返回0表示编译成功,字符串数组as则是我们用javac命令编译时的参数,以空格划分。例如:
javac -classpath c:"foo"bar.jar;. -d c:" c:"Some.java
则 字符串数组as为{"-classpath","c:""foo""bar.jar;.","-d","c:""","c:""Some.java"}, 如果带有PrintWriter参数,则会把编译信息出到这个指定的printWriter中。默认的输出是System.err。

其中 Main是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,编译器在解析这个java源文件时所发现的它所依赖和引用的所有Class也将由system classloader载入,如果system classloader不能载入某个Class时,编译器将抛出一个“cannot resolve symbol”错误。

所以首先编译就通不过,也就是编译器无法编译一个引用了不在CLASSPATH中的未知Class的java源文件,而由于拼写错误或者没有把所需类库放到CLASSPATH中,大家一定经常看到这个“cannot resolve symbol”这个编译错误吧!

其 次,就是我们把这个Class放到编译路径中,成功的进行了编译,然后在运行的时候不把它放入到CLASSPATH中而利用我们自己的 classloader来动态载入这个Class,这时候也会出现“java.lang.NoClassDefFoundError”的违例,为什么呢?

我 们再来分析一下,首先调用这个造型语句的可执行的Class一定是由JVM使用Launcher初始化的system classloader载入的,根据全盘负责原则,当我们进行造型的时候,JVM也会使用system classloader来尝试载入这个Class来对实例进行造型,自然在system classloader寻找不到这个Class时就会抛出“java.lang.NoClassDefFoundError”的违例。

OK, 现在让我们来总结一下,java文件的编译和Class的载入执行,都是使用Launcher初始化的system classloader作为类载入器的,我们无法动态的改变system classloader,更无法让JVM使用我们自己的classloader来替换system classloader,根据全盘负责原则,就限制了编译和运行时,我们无法直接显式的使用一个system classloader寻找不到的Class,即我们只能使用Java核心类库,扩展类库和CLASSPATH中的类库中的Class。

还 不死心!再尝试一下这种情况,我们把这个Class也放入到CLASSPATH中,让system classloader能够识别和载入。然后我们通过自己的classloader来从指定的class文件中载入这个Class(不能够委托 parent载入,因为这样会被system classloader从CLASSPATH中将其载入),然后实例化一个Object,并造型成这个Class,这样JVM也识别这个Class(因为 system classloader能够定位和载入这个Class从CLASSPATH中),载入的也不是CLASSPATH中的这个Class,而是从 CLASSPATH外动态载入的,这样总行了吧!十分不幸的是,这时会出现“java.lang.ClassCastException”违例。

为 什么呢?我们也来分析一下,不错,我们虽然从CLASSPATH外使用我们自己的classloader动态载入了这个Class,但将它的实例造型的时 候是JVM会使用system classloader来再次载入这个Class,并尝试将使用我们的自己的classloader载入的Class的一个实例造型为system classloader载入的这个Class(另外的一个)。大家发现什么问题了吗?也就是我们尝试将从一个classloader载入的Class的一 个实例造型为另外一个classloader载入的Class,虽然这两个Class的名字一样,甚至是从同一个class文件中载入。但不幸的是JVM 却认为这个两个Class是不同的,即JVM认为不同的classloader载入的相同的名字的Class(即使是从同一个class文件中载入的)是 不同的!这样做的原因我想大概也是主要出于安全性考虑,这样就保证所有的核心Java类都是system classloader载入的,我们无法用自己的classloader载入的相同名字的Class的实例来替换它们的实例。

看到这里,聪明的读者一定想到了该如何动态载入我们的Class,实例化,造型并调用了吧!

那 就是利用面向对象的基本特性之一的多形性。我们把我们动态载入的Class的实例造型成它的一个system classloader所能识别的父类就行了!这是为什么呢?我们还是要再来分析一次。当我们用我们自己的classloader来动态载入这我们只要把 这个Class的时候,发现它有一个父类Class,在载入它之前JVM先会载入这个父类Class,这个父类Class是system classloader所能识别的,根据委托机制,它将由system classloader载入,然后我们的classloader再载入这个Class,创建一个实例,造型为这个父类Class,注意了,造型成这个父类 Class的时候(也就是上溯)是面向对象的java语言所允许的并且JVM也支持的,JVM就使用system classloader再次载入这个父类Class,然后将此实例造型为这个父类Class。大家可以从这个过程发现这个父类Class都是由 system classloader载入的,也就是同一个class loader载入的同一个Class,所以造型的时候不会出现任何异常。而根据多形性,调用这个父类的方法时,真正执行的是这个Class(非父类 Class)的覆盖了父类方法的方法。这些方法中也可以引用system classloader不能识别的Class,因为根据全盘负责原则,只要载入这个Class的classloader即我们自己定义的 classloader能够定位和载入这些Class就行了。

这样我们就可以事先定义好一组接口或者基类并放入CLASSPATH中,然 后在执行的时候动态的载入实现或者继承了这些接口或基类的子类。还不明白吗?让我们来想一想Servlet吧,web application server能够载入任何继承了Servlet的Class并正确的执行它们,不管它实际的Class是什么,就是都把它们实例化成为一个Servlet Class,然后执行Servlet的init,doPost,doGet和destroy等方法的,而不管这个Servlet是从web- inf/lib和web-inf/classes下由system classloader的子classloader(即定制的classloader)动态载入。说了这么多希望大家都明白了。在applet,ejb等 容器中,都是采用了这种机制.

对于以上各种情况,希望大家实际编写一些example来实验一下。

最后我再说点别 的,classloader虽然称为类加载器,但并不意味着只能用来加载Class,我们还可以利用它也获得图片,音频文件等资源的URL,当然,这些资 源必须在CLASSPATH中的jar类库中或目录下。我们来看API的doc中关于ClassLoader的两个寻找资源和Class的方法描述吧:
public URL getResource(String name)
用指定的名字来查找资源,一个资源是一些能够被class代码访问的在某种程度上依赖于代码位置的数据(图片,音频,文本等等)。
                一个资源的名字是以'/'号分隔确定资源的路径名的。
                这个方法将先请求parent classloader搜索资源,如果没有parent,则会在内置在虚拟机中的classloader(即bootstrap classloader)的路径中搜索。如果失败,这个方法将调用findResource(String)来寻找资源。
public static URL getSystemResource(String name)
                从用来载入类的搜索路径中查找一个指定名字的资源。这个方法使用system class loader来定位资源。即相当于ClassLoader.getSystemClassLoader().getResource(name)。

例如:
    System.out.println(ClassLoader.getSystemResource("java/lang/String.class"));
的结果为:
    jar:file:/C:/j2sdk1.4.1_01/jre/lib/rt.jar!/java/lang/String.class
表明String.class文件在rt.jar的java/lang目录中。
因此我们可以将图片等资源随同Class一同打包到jar类库中(当然,也可单独打包这些资源)并添加它们到class loader的搜索路径中,我们就可以无需关心这些资源的具体位置,让class loader来帮我们寻找了!

以上是转自bea论坛的一篇文章,作者不清楚,估计是bea内部的大牛。是值得俺们顶礼膜拜的神一般的存在 XD



最后 附上自己的几点理解

bootstrap classloader  -------  对应jvm中某c++写的dll类
Extenson ClassLoader ---------对应内部类ExtClassLoader
System ClassLoader  ---------对应内部类AppClassLoader
Custom ClassLoader  ----------对应任何URLClassLoader的子类(你也可以继承SecureClassLoader或者更加nb一点 直接继承ClassLoader,这样的话你也是神一般的存在了 XD)


以上四种classloder按照从上到下的顺序,依次为下一个的parent

这个第一概念

第二个概念是几个有关的classloader的类

           抽象类 ClassLoader
                  |
            SecureClassLoader
                   |
            URLClassloader
             |           |                
 sun的ExtClassLoader   sun的AppClassLoader
以上的类之间是继承关系,与第一个概念说的parent是两回事情,需要小心。

第三个概念是Thread的ContextClassLoader
其实从Context的名称就可以看出来,这只是一个用以存储任何classloader引用的临时存储空间,与classloader的层次没有任何关系。


第四 就是如何实现自己的classloader了,本来是要翻译另外一篇文章Find a way out of the ClassLoader maze
不过今天时间都花在《小夫妻天天恶战》这篇神文上了 XD 强烈推荐任何和俺一样期望彪悍人生的朋友都去看看

翻译明天补上,浪费时间是可耻的 XD

匿鸟,晚上还有朋友推荐的 《lie with me》(与我同眠)要看。XD


posted @ 2008-05-29 20:11 季失羽 阅读(2238) | 评论 (1)编辑 收藏