菜鸟学习笔记:Java提升篇12(Java动态性2——动态编译、javassist字节码操作)
菜鸟学习笔记: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);
}
}
执行后生成的文件:
我们也可以通过反射来运行编译好的类:
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字节码操作
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");
}
}
实验结果:
常用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());//获取接口
}
程序结果:
方法操作
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);
}
程序结果:
方法加强
通过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");
}
程序结果:
获取构造器
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());
}
}
结果:
获取注解
以上一篇文章中讲解的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);
}
}
程序结果:
对于javassist中常用的内容大概就是这些,如果大家想深入去探究可以去官网查阅API,当然这些对于平时的运用已经足够了。
后语
在新年即将来临之际,这次的Java系列的学习笔记就全部结束了。相信陪我一起学到这里的小伙伴一定收获丰厚把,哈哈。当然Java的生态链实在太广了,以后需要学习的内容远远不止这些,不过大家也不用担心,不管是什么花里胡哨的框架,它的底层是不变的,也许个人能力有限有些东西讲述的不明确,但笔记中提到的点都是需要大家掌握的。如果读笔记实在学不会可以看看视频。认真将这些知识点过上几遍。相信不管什么框架都可以轻松上手。
从明年开始本人也要开始一轮新知识的学习了,可能以后不会像这次一样完整的写一篇教程。但对于今后会遇到的一些共性问题也会做些说明。最后祝大家在新的一年里平平安安、顺顺利利、心想事成、工资翻倍!
上一篇:菜鸟学习笔记:Java提升篇11(Java动态性1——注解与反射)
本文地址:https://blog.csdn.net/qq_41965041/article/details/111876702
上一篇: Swing实现简单的简历样式