现在的位置: 首页 > 综合 > 正文

JAVA反射与类加载

2013年10月06日 ⁄ 综合 ⁄ 共 6116字 ⁄ 字号 评论关闭

第一章 反射入门

Java Reflection (JAVA反射) Reflection 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,
或者说“自审”,并能直接操作程序的内部属性。例如,使用它能获得 Java 类中各成员的名称并显示出来。

看看原来有这样一个类

package com.samland.test;

public class TestC{  
  
private int id;
  
public void setId(int g){
    id
=g*200;
  }

  
private String getId(){
    
return "No."+id;
  }

  
public static void main(String[] args) {
    
//。。。
  }

  
public void abc(String[] args) throws Exception {
    String id 
= "===["+getId()+"]=="+args;
    System.out.println(id);
  }

}

当你新建一个测试类,并且执行下列代码时,你会发现它把类的方法都打印出来了:

public class Test1 {
  
public static void main(String[] args) {
    Class c 
= Class.forName("com.samland.test.TestC");
    System.out.println(c.getName());

    Method[] ms
=c.getMethods();
    
for (int i=0;i<ms.length;i++){
       Method d 
= ms[i];
      System.out.print(
"public ");
      System.out.print(d.getReturnType().getSimpleName());
      System.out.print(
" ");
      System.out.print(d.getName());
      System.out.print(
"(");
      Class[] ccc 
= d.getParameterTypes();
      
for (int j=0;i<ccc.length;i++){
        
if (j>0) System.out.print("");
        System.out.print(ccc[j].getSimpleName());
        System.out.print(
" arg"+j);
      }

      System.out.print(
")");
      Class[] eee 
= d.getExceptionTypes();
      
for (int j=0;j<eee.length;j++){
        
if (j==0) System.out.print(" throws ");
        
if (j>0) System.out.print("");
        System.out.print(eee[j].getSimpleName());
      }

      System.out.println(
";");
    }

  }

}

结果输出:
com.samland.test.TestC
public void setId(int arg0);
public void main(String[] arg0);
public void abc(String[] arg0) throws Exception;
public int hashCode();
public Class getClass();
public void wait() throws InterruptedException;
public void wait(long arg0, int arg1) throws InterruptedException;
public void wait(long arg0) throws InterruptedException;
public boolean equals(Object arg0);
public void notify();
public void notifyAll();
public String toString();
 

第二章 方法执行

根据方法的名称来执行方法

我们可以用 reflection 来做一些其它的事情,比如执行一个指定了名称的方法。
调用返回的 java.lang.reflect.Method 实例定义一种 invoke 方法,
您可以用来在正在定义的类的一个实例上调用方法。这种 invoke 方法使用两个参数,
为调用提供类实例和参数值数组。

下面的示例演示了这一操作:

