置顶随笔

Our primary use case also has the user switching between online and offline, so these bugs are a big problem.

I threw the full weight of my three bug votes behind bug 6931553 ;)

The fix for bug 6911785 does seem to prevent the endless "hang" on launch (with an active TCP/IP connection but no internet access), but there are still lots of problems:
- launching without internet access can take 20s - 2 minutes!
- launching offline results in the non-sensical "this application has requested to go online" dialog
- the JNLPClassLoader ends up doing DNS lookups which occasionally cause long delays during execution

It's really ugly. We have unhappy customers. I have no idea how to get these bugs fixed. Any suggestions?
http://forums.sun.com/thread.jspa?threadID=5426321&start=0&tstart=0

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6931553

posted @ 2010-08-12 23:49 梧桐夜雨 阅读(199) | 评论 (0)编辑 收藏

了解ClassLoader
1,   什么是 ClassLoader?
    Java 程序并不是一个可执行文件,是需要的时候,才把装载到 JVM中。ClassLoader 做的工作就是 JVM 中将类装入内存。 而且,Java ClassLoader 就是用 Java 语言编写的。这意味着您可以创建自己的 ClassLoader
    ClassLoader 的基本目标是对类的请求提供服务。当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。 通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。
2, 一些重要的方法
A)  方法 loadClass
        ClassLoader.loadClass() 是 ClassLoader 的入口点。该方法的定义如下:
        Class loadClass( String name, boolean resolve );
         name  JVM 需要的类的名称,如 Foo 或 java.lang.Object。
         resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。
    
    cool.gif  方法 defineClass
       defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成final的。

    C)  方法 findSystemClass
       findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。) 对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。

     D) 方法 resolveClass
   正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。


   E) 方法 findLoadedClass
      findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。

3, 怎么组装这些方法
  1) 调用 findLoadedClass 来查看是否存在已装入的类。
  2) 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
  3) 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
  4) 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
  5) 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
  6) 如果还没有类,返回 ClassNotFoundException。

4,Java 2 中 ClassLoader 的变动
1)loadClass 的缺省实现
   定制编写的 loadClass 方法一般尝试几种方式来装入所请求的类,如果您编写许多类,会发现一次次地在相同的、很复杂的方法上编写变量。 在 Java 1.2 中 loadClass 的实现嵌入了大多数查找类的一般方法,并使您通过覆盖 findClass 方法来定制它,在适当的时候 findClass 会调用 loadClass。 这种方式的好处是您可能不一定要覆盖 loadClass;只要覆盖 findClass 就行了,这减少了工作量。

2)新方法:findClass
     loadClass 的缺省实现调用这个新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代码,而无需要复制其它代码(例如,当专门的方法失败时,调用系统 ClassLoader)。

3) 新方法:getSystemClassLoader
     如果覆盖 findClass 或 loadClass,getSystemClassLoader 使您能以实际 ClassLoader 对象来访问系统 ClassLoader(而不是固定的从 findSystemClass 调用它)。
 
4) 新方法:getParent 
    为了将类请求委托给父代 ClassLoader,这个新方法允许 ClassLoader 获取它的父代 ClassLoader。当使用特殊方法,定制的 ClassLoader 不能找到类时,可以使用这种方法。
父代 ClassLoader 被定义成创建该 ClassLoader 所包含代码的对象的 ClassLoader。
运用加密技术保护Java源代码
QUOTE
Java程序的源代码很容易被别人偷看。只要有一个反编译器,任何人都可以分析别人的代码。本文讨论如何在不修改原有程序的情况下,通过加密技术保护源代码。
一、为什么要加密?
对 于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以。遗憾的是,Java程序的源代码很容易被别人偷看。只要有一 个反编译器,任何人都可以分析别人的代码。Java的灵活性使得源代码很容易被窃取,但与此同时,它也使通过加密保护代码变得相对容易,我们唯一需要了解 的就是Java的ClassLoader对象。当然,在加密过程中,有关Java Cryptography Extension(JCE)的知识也是必不可少的。

有几种技术可以“模糊”Java类文件,使得反编译器处理类文件的效果大打折扣。然而,修改反编译器使之能够处理这些经过模糊处理的类文件并不是什么难事,所以不能简单地依赖模糊技术来保证源代码的安全。

我们可以用流行的加密工具加密应用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。这时,最终用户在运行应用之前必须先进行解密。但解密之后,最终用户就有了一份不加密的类文件,这和事先不进行加密没有什么差别。

Java 运行时装入字节码的机制隐含地意味着可以对字节码进行修改。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个对象负责把新的类装 入正在运行的JVM。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后由 ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。

我们可以通过定制ClassLoader,在类文件执行之前修改它。这种技术的应用非常广泛——在这里,它的用途是在类文件装入之时进行解密,因此可以看成是一种即时解密器。由于解密后的字节码文件永远不会保存到文件系统,所以窃密者很难得到解密后的代码。

由于把原始字节码转换成Class对象的过程完全由系统负责,所以创建定制ClassLoader对象其实并不困难,只需先获得原始数据,接着就可以进行包含解密在内的任何转换。

Java 2在一定程度上简化了定制ClassLoader的构建。在Java 2中,loadClass的缺省实现仍旧负责处理所有必需的步骤,但为了顾及各种定制的类装入过程,它还调用一个新的findClass方法。

这为我们编写定制的ClassLoader提供了一条捷径,减少了麻烦:只需覆盖findClass,而不是覆盖loadClass。这种方法避免了重复所有装入器必需执行的公共步骤,因为这一切由loadClass负责。

不 过,本文的定制ClassLoader并不使用这种方法。原因很简单。如果由默认的ClassLoader先寻找经过加密的类文件,它可以找到;但由于类 文件已经加密,所以它不会认可这个类文件,装入过程将失败。因此,我们必须自己实现loadClass,稍微增加了一些工作量。

二、定制类装入器
每一个运行着的JVM已经拥有一个ClassLoader。这个默认的ClassLoader根据CLASSPATH环境变量的值,在本地文件系统中寻找合适的字节码文件。

应 用定制ClassLoader要求对这个过程有较为深入的认识。我们首先必须创建一个定制ClassLoader类的实例,然后显式地要求它装入另外一个 类。这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader。Listing 1显示了如何用定制ClassLoader装入类文件。

