资讯专栏INFORMATION COLUMN

Java 动态性(4) - 字节码操作

CoderStudy / 1225人阅读

摘要:字节码操作动态性的两种常见实现方式字节码操作反射运行时操作字节码可以让我们实现如下功能动态生成新的类动态改变某个类的结构添加删除修改新的属性方法优势比反射开销小性能高性能高于反射低于常见的字节码操作类库这是的项目的一部分是广泛使用的一种框它

1.字节码操作

JAVA动态性的两种常见实现方式

字节码操作

反射

运行时操作字节码可以让我们实现如下功能

动态生成新的类

动态改变某个类的结构(添加/删除/修改 新的属性/方法)

优势

比反射开销小,性能高

JAVAasist性能高于反射,低于asm

2.常见的字节码操作类库

BCEL

Byte Code Engineering Library (BCEL), 这是Apache Software Foundation 的 Jakarta 项目的一部分.BCEL是Java classworking广泛使用的一种框,它可以让您深入JVM汇编语言进行类操作的细节.BCEL与Javassist有不同的处理字节码方法,BCEL在实际的JVM指令层次上进行操作(BCEI拥有丰富的JVM指令级支持)而Javassist所强调的是源代码级别的工作

ASM

是一个轻量级ava字节码操作框架,直接涉及量到VM底层的操作和指令

CGLIB(Code Generation Library)

是一个强大的,高性能,高质量的Code生成类库,基于ASM实现

Javassist

是一个开源的分析、编辑和创建Jaw字节码的类库.性能较ASM差,跟cglib差不多,但是使用简单.很多开源框架都在使用它

主页:http://jboss-javassist.github...

3.JAVAssist库

Javassist(Java Programming Assistant)makes java bytecode manipulation simple.

It is a class library for editing bytecodes in Java;it enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it.

Unlike other similar bytecode editors,Javassist provides two levels of API:source level and bytecode level.

If the users use the source-level API,they can edit a class file without knowledge of the specifications of the Java bytecode.The whole API is designed with only the vocabulary of the java language.You can even specify inserted bytecode-level API allows the users to directly edit a class file as other editors.

Aspect Oriented Programming(AOP面向切面编程):Javassist can be a good tool for adding new methods into a class and for inserting before/after/around advice at the both caller and callee sides.

Reflection:Ones of applications of Javassist is runtime reflection;Javassist enables Java programs to use a metaobject that controls method calls on base-level objects.No specialized complier or virtual machine are needed.

4.JAVAssist库的API简介

javaassist的最外层的API和JAVA的反射包中的API颇为类似

它主要由CtClass,CtMethod,以及CtField几个类组成.用以执行和JDK反射API中java.lang.Class,java.lang.reflect.Method,java.lang.reflect.Method.Field相同的操作(Ct为Complie Time)

5.JAVAssist库的简单使用

创建一个全新的类

使用XJAD反编译工具,将生成的class文件反编译成JAVA文件

使用前先导入javassist的jar包
Demo:

/**
 * 使用javassist生成一个新的类
 * @author Matrix42
 *
 */
public class Demo01 {

    public static void main(String[] args) throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("com.lorinda.bean.Emp");
        
        //创建属性
        CtField f1 = CtField.make("private int empno;", cc);
        CtField f2 = CtField.make("private String ename;", cc);
        cc.addField(f1);
        cc.addField(f2);
        
        //创建方法
        CtMethod m1 = CtMethod.make("public int getEmpno(){return empno;}", cc);
        CtMethod m2 = CtMethod.make("public void setEmpno(int empno){this.empno = empno;}", cc);
        cc.addMethod(m1);
        cc.addMethod(m2);
        CtMethod m3 = CtMethod.make("public String getEname(){return ename;}", cc);
        CtMethod m4 = CtMethod.make("public void setEname(String empno){this.ename = ename;}", cc);
        cc.addMethod(m3);
        cc.addMethod(m4);
        
