• java之 javassist简单使用


    0x01、javassist介绍

    什么是javassist,这个词一听起来感觉就很懵,对吧~

    public void DynGenerateClass() {
         ClassPool pool = ClassPool.getDefault();
         CtClass ct = pool.makeClass("com.ideaGenerateClass");//创建类
         ct.setInterfaces(new CtClass[]{pool.makeInterface("java.lang.Cloneable")});//让类实现Cloneable接口
         try {
             CtField f= new CtField(CtClass.intType,"id",ct);//获得一个类型为int,名称为id的字段
             f.setModifiers(AccessFlag.PUBLIC);//将字段设置为public
             ct.addField(f);//将字段设置到类上
             //添加构造函数
             CtConstructor constructor=CtNewConstructor.make("public GeneratedClass(int pId){this.id=pId;}",ct);
             ct.addConstructor(constructor);
             //添加方法
             CtMethod helloM=CtNewMethod.make("public void hello(String des){ System.out.println(des);}",ct);
             ct.addMethod(helloM);
    
             ct.writeFile();//将生成的.class文件保存到磁盘
    
             //下面的代码为验证代码
             Field[] fields = ct.toClass().getFields();
             System.out.println("属性名称:" + fields[0].getName() + "  属性类型:" + fields[0].getType());
         } catch (CannotCompileException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         } catch (NotFoundException e) {
             e.printStackTrace();
         }
     }
    

    参考该篇文章java编程-javassist

    0x02 Javassist 使用

    这里主要讲一下主要的几个类:

    1、ClassPool

    ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用。

    从实现的角度来看,ClassPool 是一个存储 CtClass 的 Hash 表,类的名称作为 Hash 表的 key。ClassPool 的 get() 函数用于从 Hash 表中查找 key 对应的 CtClass 对象。如果没有找到,get() 函数会创建并返回一个新的 CtClass 对象,这个新对象会保存在 Hash 表中。

    需要注意的是ClassPool会在内存中维护所有被它创建过的CtClass,当CtClass数量过多时,会占用大量的内存,API中给出的解决方案是重新创建ClassPool 或 有意识的调用CtClass的detach()方法以释放内存。

    常用方法:

    //返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
    static ClassPool	getDefault()
    
    //在搜索路径的开头插入目录或jar(或zip)文件。
    ClassPath	insertClassPath(java.lang.String pathname)	
    
    //ClassPath在搜索路径的开头插入一个对象。
    ClassPath	insertClassPath(ClassPath cp)
    
    
        
    //获取类加载器toClass(),getAnnotations()在 CtClass等
    java.lang.ClassLoader	getClassLoader()	
    
    //从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用。
    CtClass	get(java.lang.String classname)	
    
    //将ClassPath对象附加到搜索路径的末尾。
    ClassPath	appendClassPath(ClassPath cp)	
        
    //创建一个新的public类
    CtClass	makeClass(java.lang.String classname)
    

    2、CtClass

    CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取,CtClass需要关注的方法:

    常用方法:

    //更改超类,除非此对象表示接口。
    void	setSuperclass(CtClass clazz)
    
    //将此类转换为java.lang.Class对象。
    java.lang.Class<?>	toClass(java.lang.invoke.MethodHandles.Lookup lookup)	
        
    //将该类转换为类文件。
    byte[]	toBytecode()	
    
    //将由此CtClass 对象表示的类文件写入当前目录。
    void	writeFile()	
    
    //将由此CtClass 对象表示的类文件写入本地磁盘。
    void	writeFile(java.lang.String directoryName)	
    
    //在当前类中创建了一个静态代码块
    CtConstructor	makeClassInitializer()	
    
    freeze:冻结一个类,使其不可修改;
    isFrozen:判断一个类是否已被冻结;
    defrost:解冻一个类,使其可以被修改;
    prune:删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用;
    detach:将该class从ClassPool中删除;
    writeFile:根据CtClass生成.class文件;
    toClass:通过类加载器加载该CtClass;
    addField,removeField:添加/移除一个CtField;
    addMethod,removeMethod:添加/移除一个CtMethod;
    addConstructor,removeConstructor:添加/移除一个CtConstructor。
    

    3、CtMethod

    CtMethod:表示类中的方法。

    insertBefore:在方法的起始位置插入代码;
    insterAfter: 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到exception;
    insertAt:	 在指定的位置插入代码;
    setBody:	 将方法的内容设置为要写入的代码,当方法被abstract修饰时,该修饰符被移除;
    make:		 创建一个新的方法。
    

    4、CtConstructor

    CtConstructor的实例表示一个构造函数。它可能代表一个静态构造函数(类初始化器)。可以通过CtConstructor.make方法创建

    常用方法

    //设置构造函数主体。
    void	setBody(java.lang.String src)	
    
    //从另一个构造函数复制一个构造函数主体。
    void	setBody(CtConstructor src, ClassMap map)	
    
    //复制此构造函数并将其转换为方法。
    CtMethod	toMethod(java.lang.String name, CtClass declaring)	
    

    5、ClassPath

    该类作用是用于通过 getResourceAsStream() 在 java.lang.Class 中获取类文件的搜索路径。

    构造方法:

    //创建一个搜索路径。
    ClassClassPath(java.lang.Class<?> c)	
    

    常见方法:

    //获取指定类文件的URL。
    java.net.URL	find (java.lang.String classname)	
    
    //通过获取类文getResourceAsStream()。
    java.io.InputStream	openClassfile(java.lang.String classname)	
    

    代码实例:

    ClassPool pool = ClassPool.getDefault();
    

    在默认系统搜索路径获取ClassPool对象。

    如果需要修改类搜索的路径需要使用insertClassPath方法进行修改。

    pool.insertClassPath(new ClassClassPath(this.getClass()));
    

    将本类所在的路径插入到搜索路径中

    6、oBytecode

    package com.demo;
    
    import javassist.*;
    
    
    import java.io.IOException;
    import java.util.Arrays;
    
    public class testssit {
        public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {
            ClassPool pool = ClassPool.getDefault();
            pool.insertClassPath(new ClassClassPath(demo.class.getClass()));
            CtClass ctClass = pool.get("com.demo.test");
            ctClass.setSuperclass(pool.get("com.demo.test"));
    //        System.out.println(ctClass);
            byte[] bytes = ctClass.toBytecode();
            String s = Arrays.toString(bytes);
            System.out.println(s);
        }
    
    }
    

    7、toClass

    toClass:将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。

    Hello类:
    public class Hello {
        public void say() {
            System.out.println("Hello");
        }
    }
    Test 类
    public class Test {
        public static void main(String[] args) throws Exception {
            ClassPool cp = ClassPool.getDefault();//在默认系统搜索路径获取ClassPool对象。
            CtClass cc = cp.get("com.demo.Hello");  //获取hello类的
            CtMethod m = cc.getDeclaredMethod("say"); //获取hello类的say方法
            m.insertBefore("{ System.out.println("Hello.say():"); }");//在正文的开头插入字节码
            Class c = cc.toClass();//将此类转换为java.lang.Class对象
            Hello h = (Hello)c.newInstance(); //反射创建对象并进行强转
            h.say();调用方法say
        }
    }
    

    0x03、创建Class文件

    public class App {
     public static void main(String[] args) {
      try {
       createPerson();
      } catch (Exception e) {
       e.printStackTrace();
      }
     }
    
     private static void createPerson() throws Exception {
      ClassPool pool = ClassPool.getDefault();
    
      // 1. 创建一个空类
      CtClass cc = pool.makeClass("com.hearing.demo.Person");
    
      // 2. 新增一个字段 private String name = "hearing";
      CtField param = new CtField(pool.get("java.lang.String"), "name", cc);
      param.setModifiers(Modifier.PRIVATE);
      cc.addField(param, CtField.Initializer.constant("hearing"));
    
      // 3. 生成 getter、setter 方法
      cc.addMethod(CtNewMethod.setter("setName", param));
      cc.addMethod(CtNewMethod.getter("getName", param));
    
      // 4. 添加无参的构造函数
      CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);
      cons.setBody("{name = "hearing";}");
      cc.addConstructor(cons);
    
      // 5. 添加有参的构造函数
      cons = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, cc);
      // $0=this / $1,$2,$3... 代表方法参数
      cons.setBody("{$0.name = $1;}");
      cc.addConstructor(cons);
    
      // 6. 创建一个名为printName方法,无参数,无返回值,输出name值
      CtMethod ctMethod = new CtMethod(CtClass.voidType, "printName", new CtClass[]{}, cc);
      ctMethod.setModifiers(Modifier.PUBLIC);
      ctMethod.setBody("{System.out.println(name);}");
      cc.addMethod(ctMethod);
    
      //这里会将这个创建的类对象编译为.class文件
      cc.writeFile("../");
     }
    }
    

    创建的class文件如下

    public class Person {
    	private String name = "hearing";
    
    	public void setName(String var1) {
      		this.name = var1;
     	}
    
    	public String getName() {
    		return this.name;
     	}
    
     	public Person() {
      		this.name = "hearing";
     	}
    
     	public Person(String var1) {
      		this.name = var1;
     	}
    
     	public void printName() {
      		System.out.println(this.name);
     	}
    }
    

    0x04、调用生成的类对象

    1.通过反射的方式调用:

    Object person = cc.toClass().newInstance();
    Method setName = person.getClass().getMethod("setName", String.class);
    setName.invoke(person, "hearing1");
    Method execute = person.getClass().getMethod("printName");
    execute.invoke(person);
    

    2.通过读取class文件的方式调用:

    ClassPool pool = ClassPool.getDefault();
    // 设置类路径
    pool.appendClassPath("../");
    CtClass ctClass = pool.get("com.hearing.demo.Person");
    Object person = ctClass.toClass().newInstance();
    // 下面和通过反射的方式一样去使用
    
  • 相关阅读:
    protobuf配置与使用
    gvim配置
    html div+css做页面布局
    php info
    开源相关工具汇总
    mem 0908
    linux dd指令
    java面试(2)--大数据相关
    Java基础面试题(1)
    转自ruby迷: 使用Net::SSH和Net::SCP编写Linux服务器管理脚本
  • 原文地址:https://www.cnblogs.com/0x7e/p/14396154.html
Copyright © 2020-2023  润新知