【Listing 1:利用定制的ClassLoader装入类文件】

  // 首先创建一个ClassLoader对象
  ClassLoader myClassLoader = new myClassLoader();

  // 利用定制ClassLoader对象装入类文件
  // 并把它转换成Class对象
  Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );

  // 最后,创建该类的一个实例
  Object newInstance = myClass.newInstance();

  // 注意,MyClass所需要的所有其他类,都将通过
  // 定制的ClassLoader自动装入






如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。

ClassLoader 有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个 参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用 resolveClass。

【Listing 2:ClassLoader.loadClass()的一个简单实现】

      public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {
    try {
      // 我们要创建的Class对象
       Class clasz = null;

      // 必需的步骤1:如果类已经在系统缓冲之中,
      // 我们不必再次装入它
      clasz = findLoadedClass( name );

      if (clasz != null)
        return clasz;

      // 下面是定制部分
      byte classData[] = /* 通过某种方法获取字节码数据 */;
      if (classData != null) {
        // 成功读取字节码数据,现在把它转换成一个Class对象
        clasz = defineClass( name, classData, 0, classData.length );
      }

      // 必需的步骤2:如果上面没有成功,
      // 我们尝试用默认的ClassLoader装入它
      if (clasz == null)
        clasz = findSystemClass( name );

      // 必需的步骤3:如有必要,则装入相关的类
      if (resolve && clasz != null)
        resolveClass( clasz );

      // 把类返回给调用者
      return clasz;

    } catch( IOException ie ) {
      throw new ClassNotFoundException( ie.toString() );
    } catch( GeneralSecurityException gse ) {
      throw new ClassNotFoundException( gse.toString() );
    }
  }






Listing 2显示了一个简单的loadClass实现。代码中的大部分对所有ClassLoader对象来说都一样,但有一小部分(已通过注释标记)是特有的。在处理过程中,ClassLoader对象要用到其他几个辅助方法:

findLoadedClass:用来进行检查,以便确认被请求的类当前还不存在。loadClass方法应该首先调用它。
defineClass:获得原始类文件字节码数据之后,调用defineClass把它转换成一个Class对象。任何loadClass实现都必须调用这个方法。
findSystemClass:提供默认ClassLoader的支持。如果用来寻找类的定制方法不能找到指定的类(或者有意地不用定制方法),则可以调用该方法尝试默认的装入方式。这是很有用的,特别是从普通的JAR文件装入标准Java类时。
resolveClass:当JVM想要装入的不仅包括指定的类,而且还包括该类引用的所有其他类时,它会把loadClass的resolve参数设置成true。这时,我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass。



三、加密、解密
Java加密扩展即Java Cryptography Extension,简称JCE。它是Sun的加密服务软件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一种扩展。

JCE 没有规定具体的加密算法,但提供了一个框架,加密算法的具体实现可以作为服务提供者加入。除了JCE框架之外,JCE软件包还包含了SunJCE服务提供 者,其中包括许多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。

为简单计,在本文中我们将用DES算法加密和解密字节码。下面是用JCE加密和解密数据必须遵循的基本步骤:

步骤1:生成一个安全密匙。在加密或解密任何数据之前需要有一个密匙。密匙是随同被加密的应用一起发布的一小段数据,Listing 3显示了如何生成一个密匙。 【Listing 3:生成一个密匙】

  // DES算法要求有一个可信任的随机数源
  SecureRandom sr = new SecureRandom();

  // 为我们选择的DES算法生成一个KeyGenerator对象
  KeyGenerator kg = KeyGenerator.getInstance( "DES" );
  kg.init( sr );

  // 生成密匙
  SecretKey key = kg.generateKey();

  // 获取密匙数据
  byte rawKeyData[] = key.getEncoded();

  /* 接下来就可以用密匙进行加密或解密,或者把它保存
     为文件供以后使用 */
  doSomething( rawKeyData );




步骤2:加密数据。得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序(见Listing 4)。 【Listing 4:用密匙加密原始数据】

    // DES算法要求有一个可信任的随机数源
    SecureRandom sr = new SecureRandom();

    byte rawKeyData[] = /* 用某种方法获得密匙数据 */;

    // 从原始密匙数据创建DESKeySpec对象
    DESKeySpec dks = new DESKeySpec( rawKeyData );

    // 创建一个密匙工厂,然后用它把DESKeySpec转换成
    // 一个SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // Cipher对象实际完成加密操作
    Cipher cipher = Cipher.getInstance( "DES" );

    // 用密匙初始化Cipher对象
    cipher.init( Cipher.ENCRYPT_MODE, key, sr );

    // 现在,获取数据并加密
    byte data[] = /* 用某种方法获取数据 */

    // 正式执行加密操作
    byte encryptedData[] = cipher.doFinal( data );

    // 进一步处理加密后的数据
    doSomething( encryptedData );



步骤3:解密数据。运行经过加密的应用时,ClassLoader分析并解密类文件。操作步骤如Listing 5所示。 【Listing 5:用密匙解密数据】

    // DES算法要求有一个可信任的随机数源
    SecureRandom sr = new SecureRandom();

    byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;

    // 从原始密匙数据创建一个DESKeySpec对象
    DESKeySpec dks = new DESKeySpec( rawKeyData );

    // 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
    // 一个SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // Cipher对象实际完成解密操作
    Cipher cipher = Cipher.getInstance( "DES" );

    // 用密匙初始化Cipher对象
    cipher.init( Cipher.DECRYPT_MODE, key, sr );

    // 现在,获取数据并解密
    byte encryptedData[] = /* 获得经过加密的数据 */

    // 正式执行解密操作
    byte decryptedData[] = cipher.doFinal( encryptedData );

    // 进一步处理解密后的数据
    doSomething( decryptedData );



四、应用实例
前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:

步骤1:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。
步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个文件: % java GenerateKey key.data



步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类: % java EncryptClasses key.data App.class Foo.class Bar.class


该命令把每一个.class文件替换成它们各自的加密版本。
步骤4:运行经过加密的应用。用户通过一个DecryptStart程序运行经过加密的应用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,启动被加密应用的程序】

