java动态性

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:张三
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值