欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

程序员文章站 2022-04-19 11:27:57
菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)Java的动态编译Java的动态编译Java的动态编译是指在Java程序运行时动态的执行编译指令进而执行另一段Java代码,它是在Java6.0中引入的机制。对于Java动态编译有两种做法,一种是通过Runtime调用Javac......

菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

Java的动态编译

Java的动态编译是指在Java程序运行时动态的执行编译指令进而执行另一段Java代码,它是在Java6.0中引入的机制。
对于Java动态编译有两种做法,一种是通过Runtime调用Javac,启动新的进程进行操作,比如:

Runtime run = Runtime.getRuntime();
Process process = run.exec("javac HelloWorld.java");

另一种是通过JavaCompiler动态编译,


    public static void main(String[] args) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        int result = compiler.run(null, null, null, "D:\\HelloWorld.java");
        //这里四个参数中:
        //第一个参数为一个流对象,表示为java编译器提供的参数,如果为null表示System.in
        //第二个参数表示得到Java编译器的输出信息
        //第三个编译器代表接收编译器的错误消息
        //第四个参数为一个字符串或字符串数组,表示传入的Java源文件
        //返回值是一个int类型的值,0代表编译成功,非0代表编译失败
        //执行完成后会生成class文件
        System.out.println(result == 0 ? "编译成功" : "编译失败");
 
		//执行java 命令 , 空参数, 所在文件夹
        Process process = Runtime.getRuntime().exec("java HelloWorld");
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        String str;
        while ((str = bufferedReader.readLine()) != null) {
            System.out.println(str);
         }
    }

执行后生成的文件:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
我们也可以通过反射来运行编译好的类:

 public static void main(String[] args) throws IOException {
 	try {  
    	URL[] urls = new URL[] { new URL("file:/" + "D:/HelloWorld") };
    	//加载class文件  
    	URLClassLoader loader = new URLClassLoader(urls);  
    	// 通过反射调用此类  
    	Class clazz = loader.loadClass("HelloWorld");  
    	Method m = clazz.getMethod("main", String[].class);  
    	// 由于可变参数是jdk5.0之后才有,上面代码会编译成m.invoke(null,"aa","bb");会发生参数不匹配的问题  
    	// 因此必须加上Object 强转  
    	m.invoke(null, (Object) new String[] {});  
  
	} catch (Exception e) {  
    	e.printStackTrace();  
	}
 }

执行结果与上例相同。

通过脚本引擎执行代码

Java脚本引擎是JDK6.0之后添加的新功能,它使得Java可以通过一套固定的接口与各种脚本引擎交互,从而达到在Java平台上调用各种脚本语言的目的,是Java联通其他脚本语言的桥梁,使得Java可以把一些复杂业务交给脚本语言处理,大大提高了开发效率 。
以Javascript为例,Java脚本API可以获取脚本程序的输入,通过脚本引擎运行脚本返回运行结果。Java调用JS运用的是Rhino,它是Java编写的Javascript开源实现。通过脚本引擎的运行上下文可以在脚本和Java平台之间交换数据。调用方式如下:

ScriptEngineManager sem = new ScriptEngineManager();
ScriptEngine engine = sem.getEngineByName("javascript");

运用engine对象就可以实现Java与JS的交互。

	public static void main(String[] args) throws Exception {
		// 获得脚本引擎对象
		ScriptEngineManager sem = new ScriptEngineManager();
		ScriptEngine engine = sem.getEngineByName("javascript");

		// 定义变量,存储到引擎的上下文中
		engine.put("msg", "hello~world!");
		String str = "var user= {name:'HanQuan', age:29};";
		str += "print(user.name);";

		// 执行脚本
		engine.eval(str);
		engine.eval("msg = 'aaa'");
		System.out.println(engine.get("msg"));
		
		//定义函数
		engine.eval("function add(a,b){var sum = a + b; return sum;}");
		//执行函数
		//取得接口
		Invocable jsInvocable = (Invocable)engine;
		//执行脚本中定义的方法
		Object result = jsInvocable.invokeFunction("add", new Object[] {22,33});
		System.out.println(result);
	}

程序结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

Java字节码操作

Java动态性的实现有两种方式,一种是之前所讲的反射,另一种就是所谓的字节码操作,相对反射而言字节码操作的开销要小于反射,使得其效能高于反射机制。
字节码操作的含义就是通过程序对Java代码编译新城的class文件进行操作。Java中涉及字节码操作的类库有BCEL、ASM、CGLIB、Javassist,其中BCEL和ASM直接涉及到JVM底层的操作和指令,效率较高,CGLIB基于ASM实现,效率相对可以。而JAVAssist是一个开源的分析、编辑和创建Java字节码的类库。性能与CGLIB相似,但它的特点是使用简单。本次学习就是基于JAVAssist。

JAVAssist的简单使用

JAVAssist在使用时需要导入Javassist.jar包。下载官网
我们直接通过一改简单实例来创建一个Person类:

public static void main(String[] args) throws Exception {
	public static void main(String[] args) throws Exception {
		ClassPool pool = ClassPool.getDefault();
		CtClass personClass = pool.makeClass("eee.Person");
		//创建属性
		CtField idField = CtField.make("private int id;", personClass);
		personClass.addField(idField);
		//创建方法
		CtMethod m1 = CtMethod.make("public int getId(){return id;}", personClass);
		CtMethod m2 = CtMethod.make("public void setId(int id){this.id = id;}", personClass);
		personClass.addMethod(m1);
		personClass.addMethod(m2);
		//添加构造器
		CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType},personClass);
		constructor.setBody("{this.id = id;}");
		personClass.addConstructor(constructor);
		personClass.writeFile("c:/aaa");

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