import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class DecryptStart extends ClassLoader
{
  // 这些对象在构造函数中设置,
  // 以后loadClass()方法将利用它们解密类
  private SecretKey key;
  private Cipher cipher;

  // 构造函数:设置解密所需要的对象
  public DecryptStart( SecretKey key ) throws GeneralSecurityException,
      IOException {
    this.key = key;

    String algorithm = "DES";
    SecureRandom sr = new SecureRandom();
    System.err.println( "[DecryptStart: creating cipher]" );
    cipher = Cipher.getInstance( algorithm );
    cipher.init( Cipher.DECRYPT_MODE, key, sr );
  }

  // main过程:我们要在这里读入密匙,创建DecryptStart的
  // 实例,它就是我们的定制ClassLoader。
  // 设置好ClassLoader以后,我们用它装入应用实例,
  // 最后,我们通过Java Reflection API调用应用实例的main方法
  static public void main( String args[] ) throws Exception {
    String keyFilename = args[0];
    String appName = args[1];

     // 这些是传递给应用本身的参数
    String realArgs[] = new String[args.length-2];
    System.arraycopy( args, 2, realArgs, 0, args.length-2 );

    // 读取密匙
    System.err.println( "[DecryptStart: reading key]" );
    byte rawKey[] = Util.readFile( keyFilename );
    DESKeySpec dks = new DESKeySpec( rawKey );
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // 创建解密的ClassLoader
    DecryptStart dr = new DecryptStart( key );

    // 创建应用主类的一个实例
    // 通过ClassLoader装入它
    System.err.println( "[DecryptStart: loading "+appName+"]" );
    Class clasz = dr.loadClass( appName );

    // 最后,通过Reflection API调用应用实例
    // 的main()方法

    // 获取一个对main()的引用
    String proto[] = new String[1];
    Class mainArgs[] = { (new String[1]).getClass() };
    Method main = clasz.getMethod( "main", mainArgs );

    // 创建一个包含main()方法参数的数组
    Object argsArray[] = { realArgs };
    System.err.println( "[DecryptStart: running "+appName+".main()]" );

    // 调用main()
    main.invoke( null, argsArray );
  }

  public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {
    try {
      // 我们要创建的Class对象
      Class clasz = null;

      // 必需的步骤1:如果类已经在系统缓冲之中
      // 我们不必再次装入它
      clasz = findLoadedClass( name );

      if (clasz != null)
        return clasz;

      // 下面是定制部分
      try {
        // 读取经过加密的类文件
        byte classData[] = Util.readFile( name+".class" );

        if (classData != null) {
          // 解密...
          byte decryptedClassData[] = cipher.doFinal( classData );

          // ... 再把它转换成一个类
          clasz = defineClass( name, decryptedClassData,
            0, decryptedClassData.length );
          System.err.println( "[DecryptStart: decrypting class "+name+"]" );
        }
      } catch( FileNotFoundException fnfe ) {
      }

      // 必需的步骤2:如果上面没有成功
      // 我们尝试用默认的ClassLoader装入它
      if (clasz == null)
        clasz = findSystemClass( name );

      // 必需的步骤3:如有必要,则装入相关的类
      if (resolve && clasz != null)
        resolveClass( clasz );

      // 把类返回给调用者
      return clasz;
    } catch( IOException ie ) {
      throw new ClassNotFoundException( ie.toString()
);
    } catch( GeneralSecurityException gse ) {
      throw new ClassNotFoundException( gse.toString()
);
    }
  }
}


对于未经加密的应用,正常执行方式如下: % java App arg0 arg1 arg2


对于经过加密的应用,则相应的运行方式为: % java DecryptStart key.data App arg0 arg1 arg2



DecryptStart 有两个目的。一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同时,DecryptStart还包含一个 main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、Foo.java和Bar.java内。 Util.java是一个文件I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。

五、注意事项
我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。

虽 然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办 法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。

另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。Java没有为此提供真正有效的补救措施。

不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。

参考资源

在运行时刻更新功能模块。介绍了一个利用类库加载器ClassLoader 实现在运行时刻更新部分功能模块的Java程序,并将其与C/C++中实现同样功能的动态链接库方案进行了比较。
Java 技巧 105:利用 JWhich 掌握类路径。展示一个简单的工具,它可以清楚地确定类装载器从类路径中载入了什么 Java 类。
要了解更多的 Java 安全信息,请阅读 java.sun.com的 Java Security API 页。
如何封锁您的(或打开别人的) Java 代码。Java 代码反编译和模糊处理的指南。
使您的软件运行起来:摆弄数字。真正安全的软件需要精确的随机数生成器。
下载本文代码:EncryptedJavaClass_code.zip


关于作者
俞良松,软件工程师,独立顾问和自由撰稿人。最初从事PB和Oracle开发,现主要兴趣在于Internet开发。您可以通过 javaman@163.net 和我联系。

posted @ 2010-04-01 22:52 梧桐夜雨 阅读(1004) | 评论 (0)编辑 收藏

     摘要:   阅读全文

posted @ 2010-04-01 22:29 梧桐夜雨 阅读(208) | 评论 (0)编辑 收藏


2010年9月21日

     摘要: http://www.pinlady.net/PluginDetect/JavaDetect.htm Java Plugin Detector [This detector can perform both OTF and NOTF Java detection] Java (using <applet> and/or <object> tag) inst...  阅读全文

posted @ 2010-09-21 00:16 梧桐夜雨 阅读(562) | 评论 (0)编辑 收藏


2010年8月12日

Our primary use case also has the user switching between online and offline, so these bugs are a big problem.

I threw the full weight of my three bug votes behind bug 6931553 ;)

The fix for bug 6911785 does seem to prevent the endless "hang" on launch (with an active TCP/IP connection but no internet access), but there are still lots of problems:
- launching without internet access can take 20s - 2 minutes!
- launching offline results in the non-sensical "this application has requested to go online" dialog
- the JNLPClassLoader ends up doing DNS lookups which occasionally cause long delays during execution

It's really ugly. We have unhappy customers. I have no idea how to get these bugs fixed. Any suggestions?
http://forums.sun.com/thread.jspa?threadID=5426321&start=0&tstart=0

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6931553

posted @ 2010-08-12 23:49 梧桐夜雨 阅读(199) | 评论 (0)编辑 收藏


2010年5月6日

     摘要:   阅读全文

posted @ 2010-05-06 23:52 梧桐夜雨 阅读(947) | 评论 (0)编辑 收藏


2010年4月1日

了解ClassLoader
1,   什么是 ClassLoader?
    Java 程序并不是一个可执行文件,是需要的时候,才把装载到 JVM中。ClassLoader 做的工作就是 JVM 中将类装入内存。 而且,Java ClassLoader 就是用 Java 语言编写的。这意味着您可以创建自己的 ClassLoader
    ClassLoader 的基本目标是对类的请求提供服务。当 JVM 需要使用类时,它根据名称向 ClassLoader 请求这个类,然后 ClassLoader 试图返回一个表示这个类的 Class 对象。 通过覆盖对应于这个过程不同阶段的方法,可以创建定制的 ClassLoader。
