Java反射机制及应用(二)

上一篇文章简单说明了java反射机制及其一些基本操作,这篇文章就简单写一下它的应用。

本文地址: http://www.hoverlees.com/blog/?p=539

反射机制可以应用的地方还包括函数跳转表,脚本执行等,函数跳转表常用在多路判断上,以达到线性时间判断的效果。跳转表常用在流解析等应用上,比如说解析xml,解析脚本等,我们可以做一个长度为256的跳转表,每读入一个字节直接跳到指定处理的代码段,从而不用在判断上浪费CPU。用反射机制实现的跳转表,可以使用Method数组实现。当然,跳转表这儿,我推荐的方法是接口数组实现跳转表。例如你定义一个interface包含一个函数,然后定义一个接口数组,每个接口不同的实现即可。

本文的例子是脚本执行,为了让下面的字符串执行起来。

sget
stdout,java.lang.System,out
String
info,=========Output by commands=========
invoke
r,stdout,println,java.lang.String,info
new
t1,com.hoverlees.reflect.TestClass
invoke
can_next,t1,next
while
can_next
invoke
r,t1,doit
invoke
can_next,t1,next
endwhile

第一行是命令,第二行是参数。说明我就写在代码里了,就不多说了,直接看代码。

/**
 * Cmd.java
**/
package com.hoverlees.reflect;

public final class Cmd{
	public String command;
	public String[] params;
}
/**
*CommandExecutor.java
**/

package com.hoverlees.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Stack;

/**
 * Java反射机制使用例子,执行字符串命令流
 * @author hoverlees http://www.hoverlees.com
 * CommandExecutor 命令执行器
 */