实验结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

常用API

类操作

通过javassist可以对类进行操作:

	public static void main(String[] args) throws Exception {
		ClassPool pool = ClassPool.getDefault();
		CtClass personClass = pool.makeClass("eee.Person");
		
		byte[] bytes = personClass.toBytecode();	
		System.out.println(Arrays.toString(bytes));

		System.out.println("类名:"+personClass.getName());//获取类名
		System.out.println("简要类名:"+personClass.getSimpleName());//获取简要类名
		System.out.println("父类:"+personClass.getSuperclass());//获取父类
		System.out.println("接口:"+personClass.getInterfaces());//获取接口
	}

程序结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

方法操作

	public static void main(String[] args) throws Exception {
		ClassPool pool = ClassPool.getDefault();
		CtClass personClass = pool.makeClass("eee.Person");
		
		//创建属性的另一种方式
		CtField idField = new CtField(CtClass.intType, "id", personClass);
		idField.setModifiers(Modifier.PRIVATE);//属性修饰符为private
		personClass.addField(idField);
		
		//创建方法的另一种方式
		CtMethod method = new CtMethod(CtClass.intType, "add", 
				new CtClass[] {CtClass.intType,CtClass.intType}, personClass);
		method.setModifiers(Modifier.PUBLIC);//方法修饰符为public
		method.setBody("{System.out.println(\"I am person\");return $1+$2;}");//创建方法体,其中$1和$2分别代表第一第二个参数
		personClass.addMethod(method);
		
		//通过反射生成新的方法
		Class clazz = personClass.toClass();
		Object obj = clazz.newInstance();
		Method m = clazz.getDeclaredMethod("add", int.class, int.class);
		Object result = m.invoke(obj, 200, 300);
		System.out.println(result);
	}

程序结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

方法加强

通过javassist可以对类中已有的方法进行加强,也就是在已有方法中添加其他操作,我们首先定义一个Person类:

public class Person {
    //私有属性
    private String name;
    //公有属性
    public  Integer age;
    //无参构造
    public Person() {
    }
    //有参构造
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //私有方法
     private void method1(){
        System.err.println("method1——run");
    }
    //公有方法
    public void method2(String param){
        System.err.println("method1=2——run :"+param);
    }
    @Override
    public String toString() {
        return "Proson{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

对该类的method1进行加强:

	public static void main(String[] args) throws Exception {
		ClassPool pool = ClassPool.getDefault();
		CtClass personClass = pool.get("eee.Person");
		
		CtMethod method = personClass.getDeclaredMethod("method2", new CtClass[] {pool.get(String.class.getName())});
		method.insertBefore("System.out.println(\"方法增强了\");");
		
		//通过反射生成新的方法
		Class clazz = personClass.toClass();
		Object obj = clazz.newInstance();
		Method m = clazz.getDeclaredMethod("method2", String.class);
		Object result = m.invoke(obj, "aaa");
	}

程序结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

获取构造器

	public static void main(String[] args) throws Exception {
   		ClassPool pool = ClassPool.getDefault();
   		CtClass personClass = pool.get("eee.Person");
   		//获取后也可以对构造器进行加强
   		CtConstructor[] cs = personClass.getConstructors();
   		for (CtConstructor c : cs) {
   			System.out.println(c.getLongName());
   		}
   }

结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)

获取注解

以上一篇文章中讲解的Student类为例:

@Target(ElementType.TYPE)//只能在类型或接口前
@Retention(RetentionPolicy.RUNTIME)//运行期保留,只有这样的注解才能被反射获取
@interface TableStudnet{
    String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
    String columnName();
    String type();
    int length();
}
@TableStudnet("student")
public class Student{
    @Filed(columnName = "db_id",type="int",length=10)
    private int id;
    @Filed(columnName = "db_age",type="int",length=10)
    private int age;
    @Filed(columnName = "db_name",type="varchar",length=3)
    private String name;
}

获取注解的方法

public class test2 {
	public static void main(String[] args) throws Exception {
   	ClassPool pool = ClassPool.getDefault();
   	CtClass personClass = pool.get("eee.Student");

   	Object[] all = personClass.getAnnotations();
   	TableStudnet s = (TableStudnet)all[0];
   	String value = s.value();
   	System.out.println("value:" + value);
	}
}

程序结果:
菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
对于javassist中常用的内容大概就是这些,如果大家想深入去探究可以去官网查阅API,当然这些对于平时的运用已经足够了。

后语

在新年即将来临之际,这次的Java系列的学习笔记就全部结束了。相信陪我一起学到这里的小伙伴一定收获丰厚把,哈哈。当然Java的生态链实在太广了,以后需要学习的内容远远不止这些,不过大家也不用担心,不管是什么花里胡哨的框架,它的底层是不变的,也许个人能力有限有些东西讲述的不明确,但笔记中提到的点都是需要大家掌握的。如果读笔记实在学不会可以看看视频。认真将这些知识点过上几遍。相信不管什么框架都可以轻松上手。
从明年开始本人也要开始一轮新知识的学习了,可能以后不会像这次一样完整的写一篇教程。但对于今后会遇到的一些共性问题也会做些说明。最后祝大家在新的一年里平平安安、顺顺利利、心想事成、工资翻倍!
上一篇:菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)

本文地址:https://blog.csdn.net/qq_41965041/article/details/111876702