2, 一些重要的方法
A)  方法 loadClass
        ClassLoader.loadClass() 是 ClassLoader 的入口点。该方法的定义如下:
        Class loadClass( String name, boolean resolve );
         name  JVM 需要的类的名称,如 Foo 或 java.lang.Object。
         resolve 参数告诉方法是否需要解析类。在准备执行类之前,应考虑类解析。并不总是需要解析。如果 JVM 只需要知道该类是否存在或找出该类的超类,那么就不需要解析。
    
    cool.gif  方法 defineClass
       defineClass 方法是 ClassLoader 的主要诀窍。该方法接受由原始字节组成的数组并把它转换成 Class 对象。原始数组包含如从文件系统或网络装入的数据。defineClass 管理 JVM 的许多复杂、神秘和倚赖于实现的方面 -- 它把字节码分析成运行时数据结构、校验有效性等等。不必担心,您无需亲自编写它。事实上,即使您想要这么做也不能覆盖它,因为它已被标记成final的。

    C)  方法 findSystemClass
       findSystemClass 方法从本地文件系统装入文件。它在本地文件系统中寻找类文件,如果存在,就使用 defineClass 将原始字节转换成 Class 对象,以将该文件转换成类。当运行 Java 应用程序时,这是 JVM 正常装入类的缺省机制。(Java 2 中 ClassLoader 的变动提供了关于 Java 版本 1.2 这个过程变动的详细信息。) 对于定制的 ClassLoader,只有在尝试其它方法装入类之后,再使用 findSystemClass。原因很简单:ClassLoader 是负责执行装入类的特殊步骤,不是负责所有类。例如,即使 ClassLoader 从远程的 Web 站点装入了某些类,仍然需要在本地机器上装入大量的基本 Java 库。而这些类不是我们所关心的,所以要 JVM 以缺省方式装入它们:从本地文件系统。这就是 findSystemClass 的用途。

     D) 方法 resolveClass
   正如前面所提到的,可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的 loadClass 时,可以调用 resolveClass,这取决于 loadClass 的 resolve 参数的值。


   E) 方法 findLoadedClass
      findLoadedClass 充当一个缓存:当请求 loadClass 装入类时,它调用该方法来查看 ClassLoader 是否已装入这个类,这样可以避免重新装入已存在类所造成的麻烦。应首先调用该方法。

3, 怎么组装这些方法
  1) 调用 findLoadedClass 来查看是否存在已装入的类。
  2) 如果没有,那么采用那种特殊的神奇方式来获取原始字节。
  3) 如果已有原始字节,调用 defineClass 将它们转换成 Class 对象。
  4) 如果没有原始字节,然后调用 findSystemClass 查看是否从本地文件系统获取类。
  5) 如果 resolve 参数是 true,那么调用 resolveClass 解析 Class 对象。
  6) 如果还没有类,返回 ClassNotFoundException。

4,Java 2 中 ClassLoader 的变动
1)loadClass 的缺省实现
   定制编写的 loadClass 方法一般尝试几种方式来装入所请求的类,如果您编写许多类,会发现一次次地在相同的、很复杂的方法上编写变量。 在 Java 1.2 中 loadClass 的实现嵌入了大多数查找类的一般方法,并使您通过覆盖 findClass 方法来定制它,在适当的时候 findClass 会调用 loadClass。 这种方式的好处是您可能不一定要覆盖 loadClass;只要覆盖 findClass 就行了,这减少了工作量。

2)新方法:findClass
     loadClass 的缺省实现调用这个新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代码,而无需要复制其它代码(例如,当专门的方法失败时,调用系统 ClassLoader)。

3) 新方法:getSystemClassLoader
     如果覆盖 findClass 或 loadClass,getSystemClassLoader 使您能以实际 ClassLoader 对象来访问系统 ClassLoader(而不是固定的从 findSystemClass 调用它)。
 
4) 新方法:getParent 
    为了将类请求委托给父代 ClassLoader,这个新方法允许 ClassLoader 获取它的父代 ClassLoader。当使用特殊方法,定制的 ClassLoader 不能找到类时,可以使用这种方法。
父代 ClassLoader 被定义成创建该 ClassLoader 所包含代码的对象的 ClassLoader。
运用加密技术保护Java源代码
QUOTE
Java程序的源代码很容易被别人偷看。只要有一个反编译器,任何人都可以分析别人的代码。本文讨论如何在不修改原有程序的情况下,通过加密技术保护源代码。
一、为什么要加密?
对 于传统的C或C++之类的语言来说,要在Web上保护源代码是很容易的,只要不发布它就可以。遗憾的是,Java程序的源代码很容易被别人偷看。只要有一 个反编译器,任何人都可以分析别人的代码。Java的灵活性使得源代码很容易被窃取,但与此同时,它也使通过加密保护代码变得相对容易,我们唯一需要了解 的就是Java的ClassLoader对象。当然,在加密过程中,有关Java Cryptography Extension(JCE)的知识也是必不可少的。

有几种技术可以“模糊”Java类文件,使得反编译器处理类文件的效果大打折扣。然而,修改反编译器使之能够处理这些经过模糊处理的类文件并不是什么难事,所以不能简单地依赖模糊技术来保证源代码的安全。

我们可以用流行的加密工具加密应用,比如PGP(Pretty Good Privacy)或GPG(GNU Privacy Guard)。这时,最终用户在运行应用之前必须先进行解密。但解密之后,最终用户就有了一份不加密的类文件,这和事先不进行加密没有什么差别。

Java 运行时装入字节码的机制隐含地意味着可以对字节码进行修改。JVM每次装入类文件时都需要一个称为ClassLoader的对象,这个对象负责把新的类装 入正在运行的JVM。JVM给ClassLoader一个包含了待装入类(比如java.lang.Object)名字的字符串,然后由 ClassLoader负责找到类文件,装入原始数据,并把它转换成一个Class对象。

我们可以通过定制ClassLoader,在类文件执行之前修改它。这种技术的应用非常广泛——在这里,它的用途是在类文件装入之时进行解密,因此可以看成是一种即时解密器。由于解密后的字节码文件永远不会保存到文件系统,所以窃密者很难得到解密后的代码。

由于把原始字节码转换成Class对象的过程完全由系统负责,所以创建定制ClassLoader对象其实并不困难,只需先获得原始数据,接着就可以进行包含解密在内的任何转换。