public class Test2 {
  
public static void main(String[] args) {
    Class c 
= Class.forName("com.samland.test.TestC");
    
    Class partypes[] 
= new Class[1];
    partypes[
0= int.class;
    Method method1
= c.getMethod("setId", partypes);
    
    Object testinst 
= c.newInstance(); 
    Object ob1 
= method1.invoke(testinst,new Object[]{232});
    
partypes[
0= String[].class;
    Method method2
= c.getMethod("abc", partypes);
    Object ob2 
= method2.invoke(testinst,new Object[]{new String[]{"AVAA","VA"}});
  }
}

你会看到如下输入
===[No.46400]==[Ljava.lang.String;@1bab50a
这个其实是由com.samland.test.TestC的abc方法所输出。

一般的例子,在执行method的invoke方法时,都只会教你新建一个已经存在的实例,如
TestC obj = new TestC();
method1.invoke(obj,new Object[]{232});
可是,如果当我们的TestC并不在classpath的时候,这样就无法执行了,因此我这里通过使用
newInstance()来创建实例。

下面一章将会说道如何编译一个不在classpath的java源文件,并且执行它。

第三章 编译源文件

当你在程序的运行过程中,想通过动态编译一些非预先定义好的源代码,并且执行它其中的方法,
可以参考一下代码:

假设TestC.java是你刚刚通过某种手段生成的一个文本文件,里面包含了一个类的正确定义。
这个文件保存在某个临时路径下,我们可以编译它,并且输出到classpath,然后通过反射机制
执行它其中方法(详见本文第一、二章)。

import com.sun.tools.javac.Main;
public class Test3 {
  
public static void deploy(String fileName, String classpath) throws Exception{
     String[] cpargs 
= new String[] {"-d", classpath,fileName};
     
int status = Main.compile(cpargs);
     
if(status!=0){
     System.out.println(
"没有成功编译源文件!");
     }
else{
       System.out.println(status);
     }
     
//注意,不要使用Main.main()方法哦
     
//虽然main()和compile方法同样可以编译,但是main方法会执行一个系统退出命令
     
//他会终止你的程序运行!
     
//Main.main(new String[] {"-d",classpath,fileName});    
  }
  
  
public static void main(String[] args) {
    String filename 
= "D:/temp/TestC.java";
    
//假设你的项目输出路径就是位于这里:
    String classpath = "E:/project/WebContent/WEB-INF/classes/";
    
    deploy(filename,filename);
    
    Class c 
= Class.forName("com.samland.test.TestC");
    
    Class partypes[] 
= new Class[1];  
partypes[
0= String[].class;
    Method method2
= c.getMethod("abc", partypes);
    Object testinst 
= c.newInstance(); 
    Object ob2 
= method2.invoke(testinst,new Object[]{new String[]{"AVAA","VA"}});
  }
}

你会看到输出
===[No.0]==[Ljava.lang.String;@186d4c1

第四章 加载不在classpath的类

一般的例子都会教你使用
    Class c = Class.forName("com.samland.test.TestC");
来装载存在于classpath中的类,假设这个二进制名称com.samland.test.TestC
并不在你项目的classpath中,那么有没有办法执行它呢?

答案是:有!

只要你查阅jdk的帮助,你就会发现原来可以装载符合条件的二进制流。也就意味着你可
把你做好的*.class加密保存,需要用到的时候再解密然后加载到JVM中执行。

import com.sun.tools.javac.Main;
public class Test4 {
  
public static void deploy(String fileName, String classpath) throws Exception{
    
//详见第三章
  }
  
public static void main(String[] args) throws Exception {
    String filename 
= "D:/temp/TestC.java";
    
//E:路径并非项目的classpath!
    String classpath = "E:/";
    deploy(filename,classpath);
        
    String aClass 
= "E:/com/samland/test/TestC.class";

    MyClassLoader mc = new MyClassLoader();
    Class c 
= mc.defineClass(aClass);
    System.out.println(c.getName());
    Class partypes[] 
= new Class[1];  
partypes[
0= String[].class;
    Object testinst 
= c.newInstance(); 
    Method method2
= c.getMethod("abc", partypes);
    Object ob2 
= method2.invoke(testinst,new Object[]{new String[]{"AVAA","VA"}});
  }
}

同样你会看到输出(注意,代码中并没有出现“com.samland.test.TestC”)
com.samland.test.TestC
===[No.0]==[Ljava.lang.String;@186d4c1

因为上述代码使用了自定义的类加载(类装入)方法,代码如下:

package com.samland.service.util;

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader {
  
//defineClass(String name, byte[] b, int off, int len, ProtectionDomain protectionDomain) 
//使用可选的 ProtectionDomain 将一个字节数组转换为 Class 类的实例。
  public Class defineClass(String filename) throws IOException{
    
byte[] b = MyClassLoader.FileToBuffer(filename);
    
//一般的,name应该是类的二进制名称,这里为了不特指,所以注入了参数值null
    return defineClass(null,b,0,b.length);
  }
  
public static byte[] FileToBuffer(String filename) throws IOException{
    ByteArrayOutputStream sb 
= new ByteArrayOutputStream();
    InputStream fis 
= new FileInputStream(filename);
    
byte buffer[] = new byte[0xFF];
int b;
while((b = fis.read(buffer)) != -1){
  sb.write(buffer,
0,b);
}
fis.close();
byte[] ret = sb.toByteArray();
sb.close();
    
return ret;
  }
}

 

全文完。

抱歉!评论已关闭.