动态生成、修改java类

2020/03/09 Mmo-Game

javasist应用实践

目录

javasist

javasist采用操作字节码方式,可以动态修改和生成class文件或者说java类。class文件的属性,是通过自己去动态拼接的,包括所有的字段、方法、接口都是事先添加的,然后调用writFile()生成。可用于热更新,监控,动态生成、修改java类。

1.获取一个池并创建一个类

//获取池减少消耗
ClassPool classPool = ClassPool.getDefault();
// 创建一个类
CtClass ctClass = classPool.makeClass(className);

2.添加类注解

// 添加类注解
ClassFile ccFile = ctClass.getClassFile();
ConstPool constPool = ccFile.getConstPool();
AnnotationsAttribute bodyAttr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
Annotation bodyAnnot = new Annotation("com.game2sky.publib.annotation.Dict", constPool);
bodyAttr.addAnnotation(bodyAnnot);

Annotation bodyAnnot2 = new Annotation("javax.persistence.Table", constPool);
bodyAnnot2.addMemberValue("name", new StringMemberValue(tableName, constPool));
bodyAttr.addAnnotation(bodyAnnot2);

ccFile.addAttribute(bodyAttr);

3.为类设置字段

CtField field = null;
try
{
    field = new CtField(classPool.get(paramTypes.get(i)), paramNames.get(i), ctClass);
    field.setModifiers(Modifier.PRIVATE);
}

4.添加getter和setter方法

// 添加getter和setter方法
ctClass.addMethod(CtNewMethod.setter(setParams.get(i), field));
ctClass.addMethod(CtNewMethod.getter(getParams.get(i), field));
ctClass.addField(field);

5.导入某个目录下的包,第三方jar包

//往池里导入某个目录下的包,那么在获取对应的class时池对象会从当前目录进行搜索
classPool.importPackage("gnu.trove.map");
classPool.importPackage("gnu.trove.map.hash");
classPool.importPackage("java.util");
classPool.importPackage("com.game2sky.application.common.dict.init");

例如:我们构造trove4J的public static TIntObjectMap<DictAbility> map = new TIntObjectHashMap<DictAbility>();
需要导同时入:
classPool.importPackage("gnu.trove.map");
classPool.importPackage("gnu.trove.map.hash");
不然,会报找不到TIntObjectMap对象

6.添加getter和setter方法

// 添加getter和setter方法
ctClass.addMethod(CtNewMethod.setter(setParams.get(i), field));
ctClass.addMethod(CtNewMethod.getter(getParams.get(i), field));
ctClass.addField(field);

7.给字段设置public 和 static

ctField.setModifiers(Modifier.PUBLIC | Modifier.STATIC);

8.添加方法,一定注意泛型需要/* */包住,并且DictAbility对象一定要扫描的到

CtMethod m = CtNewMethod.make(
        "public void init(List/*<DictAbility>*/ l){int size = l.size();\n" +
                "\t\tTIntObjectMap/*<DictAbility>*/ map = new TIntObjectHashMap/*<DictAbility>*/();\n" +
                "\t\tfor (int index = (size-1); index >= 0; index--)\n" +
                "\t\t{\n" +
                "\t\t\tDictAbility record = (DictAbility) l.get(index);\n" +
                "\t\t\tDictAbility existRecord = map.get(record.getId());\n" +
                "\t\t\tnewCache.put(record.getId(), record);\n" +
                "\t\t} \n" +
                "\t\tmap = newCache;}",
        ctClass);
ctClass.addMethod(m);
// 另一种为类设置方法,在body体设置不如上面那种灵活
CtMethod method = new CtMethod(CtClass.voidType, "init", new CtClass[] { classPool.get(List.class.getName()) },
        ctClass);
method.setModifiers(Modifier.PUBLIC);
method.setBody("{}");
ctClass.addMethod(method);

9.设置接口

//设置代理类的接口
CtClass interName = classPool.getCtClass(interfaceName); //获取代理对象的接口类
CtClass[] interfaces = new CtClass[]{interName};
ctClass.setInterfaces(interfaces);

10.生成并返回class对象

//调用writeFile()对象会锁死
ctClass.writeFile();
Class<?> clazz = ctClass.toClass();

11.把生成的class文件写入文件

byte[] byteArr = ctClass.toBytecode();
File file = new File("F:/xxx/"+clazz.getSimpleName()+".class");
if(file.exists())
{
    file.delete();
}

总结

javasist在构造class过程中,所有出现的对象都必须要扫描的到,例如上面出现的DictAbility对象。

Search

    Table of Contents