Java 2在一定程度上简化了定制ClassLoader的构建。在Java 2中,loadClass的缺省实现仍旧负责处理所有必需的步骤,但为了顾及各种定制的类装入过程,它还调用一个新的findClass方法。

这为我们编写定制的ClassLoader提供了一条捷径,减少了麻烦:只需覆盖findClass,而不是覆盖loadClass。这种方法避免了重复所有装入器必需执行的公共步骤,因为这一切由loadClass负责。

不 过,本文的定制ClassLoader并不使用这种方法。原因很简单。如果由默认的ClassLoader先寻找经过加密的类文件,它可以找到;但由于类 文件已经加密,所以它不会认可这个类文件,装入过程将失败。因此,我们必须自己实现loadClass,稍微增加了一些工作量。

二、定制类装入器
每一个运行着的JVM已经拥有一个ClassLoader。这个默认的ClassLoader根据CLASSPATH环境变量的值,在本地文件系统中寻找合适的字节码文件。

应 用定制ClassLoader要求对这个过程有较为深入的认识。我们首先必须创建一个定制ClassLoader类的实例,然后显式地要求它装入另外一个 类。这就强制JVM把该类以及所有它所需要的类关联到定制的ClassLoader。Listing 1显示了如何用定制ClassLoader装入类文件。

【Listing 1:利用定制的ClassLoader装入类文件】

  // 首先创建一个ClassLoader对象
  ClassLoader myClassLoader = new myClassLoader();

  // 利用定制ClassLoader对象装入类文件
  // 并把它转换成Class对象
  Class myClass = myClassLoader.loadClass( "mypackage.MyClass" );

  // 最后,创建该类的一个实例
  Object newInstance = myClass.newInstance();

  // 注意,MyClass所需要的所有其他类,都将通过
  // 定制的ClassLoader自动装入






如前所述,定制ClassLoader只需先获取类文件的数据,然后把字节码传递给运行时系统,由后者完成余下的任务。

ClassLoader 有几个重要的方法。创建定制的ClassLoader时,我们只需覆盖其中的一个,即loadClass,提供获取原始类文件数据的代码。这个方法有两个 参数:类的名字,以及一个表示JVM是否要求解析类名字的标记(即是否同时装入有依赖关系的类)。如果这个标记是true,我们只需在返回JVM之前调用 resolveClass。

【Listing 2:ClassLoader.loadClass()的一个简单实现】

      public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {
    try {
      // 我们要创建的Class对象
       Class clasz = null;

      // 必需的步骤1:如果类已经在系统缓冲之中,
      // 我们不必再次装入它
      clasz = findLoadedClass( name );

      if (clasz != null)
        return clasz;

      // 下面是定制部分
      byte classData[] = /* 通过某种方法获取字节码数据 */;
      if (classData != null) {
        // 成功读取字节码数据,现在把它转换成一个Class对象
        clasz = defineClass( name, classData, 0, classData.length );
      }

      // 必需的步骤2:如果上面没有成功,
      // 我们尝试用默认的ClassLoader装入它
      if (clasz == null)
        clasz = findSystemClass( name );

      // 必需的步骤3:如有必要,则装入相关的类
      if (resolve && clasz != null)
        resolveClass( clasz );

      // 把类返回给调用者
      return clasz;

    } catch( IOException ie ) {
      throw new ClassNotFoundException( ie.toString() );
    } catch( GeneralSecurityException gse ) {
      throw new ClassNotFoundException( gse.toString() );
    }
  }






Listing 2显示了一个简单的loadClass实现。代码中的大部分对所有ClassLoader对象来说都一样,但有一小部分(已通过注释标记)是特有的。在处理过程中,ClassLoader对象要用到其他几个辅助方法:

findLoadedClass:用来进行检查,以便确认被请求的类当前还不存在。loadClass方法应该首先调用它。
defineClass:获得原始类文件字节码数据之后,调用defineClass把它转换成一个Class对象。任何loadClass实现都必须调用这个方法。
findSystemClass:提供默认ClassLoader的支持。如果用来寻找类的定制方法不能找到指定的类(或者有意地不用定制方法),则可以调用该方法尝试默认的装入方式。这是很有用的,特别是从普通的JAR文件装入标准Java类时。
resolveClass:当JVM想要装入的不仅包括指定的类,而且还包括该类引用的所有其他类时,它会把loadClass的resolve参数设置成true。这时,我们必须在返回刚刚装入的Class对象给调用者之前调用resolveClass。



三、加密、解密
Java加密扩展即Java Cryptography Extension,简称JCE。它是Sun的加密服务软件,包含了加密和密匙生成功能。JCE是JCA(Java Cryptography Architecture)的一种扩展。

JCE 没有规定具体的加密算法,但提供了一个框架,加密算法的具体实现可以作为服务提供者加入。除了JCE框架之外,JCE软件包还包含了SunJCE服务提供 者,其中包括许多有用的加密算法,比如DES(Data Encryption Standard)和Blowfish。

为简单计,在本文中我们将用DES算法加密和解密字节码。下面是用JCE加密和解密数据必须遵循的基本步骤:

步骤1:生成一个安全密匙。在加密或解密任何数据之前需要有一个密匙。密匙是随同被加密的应用一起发布的一小段数据,Listing 3显示了如何生成一个密匙。 【Listing 3:生成一个密匙】

  // DES算法要求有一个可信任的随机数源
  SecureRandom sr = new SecureRandom();

  // 为我们选择的DES算法生成一个KeyGenerator对象
  KeyGenerator kg = KeyGenerator.getInstance( "DES" );
  kg.init( sr );

  // 生成密匙
  SecretKey key = kg.generateKey();

  // 获取密匙数据
  byte rawKeyData[] = key.getEncoded();

  /* 接下来就可以用密匙进行加密或解密,或者把它保存
     为文件供以后使用 */
  doSomething( rawKeyData );