        //添加构造器
        CtConstructor constructor = new CtConstructor(new CtClass[]{CtClass.intType,pool.get("java.lang.String")}, cc);
        constructor.setBody("{this.empno=$1;this.ename=$2;}");
        cc.addConstructor(constructor);
        
        //将上面构造好的类写入到d:/myjava
        cc.writeFile("d:/myjava");
        System.out.println("生成类,成功!");
        
    }

}

创建完成后使用XJAD反编译就可以看到源码了

反编译源码:

package com.lorinda.bean;

public class Emp
{
    private int empno;
    private String ename;

    public int getEmpno()
    {
        return empno;
    }

    public void setEmpno(int i)
    {
        empno = i;
    }

    public String getEname()
    {
        return ename;
    }

    public void setEname(String s)
    {
        ename = ename;
    }

    public Emp(int i, String s)
    {
        empno = i;
        ename = s;
    }
}
6.JAVAssist库的API详解

方法操作

修改已有方法的方法体(插入代码到已有方法体)

新增方法

删除方法

a b c
$0,$1,$2,... this and actual parameters $0 代表的是 this,$1 代表方法参数的第一个参数,$2 代表方法参数的第二个参数, 以此类推,$N 代表方法参数的第 N 个参数
$args An arrar of parameters The type of $args is Object[], $args[0] 对应的是 $1 而不是 $0
$$ 所有方法参数的简写, 主要用在方法调用上 move(String a,String b) move($$) 相当于 move($1,$2)
fallthrough path 在类路径, 源文件路径等中有不存在的路径警告
$cflow
$r
$_
addCatch()
$class
$sig

属性操作

修改已有方法的方法体(插入代码到已有方法体)

新增方法

删除方法

Demo:

import java.awt.color.CMMException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;

public class Demo02 {

    public static void test01() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        byte[] bytes = cc.toBytecode();
        System.out.println(Arrays.toString(bytes));
        
        System.out.println(cc.getName());       //获得类名
        System.out.println(cc.getSimpleName()); //获得简要类名
        System.out.println(cc.getSuperclass()); //获得父类
        System.out.println(cc.getInterfaces()); //获得接口
    }
    
    public static void test02()throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        //CtMethod m = CtMethod.make("public int add(int a,int b){return a+b;}", cc);
        CtMethod m = new CtMethod(CtClass.intType,"add",
                new CtClass[]{CtClass.intType,CtClass.intType},cc);
        m.setModifiers(Modifier.PUBLIC);
        m.setBody("{System.out.println("Ha Ha");return $1+$2;}");
        
        cc.addMethod(m);
        
        //通过反射调用新生产的方法
        Class clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("add", int.class,int.class);
        Object result = method.invoke(obj, 200,300);
        System.out.println(result);
    }
    
    public static void test03()throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        CtMethod cm = cc.getDeclaredMethod("sayHello", new CtClass[]{CtClass.intType});
        cm.insertBefore("System.out.println($1);");
        
        Class clazz = cc.toClass();
        Object obj = clazz.newInstance();
        Method method = clazz.getDeclaredMethod("sayHello", int.class);
        method.invoke(obj, 90);
        
    }
    
    public static void test04() throws Exception{
        
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        //CtField f1 = CtField.make("private int empno", cc);
        CtField f1 = new CtField(CtClass.intType,"salary",cc);
        f1.setModifiers(Modifier.PRIVATE);
        cc.addField(f1,"1000");//1000位默认值
        
       // cc.getDeclaredField("ename"); //获取指定属性
        
        cc.addMethod(CtNewMethod.getter("salary",f1));
        cc.addMethod(CtNewMethod.setter("salary", f1));
    }
    
    public static void test05()throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.get("javassist.Emp");
        
        CtConstructor[] cs = cc.getConstructors();
        for (CtConstructor ctConstructor : cs) {
            System.out.println(ctConstructor.getLongName());
        }
    }
    
    public static void main(String[] args) throws Exception {
        //test01();
        //test02();
       // test03();
       // test04();
        test05();

    }

}