public class CommandExecutor {
	//引擎的全局变量保存到一个HashMap里。
	private HashMap<String,Object> global;
	//while用的Stack
	private Stack<WhileBlock> whileBlock;
	public CommandExecutor(){
		global=new HashMap<String,Object>();
		whileBlock=new Stack<WhileBlock>();
	}
	/**
	 * 执行原子指令的函数,优秀的脚本引擎,应该采用跳转表的方式(内部使用Hash表或查找树实现),节约效率,PHP就是这样实现的
	 * 但在Java里,函数跳转表可以通过反射机制去实现,为了不把两个例子混到一起,这儿就用if去判断了.
	 *
	 * @param ip 程序位置,脚本引擎的话,应该由解释器提供.
	 * @param command 命令
	 * @param args 参数列表
	 * @return 新的程序当前位置(跳转时使用),不跳转时一般返回当前ip.
	 */
	public int executeCommand(int ip,String command,String[] args){
		Class c;
		int n;
		Class[] paramTypes;
		Object[] params;
		Object obj;
		Field f;
		/**
		 * 判断命令是否需要执行
		 */
		if(!whileBlock.isEmpty()){
			if(!whileBlock.peek().state) return ip; //如果在while内但while的条件不为真,此不执行此命令
		}

		//下面两个例子是对基本变量的声明
		/**
		 * executeCommand("int","a","8766")
		 * =>
		 * int a=8766;
		 */
		if(command.equals("int")){
			//这儿只是举个参数检查的例子,为了省事儿,下面的命令都不会进行参数检查。
			if(args.length!=2) throw new Error("int command must have two parameters.");
			global.put(args[0], Integer.parseInt(args[1]));
		}
		/**
		 * executeCommand("String","name","Hoverlees")
		 * =>
		 * String name="hoverlees";
		 */
		else if(command.equals("String")){
			global.put(args[0], args[1]);
		}
		//下面的例子是变量赋值
		/**
		 * executeCommand("set","name","value")
		 * =>
		 * name=value;
		 */
		else if(command.equals("set")){
			global.put(args[0], global.get(args[1]));
		}
		//下面实现get,取得对象的公有属性
		/**
		 * executeCommand("get","r","obj","pname")
		 * =>
		 * r=obj.pname;
		 */
		else if(command.equals("get")){
			obj=global.get(args[1]);
			try {
				f=obj.getClass().getDeclaredField(args[2]);
				global.put(args[0], f.get(obj));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		//下面实现sget,取得类的公有static属性.
		/**
		 * executeCommand("sget","r","java.lang.System","out")
		 * =>
		 * r=System.out
		 */
		else if(command.equals("sget")){
			c=getClassByName(args[1]);
			try {
				f=c.getDeclaredField(args[2]);
				global.put(args[0], f.get(null));
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		//下面的例子是动态创建类,分别举无参数构造和有参数构造的例子
		/**
		 * executeCommand("new","name","java.lang.String")
		 * =>
		 * Object name=new String();//无参数构造
		 *
		 * executeCommand("new","name","java.lang.Integer","int.class",varname)
		 * =>
		 * Object name=new Integer(varname);
		 */
		else if(command.equals("new")){
			n=(args.length-2)/2; //取得参数个数
			paramTypes=new Class[n];
			params=new Object[n];
			for(int i=0;i<n;i++){
				paramTypes[i]=getClassByName(args[2+i*2]); //初始化参数类型列表
				params[i]=global.get(args[3+i*2]); //初始化参数值列表
			}
			c=getClassByName(args[1]);
			try{
				Constructor ct=c.getConstructor(paramTypes); //取得对应参数的构造函数
				global.put(args[0], ct.newInstance(params));
			}catch(Exception e){
				e.printStackTrace();
				global.put(args[0], null);
			}
		}
		//下面的例子是函数调用,其实它是跟调用构造函数差不多的
		/**
		 * executeCommand("invoke","r","obj","setType","int.class","vname")
		 * =>
		 * r=obj.setType((int)vname);
		 */
		else if(command.equals("invoke")){
			n=(args.length-3)/2; //取得参数个数
			paramTypes=new Class[n];
			params=new Object[n];
			for(int i=0;i<n;i++){
				paramTypes[i]=getClassByName(args[3+i*2]); //初始化参数类型列表
				params[i]=global.get(args[4+i*2]); //初始化参数值列表
			}
			c=global.get(args[1]).getClass(); //取得对象所属的类
			try{
				Method m=c.getDeclaredMethod(args[2], paramTypes);
				global.put(args[0],m.invoke(global.get(args[1]), params));
			}catch(Exception e){
				e.printStackTrace();
			}
		}
		//下面简单实现while(可嵌套)
		/**
		 * while(vname);
		 * ...
		 * endwhile;
		 */
		else if(command.equals("while")){
			WhileBlock wb=new WhileBlock();
			wb.ip=ip;
			wb.vname=args[0];
			wb.state=global.get(wb.vname).equals(true);
			whileBlock.push(wb);
		}
		else if(command.equals("endwhile")){
			WhileBlock wb=whileBlock.peek();
			if(global.get(wb.vname).equals(true)){
				return wb.ip; //如果条件还满足,那么回到while处继续执行
			}
			else whileBlock.pop();
		}

		//我从来不送人送到西,再后面需要你自己根据需要去加喽:)
		return ip;
	}
	public void debug(String name){
		System.out.println(global.get(name));
	}
	private Class getClassByName(String name){
		Class c;
		//因为int等基本数据类型的class不能直接通过Class.forName取得(当然它们属于java.lang.Number),所以这儿可以通过判断返回。
		if(name.equals("int.class")) return int.class;
		else if(name.equals("int[].class")) return int[].class;
		//...可以在这儿添加其它基本类型
		else {
			try {
				c=Class.forName(name);
			} catch (ClassNotFoundException e) {
				System.out.println(name);
				e.printStackTrace();
				c=null;
			}
		}
		return c;
	}
	private final class WhileBlock{
		public int ip;
		public String vname;
		public boolean state;
	}
}


package com.hoverlees.reflect;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
/**
 * 测试类,测试脚本执行效果。
**/
public class TestClass {
	int n;
	public TestClass(){
		n=5;
	}
	public TestClass(int num){
		n=num;
	}
	public int getN(){
		return n;
	}
	public boolean next(){
		n--;
		return n!=-1;
	}
	public void doit(){
		System.out.println("N:"+n);
	}
	public static void main(String[] args) throws IOException{
		System.out.println("=========Output by Java code=========");
		TestClass t1=new TestClass();
		while(t1.next()){
			t1.doit();
		}

		CommandExecutor executor=new CommandExecutor();
		ArrayList<Cmd> cmds=new ArrayList<Cmd>();
		int i=0;
		//初始化命令流,这个流可以从文件初始化,也可以从你定义的字符串脚本初始化.你甚至可以写和脚本解释器,对于CommandExecutor来说不重要.
		//我这儿就随便初始化了.
		/**
			sget
			stdout,java.lang.System,out
			String
			info,=========Output by commands=========
			invoke
			r,stdout,println,java.lang.String,info
			new
			t1,com.hoverlees.reflect.TestClass
			invoke
			can_next,t1,next
			while
			can_next
			invoke
			r,t1,doit
			invoke
			can_next,t1,next
			endwhile

		 */
		String script=
			"sget\n" +
			"stdout,java.lang.System,out\n" +
			"String\n" +
			"info,=========Output by commands=========\n" +
			"invoke\n" +
			"r,stdout,println,java.lang.String,info\n" +
			"new\n" +
			"t1,com.hoverlees.reflect.TestClass\n" +
			"invoke\n" +
			"can_next,t1,next\n" +
			"while\n" +
			"can_next\n" +
			"invoke\n" +
			"r,t1,doit\n" +
			"invoke\n" +
			"can_next,t1,next\n" +
			"endwhile\n\n";
		//System.out.println("Script:\n"+script);
		BufferedReader br=new BufferedReader(new InputStreamReader(new ByteArrayInputStream(script.getBytes())));
		String cmd;
		String params;
		while((cmd=br.readLine()) != null){
			params=br.readLine();
			Cmd c=new Cmd();
			c.command=cmd;
			c.params=params.split(",");
			cmds.add(i++,c);
		}

		for(int j=0;j<i;j++){
			j=executor.executeCommand(j, cmds.get(j).command, cmds.get(j).params);
		}
	}

}

让我轻轻地告诉你:
1.访问静态函数/静态变量时,object传递null
2.访问一个类的nested类时,可使用Class.forName(“package.dir.ClassName$NestedClassName”)取得.
这些就不多说了,大家悟悟就知道了.

Join the Conversation

1 Comment

Leave a Reply to sisyphus

Your email address will not be published. Required fields are marked *