步骤2:加密数据。得到密匙之后,接下来就可以用它加密数据。除了解密的ClassLoader之外,一般还要有一个加密待发布应用的独立程序(见Listing 4)。 【Listing 4:用密匙加密原始数据】

    // DES算法要求有一个可信任的随机数源
    SecureRandom sr = new SecureRandom();

    byte rawKeyData[] = /* 用某种方法获得密匙数据 */;

    // 从原始密匙数据创建DESKeySpec对象
    DESKeySpec dks = new DESKeySpec( rawKeyData );

    // 创建一个密匙工厂,然后用它把DESKeySpec转换成
    // 一个SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // Cipher对象实际完成加密操作
    Cipher cipher = Cipher.getInstance( "DES" );

    // 用密匙初始化Cipher对象
    cipher.init( Cipher.ENCRYPT_MODE, key, sr );

    // 现在,获取数据并加密
    byte data[] = /* 用某种方法获取数据 */

    // 正式执行加密操作
    byte encryptedData[] = cipher.doFinal( data );

    // 进一步处理加密后的数据
    doSomething( encryptedData );



步骤3:解密数据。运行经过加密的应用时,ClassLoader分析并解密类文件。操作步骤如Listing 5所示。 【Listing 5:用密匙解密数据】

    // DES算法要求有一个可信任的随机数源
    SecureRandom sr = new SecureRandom();

    byte rawKeyData[] = /* 用某种方法获取原始密匙数据 */;

    // 从原始密匙数据创建一个DESKeySpec对象
    DESKeySpec dks = new DESKeySpec( rawKeyData );

    // 创建一个密匙工厂,然后用它把DESKeySpec对象转换成
    // 一个SecretKey对象
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // Cipher对象实际完成解密操作
    Cipher cipher = Cipher.getInstance( "DES" );

    // 用密匙初始化Cipher对象
    cipher.init( Cipher.DECRYPT_MODE, key, sr );

    // 现在,获取数据并解密
    byte encryptedData[] = /* 获得经过加密的数据 */

    // 正式执行解密操作
    byte decryptedData[] = cipher.doFinal( encryptedData );

    // 进一步处理解密后的数据
    doSomething( decryptedData );



四、应用实例
前面介绍了如何加密和解密数据。要部署一个经过加密的应用,步骤如下:

步骤1:创建应用。我们的例子包含一个App主类,两个辅助类(分别称为Foo和Bar)。这个应用没有什么实际功用,但只要我们能够加密这个应用,加密其他应用也就不在话下。
步骤2:生成一个安全密匙。在命令行,利用GenerateKey工具(参见GenerateKey.java)把密匙写入一个文件: % java GenerateKey key.data



步骤3:加密应用。在命令行,利用EncryptClasses工具(参见EncryptClasses.java)加密应用的类: % java EncryptClasses key.data App.class Foo.class Bar.class


该命令把每一个.class文件替换成它们各自的加密版本。
步骤4:运行经过加密的应用。用户通过一个DecryptStart程序运行经过加密的应用。DecryptStart程序如Listing 6所示。 【Listing 6:DecryptStart.java,启动被加密应用的程序】

import java.io.*;
import java.security.*;
import java.lang.reflect.*;
import javax.crypto.*;
import javax.crypto.spec.*;

public class DecryptStart extends ClassLoader
{
  // 这些对象在构造函数中设置,
  // 以后loadClass()方法将利用它们解密类
  private SecretKey key;
  private Cipher cipher;

  // 构造函数:设置解密所需要的对象
  public DecryptStart( SecretKey key ) throws GeneralSecurityException,
      IOException {
    this.key = key;

    String algorithm = "DES";
    SecureRandom sr = new SecureRandom();
    System.err.println( "[DecryptStart: creating cipher]" );
    cipher = Cipher.getInstance( algorithm );
    cipher.init( Cipher.DECRYPT_MODE, key, sr );
  }

  // main过程:我们要在这里读入密匙,创建DecryptStart的
  // 实例,它就是我们的定制ClassLoader。
  // 设置好ClassLoader以后,我们用它装入应用实例,
  // 最后,我们通过Java Reflection API调用应用实例的main方法
  static public void main( String args[] ) throws Exception {
    String keyFilename = args[0];
    String appName = args[1];

     // 这些是传递给应用本身的参数
    String realArgs[] = new String[args.length-2];
    System.arraycopy( args, 2, realArgs, 0, args.length-2 );

    // 读取密匙
    System.err.println( "[DecryptStart: reading key]" );
    byte rawKey[] = Util.readFile( keyFilename );
    DESKeySpec dks = new DESKeySpec( rawKey );
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance( "DES" );
    SecretKey key = keyFactory.generateSecret( dks );

    // 创建解密的ClassLoader
    DecryptStart dr = new DecryptStart( key );

    // 创建应用主类的一个实例
    // 通过ClassLoader装入它
    System.err.println( "[DecryptStart: loading "+appName+"]" );
    Class clasz = dr.loadClass( appName );

    // 最后,通过Reflection API调用应用实例
    // 的main()方法

    // 获取一个对main()的引用
    String proto[] = new String[1];
    Class mainArgs[] = { (new String[1]).getClass() };
    Method main = clasz.getMethod( "main", mainArgs );

    // 创建一个包含main()方法参数的数组
    Object argsArray[] = { realArgs };
    System.err.println( "[DecryptStart: running "+appName+".main()]" );

    // 调用main()
    main.invoke( null, argsArray );
  }

  public Class loadClass( String name, boolean resolve )
      throws ClassNotFoundException {
    try {
      // 我们要创建的Class对象
      Class clasz = null;

      // 必需的步骤1:如果类已经在系统缓冲之中
      // 我们不必再次装入它
      clasz = findLoadedClass( name );

      if (clasz != null)
        return clasz;

      // 下面是定制部分
      try {
        // 读取经过加密的类文件
        byte classData[] = Util.readFile( name+".class" );

        if (classData != null) {
          // 解密...
          byte decryptedClassData[] = cipher.doFinal( classData );

          // ... 再把它转换成一个类
          clasz = defineClass( name, decryptedClassData,
            0, decryptedClassData.length );
          System.err.println( "[DecryptStart: decrypting class "+name+"]" );
        }
      } catch( FileNotFoundException fnfe ) {
      }

      // 必需的步骤2:如果上面没有成功
      // 我们尝试用默认的ClassLoader装入它
      if (clasz == null)
        clasz = findSystemClass( name );

      // 必需的步骤3:如有必要,则装入相关的类
      if (resolve && clasz != null)
        resolveClass( clasz );

      // 把类返回给调用者
      return clasz;
    } catch( IOException ie ) {
      throw new ClassNotFoundException( ie.toString()
);
    } catch( GeneralSecurityException gse ) {
      throw new ClassNotFoundException( gse.toString()
);
    }
  }
}


对于未经加密的应用,正常执行方式如下: % java App arg0 arg1 arg2