构造方法操作

getConstructors()

注解操作

代码片段:

public @interface Author {
String name();
int year();
}
@Author(name="Chiba",year=2005)
public class Point{
    int x,y;
}
CtClass cc = ClassPool.getDefault.get("Point");
Object[] all = cc.getAnnotations();
Author a = (Author)all[0];
String name = a.name();
int year = a.year();
System.out.println("name:"+name+",year:"+year);
当调用了writeFile(),toClass(),toBytecode(),Javassist会把那个CtClass对象冻结,如果想使用冻结的对象可以调用.defrose()方法

局限性

JDK5.0新语法不支持(包括泛型,枚举),不支持注解修改,单可以的通过底层javasist类来解决,具体参考:javassist.bytecode.annotation

不支持数组的初始化,如String[]{"1","2"},除非只有数组容量为1

不支持内部类盒匿名类

不支持continue盒break表达式

对于继承关系,有些语法不支持,如:

class A{}

class B extends A{}

class C extends B{}

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71943.html

相关文章

  • 教你用Java字节做点有趣的事

    摘要:字节码是程序的中间表示形式介于人类可读的源码和机器码之间。在中一般是用编译源文件变成字节码,也就是我们的文件。字节码的执行操作,指的就是对当前栈帧数据结构进行的操作。 0.写在前面 为什么会写这篇文章呢?主要是之前调研过日志脱敏相关的一些,具体可以参考LOG4j脱敏插件如何编写里面描述了日志脱敏插件编写方法: 直接在toString中修改代码,这种方法很麻烦,效率低,需要修改每一个要...

    hqman 评论0 收藏0
  • Java知识点总结(动态字节操作-Javassist介绍)

    摘要:知识点总结动态字节码操作介绍知识点总结动态字节码操作运行时操作字节码可以让我们实现如下功能动态生成新的类动态改变某个类的结构添加删除修改新的属性方法常见的字节码操作类库,这是的项目的一部分。 Java知识点总结(动态字节码操作-Javassist介绍) @(Java知识点总结)[Java, 动态字节码操作] 运行时操作字节码可以让我们实现如下功能: 动态生成新的类 动态改变某个类的结...

    godruoyi 评论0 收藏0
  • Java动态编程初探

    摘要:动态编程使用场景通过配置生成代码,减少重复编码,降低维护成本。动态生成字节码操作字节码的工具有,其中有两个比较流行的,一个是,一个是。 作者简介 传恒,一个喜欢摄影和旅游的软件工程师,先后从事饿了么物流蜂鸟自配送和蜂鸟众包的开发,现在转战 Java,目前负责物流策略组分流相关业务的开发。 什么是动态编程 动态编程是相对于静态编程而言的,平时我们讨论比较多的静态编程语言例如Java, 与动态...

    赵连江 评论0 收藏0
  • Java虚拟机 :Java字节指令的执行

    摘要:虚拟机执行程序的基础是特定的二进制指令集和运行时栈帧二进制指令集是虚拟机规定的一些指令,在编译后二进制字节码的类方法里的字节码就是这种指令,所以只要找到方法区里的类方法就可以依照这套指令集去执行命令。 这篇文章的素材来自周志明的《深入理解Java虚拟机》。 作为Java开发人员,一定程度了解JVM虚拟机的的运作方式非常重要,本文就一些简单的虚拟机的相关概念和运作机制展开我自己的学习过程...

    coolpail 评论0 收藏0
  • 字节及ASM使用

    摘要:字节码及使用什么是字节码机器码机器码是可直接解读的指令。字节码的执行操作,指的就是对当前栈帧数据结构进行的操作。动态链接每个栈帧指向运行时常量池中该栈帧所属的方法的引用,也就是字节码的发放调用的引用。 字节码及ASM使用 什么是字节码? 机器码机器码(machine code)是CPU可直接解读的指令。机器码与硬件等有关,不同的CPU架构支持的硬件码也不相同。 字节码字节码(byte...

    hearaway 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<