java动态性的两种常见实现方式
1.字节码操作
2.反射
文章目录
反射机制
(1)反射
指的是可以于运行时加载、探知、使用编译期间完全未知的类。程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性和方法。
具体请看:https://blog.youkuaiyun.com/weixin_44035017/article/details/101161153
反射机制可以动态加载类,动态获取类的信息,动态构造对象,动态调用类和对象的方法与构造器,动态调用和处理属性,获取泛型的信息,处理注解等
(2)反射机制性能问题
setAccessible:启用和禁用访问安全检查的开关,值为 true 则指示反射的对象在使用时应该取消 Java 语
言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。并不是为true就能访问为false就不能访问。 禁止安全检查,可以提高反射的运行速度。
setAccessible为tru比 为false时的性能大约提升四倍,如果要使用反射并需要性能可以设为true
动态编译
JAVA 6.0引入了动态编译机制
服务器动态加载某些类文件进行编译
比如客户端上传个java代码,服务器端编译执行代码,也就是在运行时需要去编译一段代码
动态编译的两种方法
1.通过Runtime调用javac,启动新的进程去操作
Runtime run = Runtime.getRuntime();
Process process = run.exec("javac -cp 文件目录");
这是启用一个新的进程来编译
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Test {
public static void main(String[] args) throws IOException {
/*JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();
int result=compiler.run(null,null,null,"D:/HelloWorld.java");
*/
Runtime run=Runtime.getRuntime();
Process process=run.exec("java -cp d:/test HelooWorld");
InputStream in=process.getInputStream();
BufferedReader br=new BufferedReader(new InputStreamReader(in));
String str="";
while ((str=br.readLine())!=null){
System.out.println(str);
}
}
}
2.通过JavaCompiler动态编译
JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();
int result=compiler.run(In,null,null,"文件目录(可变参数)");
run(InputStream in, OutputStream out, OutputStream err, String… arguments)
第一个参数为java编译器提供参数
第二个参数为得到java编译器的输出信息
第三个参数为接收编译器的错误信息
第四个参数为可变参数,能传入一个或多个 Java 源文件
返回值:0表示编译成功,非0表示编译失败
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.IOException;
public class Test {
public static void main(String[] args) throws IOException {
JavaCompiler compiler= ToolProvider.getSystemJavaCompiler();
int result=compiler.run(null,null,null,"D:/test/HelloWorld.java");
System.out.println(result);
}
}
输出结果:
0
使用类加载器然后执行文件
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Test {
public static void main(String[] args) throws Exception
URL[] urls=new URL[]{new URL("file:/"+"d:/test/")};
URLClassLoader loader=new URLClassLoader(urls);
Class c=loader.loadClass("HelloWorld");
Method method=c.getMethod("main",String[].class);
method.invoke(null,(Object) new String[]{}); //main方法是静态方法,所以对象是null
//一定要强转为Object
}
}
输出结果:
HelloWorld
method.invoke(null,(Object) new String[]{}); //main方法是静态方法,所以对象是null
一定要强转为Object
如果强转为Object,比如method.invoke(null,(Object) new String[]{“one”,"two});会编译成:m.invoke(null,“one”,“two”),就发生了参数个数不匹配的问题。
如果是一段字符串,可以通过IO操作将字符串转换成一个临时文件,然后动态编译
String str = "public class HelloWorld{public static void main(String[] args){System.out.println(\"HelloWorld\");}}";
脚本引擎执行javascript代码
java脚本API提供了接口可以获得脚本程序输入,通过脚本引擎运行脚本并返回运行结果
不同的脚本语言对接口有不同的实现,javaScript使用了Rhino.Rhino 是一种使用 Java 语言编写的 JavaScript 的开源实现,原先由Mozilla开发,JDK 6.0就已经被集成到JDK了。
Rhino首页https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino
通过脚本引擎的运行上下文可以在java和脚本之间进行交互,然后通过java应用程序调用脚本函数
(1)获取脚本引擎
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine=manager.getEngineByName("javascript");
(2)脚本引擎定义变量
脚本引擎存储变量是使用键值对,使用ScriptEngine 的put(String key,String value)方法来增加变量,key用来存储变量的名称,value用来存储变量的值。使用get(sSring key)方法可以获得变量的值。实现了脚本和java的交互
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class Demo {
public static void main(String[] args) {
//1.获取脚本引擎
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine=manager.getEngineByName("javascript");
//2.定义变量,存储到引擎变量
engine.put("str","无兄弟不篮球");
System.out.println(engine.get("str"));
}
}
输出结果:
无兄弟不篮球
(3)定义脚本源码,使用脚本引擎执行
用字符串定义脚本源码,然后使用ScriptEngine的eval()方法执行
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Demo {
public static void main(String[] args) {
//获取脚本引擎
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine=manager.getEngineByName("javascript");
//定义脚本源码,使用脚本引擎执行
String code="var person={ name:'张三',id:0001,sex:'男'}; var name=person.name;"
code+="print(person.name);";
try {
engine.eval(code);
System.out.println(engine.get("name"));
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
输出结果:
张三
张三
(4)定义脚本函数,执行函数
使用Invocable接口
public interface Invocable
由ScriptEngines实现的可选接口,其方法允许在先前执行的脚本中调用过程。
使用Invocable接口的Object invokeFunction(String name,Object… args)方法可以返回一个Object
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
public class Demo {
public static void main(String[] args) {
//1.获取脚本引擎
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine=manager.getEngineByName("javascript");
//定义函数
try {
engine.eval("function add(a,b){var sum=a+b;return sum;}");
Invocable jsinvoke= (Invocable) engine;
Object result=jsinvoke.invokeFunction("add",1,2);
System.out.println(result);
} catch (ScriptException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
输出结果:
3.0
(5)脚本中导入java包
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.util.List;
public class Demo {
public static void main(String[] args) {
//获取脚本引擎
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine=manager.getEngineByName("javascript");
//导入Java包
String jscode="var list=java.util.Arrays.asList([\"欧文\",\"库里\"]);";
try {
engine.eval(jscode);
List<String> list= (List<String>) engine.get("list");
for (String temp:list) {
System.out.println(temp);
}
} catch (ScriptException e) {
e.printStackTrace();
}
}
}
输出结果:
欧文
库里
注:jdk1.8导入java包直接使用java.util.Arrays
而jdk1.6使用的是
String jscode="importPackage(java.util); var list=Arrays.asList([\"欧文\",\"库里\"]);";
(6)执行js文件
test.js
function add(a,b) {
var sum=a+b;
print(sum);
return sum;
}
add(1,2);
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.util.List;
public class Demo {
public static void main(String[] args) {
//1.获取脚本引擎
ScriptEngineManager manager=new ScriptEngineManager();
ScriptEngine engine=manager.getEngineByName("javascript");
//执行js文件
URL url=Demo.class.getClassLoader().getResource("test.js"); //src下
try {
FileReader fr=new FileReader(url.getPath());
engine.eval(fr);
fr.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ScriptException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出结果:
3
字节码操作
java动态性的两种常见实现方式
1.字节码操作
2.反射
字节码的性能比反射高,它们俩在实际使用中是相辅相成的
通过字节码操作,可以在运行时动态生成新的类,动态改变某个类的结构(增删改 属性或方法)
常见的字节码操作类库
BCEL
ASM
CGLIB
Javassist
bcel,asm都需要直接跟虚拟机指令打交道
Javassist即有面向java源码的又有面向字节码的
https://github.com/jboss-javassist/javassist 下载
解压后在项目中导入jar包
Javassist中最为重要的是ClassPool,CtClass ,CtMethod , CtField这几个类。
ClassPool:一个基于HashMap实现的CtClass对象容器,其中键是类名称,值是表示该类的CtClass对象。默认的ClassPool使用与底层JVM相同的类路径,因此在某些情况下,可能需要向ClassPool添加类路径或类字节。
CtClass:表示一个类,这些CtClass对象可以从ClassPool获得。
CtMethods:表示类中的方法。
CtFields :表示类中的字段。
javassist的用法和反射的用法比较像
使用javassist创建新类
使用ClassPool的makeClass方法
import javassist.*;
import java.io.IOException;
public class Demo1 {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.makeClass("Person");
//创建属性
CtField f1=CtField.make("private int id;",ctClass);
CtField f2=CtField.make("private String name;",ctClass);
ctClass.addField(f1);
ctClass.addField(f2);
//添加构造方法
CtConstructor constructor=new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")},ctClass);
constructor.setBody("{this.id=id;this.name=name;}");
ctClass.addConstructor(constructor);
//添加方法
CtMethod m1=CtMethod.make("public int getId() {return id;}",ctClass);
CtMethod m2=CtMethod.make("public void setId(int id) {this.id=id;}",ctClass);
ctClass.writeFile("D:/temp");
System.out.println("已创建一个新的类");
}
}
D盘已经生成一个新的class文件
然后可以使用反编译软件查看
使用javassist获取信息
写一个要获取信息的类
package JavassistLearn;
public class Person {
private int id;
private String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
import java.io.IOException;
import java.util.Arrays;
public class Demo2 {
public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.get("JavassistLearn.Person"); //包名.类名
byte[] bytes=ctClass.toBytecode();
System.out.println(Arrays.toString(bytes));
System.out.println(ctClass.getName()); //获取完整名称
System.out.println(ctClass.getSimpleName()); //获取类名
System.out.println(ctClass.getSuperclass()); //获取父类
System.out.println(ctClass.getInterfaces()); //获取接口
}
}
使用Javassist创建新方法
package JavassistLearn;
import javassist.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo3 {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.get("JavassistLearn.Person");
CtMethod method=new CtMethod(CtClass.intType,"add",new CtClass[]{CtClass.intType,CtClass.intType},ctClass);
method.setModifiers(Modifier.PUBLIC);
method.setBody("{System.out.println(\"javassist创造的新方法:\"+$1+$2);return $1+$2;}");
ctClass.addMethod(method);
//通过反射调用新方法
Class clzz=ctClass.toClass();
Object obj=clzz.newInstance();
Method m=clzz.getDeclaredMethod("add",int.class,int.class);
Object result=m.invoke(obj,10,20);
System.out.println(result);
}
}
输出结果:
javassist创造的新方法:1020
30
method.setBody("{System.out.println(“javassist创造的新方法:”+$1+$2);return $1+$2;}");
$1代表第一个参数,$2代表第二个参数
不能使用a+b去,因为这句话和创建方法中的形参a,b是分开的
$0代表this
CtMethod m = CtNewMethod.make("public int add(int a,int b){return a+b;}", cc);
直接使用源码也可以
使用Javassist修改方法
package JavassistLearn;
import javassist.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo4 {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.get("JavassistLearn.Person");
CtMethod method=ctClass.getDeclaredMethod("setId",new CtClass[]{CtClass.intType});
method.insertBefore("System.out.println($1);");
method.insertBefore("System.out.println(\"设置成功\");");
Class clzz=ctClass.toClass();
Object obj=clzz.newInstance();
Method m=clzz.getDeclaredMethod("setId",int.class);
m.invoke(obj,20000);
}
}
输出结果:
设置成功
20000
insertBefore表示在方法体的前面增加代码
类似还有insertAfter在方法体的后面增加代码,insertAt(行号, “代码”)在某一个前面加代码
使用Javassist修改属性
package JavassistLearn;
import javassist.*;
public class Demo5 {
public static void main(String[] args) throws NotFoundException, CannotCompileException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.get("JavassistLearn.Person");
CtField field=new CtField(CtClass.intType,"age",ctClass);
field.setModifiers(Modifier.PRIVATE);
ctClass.addField(field);
ctClass.addMethod(CtNewMethod.getter("getAge",field)); //对这个属性增加get和set方法
ctClass.addMethod(CtNewMethod.setter("setAge",field));
//然后使用反射...
}
}
CtField field=new CtField(CtClass.intType,"age",ctClass);
等同于
CtField field= CtField.make("private int age;", ctClass);
使用Javassist获取构造方法
public class Demo6 {
public static void main(String[] args) throws NotFoundException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.get("JavassistLearn.Person");
CtConstructor[] cs=ctClass.getConstructors();
for (CtConstructor c:cs) {
System.out.println(c.getName());
System.out.println(c.getLongName());
}
}
}
输出结果:
Person
JavassistLearn.Person()
Person
JavassistLearn.Person(int,java.lang.String)
有两个构造方法
使用Javassist获取注解
定义一个注解
package JavassistLearn;
public @interface whois {
int id();
String name();
}
在Person中使用注解
package JavassistLearn;
@whois(id=1001,name="张三")
public class Person {
private int id;
private String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package JavassistLearn;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.NotFoundException;
public class Demo7 {
public static void main(String[] args) throws NotFoundException, ClassNotFoundException {
ClassPool pool=ClassPool.getDefault();
CtClass ctClass=pool.get("JavassistLearn.Person");
Object[] obj=ctClass.getAnnotations();
whois w= (whois) obj[0]; //只有一个注解,所以取obj[0]
int id=w.id();
String name=w.name();
System.out.println("id:"+id+" name:"+name);
}
}
输出结果:
id:1001 name:张三