对于经过加密的应用,则相应的运行方式为: % java DecryptStart key.data App arg0 arg1 arg2



DecryptStart 有两个目的。一个DecryptStart的实例就是一个实施即时解密操作的定制ClassLoader;同时,DecryptStart还包含一个 main过程,它创建解密器实例并用它装入和运行应用。示例应用App的代码包含在App.java、Foo.java和Bar.java内。 Util.java是一个文件I/O工具,本文示例多处用到了它。完整的代码请从本文最后下载。

五、注意事项
我们看到,要在不修改源代码的情况下加密一个Java应用是很容易的。不过,世上没有完全安全的系统。本文的加密方式提供了一定程度的源代码保护,但对某些攻击来说它是脆弱的。

虽 然应用本身经过了加密,但启动程序DecryptStart没有加密。攻击者可以反编译启动程序并修改它,把解密后的类文件保存到磁盘。降低这种风险的办 法之一是对启动程序进行高质量的模糊处理。或者,启动程序也可以采用直接编译成机器语言的代码,使得启动程序具有传统执行文件格式的安全性。

另外还要记住的是,大多数JVM本身并不安全。狡猾的黑客可能会修改JVM,从ClassLoader之外获取解密后的代码并保存到磁盘,从而绕过本文的加密技术。Java没有为此提供真正有效的补救措施。

不过应该指出的是,所有这些可能的攻击都有一个前提,这就是攻击者可以得到密匙。如果没有密匙,应用的安全性就完全取决于加密算法的安全性。虽然这种保护代码的方法称不上十全十美,但它仍不失为一种保护知识产权和敏感用户数据的有效方案。

参考资源

在运行时刻更新功能模块。介绍了一个利用类库加载器ClassLoader 实现在运行时刻更新部分功能模块的Java程序,并将其与C/C++中实现同样功能的动态链接库方案进行了比较。
Java 技巧 105:利用 JWhich 掌握类路径。展示一个简单的工具,它可以清楚地确定类装载器从类路径中载入了什么 Java 类。
要了解更多的 Java 安全信息,请阅读 java.sun.com的 Java Security API 页。
如何封锁您的(或打开别人的) Java 代码。Java 代码反编译和模糊处理的指南。
使您的软件运行起来:摆弄数字。真正安全的软件需要精确的随机数生成器。
下载本文代码:EncryptedJavaClass_code.zip


关于作者
俞良松,软件工程师,独立顾问和自由撰稿人。最初从事PB和Oracle开发,现主要兴趣在于Internet开发。您可以通过 javaman@163.net 和我联系。

posted @ 2010-04-01 22:52 梧桐夜雨 阅读(1004) | 评论 (0)编辑 收藏

     摘要:   阅读全文

posted @ 2010-04-01 22:29 梧桐夜雨 阅读(208) | 评论 (0)编辑 收藏


2010年1月3日

     摘要:     级别: 中级 ...  阅读全文

posted @ 2010-01-03 22:25 梧桐夜雨 阅读(230) | 评论 (0)编辑 收藏

1.      代理模式

代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式一般涉及到的角色有:

抽象角色:声明真实对象和代理对象的共同接口;

代理角色:代理对象角色内部含有对真实对象的引用,从而可以操作真实对象,同时代理对象提供与真实对象相同的接口以便在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。

真实角色:代理角色所代表的真实对象,是我们最终要引用的对象。(参见文献1)

以下以《Java与模式》中的示例为例:

抽象角色:

abstract public class Subject

{

    abstract public void request();

}

真实角色:实现了Subjectrequest()方法。

public class RealSubject extends Subject

{

       public RealSubject()

       {

       }

      

       public void request()

       {

              System.out.println("From real subject.");

       }

}

代理角色:

public class ProxySubject extends Subject

{

    private RealSubject realSubject;  //以真实角色作为代理角色的属性

      

       public ProxySubject()

       {

       }

 

       public void request()  //该方法封装了真实对象的request方法

       {

        preRequest(); 

              if( realSubject == null )

        {

                     realSubject = new RealSubject();

              }

        realSubject.request();  //此处执行真实对象的request方法

        postRequest();

       }

 

    private void preRequest()

    {

        //something you want to do before requesting

    }

 

    private void postRequest()

    {

        //something you want to do after requesting

    }

}

客户端调用:

Subject sub=new ProxySubject();

Sub.request();

       由以上代码可以看出,客户实际需要调用的是RealSubject类的request()方法,现在用ProxySubject来代理RealSubject类,同样达到目的,同时还封装了其他方法(preRequest(),postRequest()),可以处理一些其他问题。

       另外,如果要按照上述的方法使用代理模式,那么真实角色必须是事先已经存在的,并将其作为代理对象的内部属性。但是实际使用时,一个真实角色必须对应一个代理角色,如果大量使用会导致类的急剧膨胀;此外,如果事先并不知道真实角色,该如何使用代理呢?这个问题可以通过Java的动态代理类来解决。

 

2.动态代理类

       Java动态代理类位于Java.lang.reflect包下,一般主要涉及到以下两个类:

(1). Interface InvocationHandler:该接口中仅定义了一个方法Objectinvoke(Object obj,Method method, Object[] args)。在实际使用时,第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request()args为该方法的参数数组。这个抽象方法在代理类中动态实现。


(2).Proxy
:该类即为动态代理类,作用类似于上例中的ProxySubject,其中主要包含以下内容:

Protected Proxy(InvocationHandler h):构造函数,估计用于给内部的h赋值。

Static Class getProxyClass (ClassLoader loader, Class[] interfaces):获得一个代理类,其中loader是类装载器,interfaces是真实类所拥有的全部接口的数组。

Static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用(可使用被代理类的在Subject接口中声明过的方法)


      
所谓Dynamic Proxy是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。你当然可以把该class的实例当作这些interface中的任何一个来用。当然啦,这个Dynamic Proxy其实就是一个Proxy,它不会替你作实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。(参见文献3)

    在使用动态代理类时,我们必须实现InvocationHandler接口,以第一节中的示例为例:

抽象角色(之前是抽象类,此处应改为接口)

public interface Subject

{

    abstract public void request();

}

具体角色RealSubject:同上;

 

代理角色:

import java.lang.reflect.Method;

import java.lang.reflect.InvocationHandler;

 

public class DynamicSubject implements InvocationHandler {

  private Object sub;

 

  public DynamicSubject() {

  }

 

  public DynamicSubject(Object obj) {

    sub = obj;

  }

 

 

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

    System.out.println("before calling " + method);

 

    method.invoke(sub,args);

 

    System.out.println("after calling " + method);

    return null;

  }

 

}

 

       该代理类的内部属性为Object类,实际使用时通过该类的构造函数DynamicSubject(Object obj)对其赋值;此外,在该类还实现了invoke方法,该方法中的

method.invoke(sub,args);

其实就是调用被代理对象的将要被执行的方法,方法参数sub是实际的被代理对象,args为执行被代理对象相应操作所需的参数。通过动态代理类,我们可以在调用之前或之后执行一些相关操作。

客户端

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Proxy;

import java.lang.reflect.Constructor;

import java.lang.reflect.Method;

 

public class Client

{

 

    static public void main(String[] args) throws Throwable

       {

      RealSubject rs = new RealSubject();  //在这里指定被代理类

      InvocationHandler ds = new DynamicSubject(rs);  //初始化代理类

         Class cls = rs.getClass();

      //以下是分解步骤

      /*

      Class c = Proxy.getProxyClass(cls.getClassLoader(),cls.getInterfaces()) ;

      Constructor ct=c.getConstructor(new Class[]{InvocationHandler.class});

      Subject subject =(Subject) ct.newInstance(new Object[]{ds});

     */

     //以下是一次性生成

      Subject subject = (Subject) Proxy.newProxyInstance(cls.getClassLoader(),

                                 cls.getInterfaces(),ds );


      subject.request();

}

       通过这种方式,被代理的对象(RealSubject)可以在运行时动态改变,需要控制的接口(Subject接口)可以在运行时改变,控制的方式(DynamicSubject)也可以动态改变,从而实现了非常灵活的动态代理关系(参见文献2)

 

 

参考文献:

1.      阎宏,《Java 与模式》

2.          透明,《动态代理的前世今生》

3.   Forest Hou,《Dynamic Proxy Java RMI 中的应用》

posted @ 2010-01-03 22:16 梧桐夜雨 阅读(276) | 评论 (0)编辑 收藏

Java SE1.5中, 增加了一个新的特性:泛型(日本语中的总称型)。何谓泛型呢?通俗的说,就是泛泛的指定对象所操作的类型,而不像常规方式一样使用某种固定的类型去指定。 泛型的本质就是将所操作的数据类型参数化,也就是说,该数据类型被指定为一个参数。这种参数类型可以使用在类、接口以及方法定义中。

 

一、为什么使用泛型呢?

     在以往的J2SE中,没有泛型的情况下,通常是使用Object类型来进行多种类型数据的操作。这个时候操作最多的就是针对该Object进行数据的强制转换,而这种转换是基于开发者对该数据类型明确的情况下进行的(比如将Object型转换为String型)。倘若类型不一致,编译器在编译过程中不会报错,但在运行时会出错。

    使用泛型的好处在于,它在编译的时候进行类型安全检查,并且在运行时所有的转换都是强制的,隐式的,大大提高了代码的重用率。

 

二、  泛型的简单例子:

首先,我们来看看下面两个普通的class定义

public class getString {

private String myStr;

 

public String getStr() {

    return myStr;

}

 

public void setStr(str) {

    myStr = str;

}

}

 

public class getDouble {

private Double myDou;

 

public Double getDou() {

    return myDou;

}

 

public void setDou(dou) {

    myDou = dou;

}

          }

 

    这两个class除了所操作的数据类型不一致,其他机能都是相同的。现在,我们可以使用泛型来将上面两个class合并为一个,从而提高代码利用率,减少代码量。

        public class getObj<T> {

            private T myObj ;

           

            public T getObj() {

                return myObj;

            }

 

            public void setObj<T obj> {

                 myObj = obj;

              }

        }

 

    那么,使用了泛型后,如何生成这个class的实例来进行操作呢?请看下面的代码:

        getObj<String> strObj = new getObj<String>();

        strObj.setObj(Hello Nissay);

        System.out.println(strObj.getObj());

 

         getObj<Double> douObj = new getObj<Double>();

         douObj.setObj(new Double(116023));

         System.out.println(douObj.getObj());

 

三、例子分析

现在我们来分析上面那段蓝色字体的代码:

1<T>是泛型的标记,当然可以使用别的名字,比如。使用<T>声明一个泛型的引用,从而可以在class、方法及接口中使用它进行数据定义,参数传递。

2<T>在声明的时候相当于一个有意义的数据类型,编译过程中不会发生错误;在实例化时,将其用一个具体的数据类型进行替代,从而就可以满足不用需求。

 

四、泛型的规则和限制

通过上述的例子,我们简单理解了泛型的含义。在使用泛型时,请注意其使用规则和限制,如下:

1、泛型的参数类型只能是类(class)类型,而不能是简单类型。

      比如,<int>是不可使用的。

2、可以声明多个泛型参数类型,比如<T, P,Q>,同时还可以嵌套泛型,例如:<List<String>>

3、泛型的参数类型可以使用extends语句,例如<T extends superclass>

4、泛型的参数类型可以使用super语句,例如< T super childclass>

5、泛型还可以使用通配符,例如<? extends ArrayList>

 

五、 扩展

1extends语句

使用extends语句将限制泛型参数的适用范围。例如:

<T extends collection> ,则表示该泛型参数的使用范围是所有实现了collection接口的calss。如果传入一个<String>则程序编译出错。

2super语句

super语句的作用与extends一样,都是限制泛型参数的适用范围。区别在于,super是限制泛型参数只能是指定该class的上层父类。

例如<T super List>,表示该泛型参数只能是ListList的上层父类。

3、通配符

使用通配符的目的是为了解决泛型参数被限制死了不能动态根据实例来确定的缺点。

举个例子:public class SampleClass < T extends S> {}

假如ABC,…Z26class都实现了S接口。我们使用时需要使用到这26class类型的泛型参数。那实例化的时候怎么办呢?依次写下

SampleClass<A> a = new SampleClass();

SampleClass<B> a = new SampleClass();

SampleClass<Z> a = new SampleClass();

这显然很冗余,还不如使用Object而不使用泛型,呵呵,是吧?

别着急,咱们使用通配符,就OK了。

SampleClass<? Extends S> sc = new SampleClass();

posted @ 2010-01-03 22:10 梧桐夜雨 阅读(298) | 评论 (0)编辑 收藏


仅列出标题  

posts - 9, comments - 0, trackbacks - 0, articles - 0

Copyright © 梧桐夜雨