javassist简单介绍以及如何使用它来实现lombok的功能

javassist简单介绍以及如何使用它来实现lombok的功能_第1张图片

最近在学习MyBatis的源码,在阅读的过程中,发现有一个叫做javaassist的工具,查阅了一些资料,这个工具和ASM的功能类似,可以直接修改Java的字节码文件,而ASM要更偏向底层一些。

下面我们通过一个例子来学习其使用方法:

public class Run {
    public static void main(String[] args) {
        ClassPool pool = ClassPool.getDefault();
        try {
            CtClass ctClass = pool.getCtClass("test01.Cats");
            CtMethod ctMethod = ctClass.getDeclaredMethod("getName");
            for (CtField ctField : ctClass.getDeclaredFields()) {
                System.out.println(ctField.getName());
            }
            ctMethod.insertBefore("System.out.println(\"teter==est\");");
            ctClass.writeFile();
            Class clazz = ctClass.toClass();
            Constructor constructor = clazz.getConstructor(new Class[]{String.class, Integer.class});
            Cats cats = (Cats) constructor.newInstance("tom", 23);
            System.out.println(cats.getName());
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (CannotCompileException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

下面是Cats类的结构,很简单,总共就包含两个属性:

public class Cats implements Cloneable {
    private String name;
    private Integer age;

   public Cats(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Cats() {
    }
		
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    getSet()方法...
}

我们在Cats类的getName()方法前面添加一处打印的语句,然后执行代码查看结果:

javassist简单介绍以及如何使用它来实现lombok的功能_第2张图片

 

除了修改已经存在的类,javaassist工具甚至能够直接拼接出一个class文件:

public class Run02 {
    public static void main(String[] args) {
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("test01.Dog");
        try {
            CtField name = CtField.make("private String name;", ctClass);
            CtField age = CtField.make("private Integer age;", ctClass);
            ctClass.addField(name);
            ctClass.addField(age);
            CtMethod methodSetName = CtMethod.make("public void setName(String name){this.name=name;}", ctClass);
            CtMethod methodSetAge = CtMethod.make("public void setAge(Integer age){this.age=age;}", ctClass);
            CtMethod meGetName = CtMethod.make("public String getName(){return this.name;}", ctClass);
            CtMethod meGetAge = CtMethod.make("public Integer getAge(){return this.age;}", ctClass);
            ctClass.addMethod(methodSetAge);
            ctClass.addMethod(methodSetName);
            ctClass.addMethod(meGetAge);
            ctClass.addMethod(meGetName);
            CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, ctClass);
            ctConstructor.setBody("{}");
            CtConstructor ctConstructorWithParam = new CtConstructor(new CtClass[]{pool.get("java.lang.Integer"), pool.get("java.lang.String")}, ctClass);
            ctConstructorWithParam.setBody("{this.name=$2;this.age=$1;}");
            ctClass.addConstructor(ctConstructor);
            ctClass.addConstructor(ctConstructorWithParam);
            ctClass.writeFile();
            //调用反射
            for (CtField ctField : ctClass.getDeclaredFields()) {
                System.out.println(ctField.getName());
            }
        } catch (CannotCompileException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

,自此,相信大家对于javaassist这个强大的工具有了一定的了解;于此同时,我又联想到在日常的开发过程中常用的lombok工具,我们可以通过注解的方式来生成一些列的get,set,构造器等,而无需自己手动编写,那它是如何实现的呢?由于本人暂时还没有阅读过lombok的源码,所以猜测也是通过修改字节码的方式对实体类进行了修改,依据这一思路,下面我们借助javaassist来手写一个类似于lombok的小工具吧:

随意创建一个注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HaHaData {

}

 

然后在Pay类上边加上对应的注解:

@HaHaData
public class Pay {
    private String payId;
    private Long userId;
}

接下来修改class文件:

public class Run03 {
    private static String classLoadPath = "utilTest41.Pay";

    public static void main(String[] args) {
        try {
            Class clazz = Class.forName(classLoadPath);
            HaHaData haHaData = (HaHaData) clazz.getAnnotation(HaHaData.class);
            if (haHaData != null) {
                ClassPool pool = ClassPool.getDefault();
                CtClass ctClass = pool.getCtClass(classLoadPath);
                Field[] fields = clazz.getDeclaredFields();
                CtClass[] ctClasses = new CtClass[fields.length];
                StringBuilder constructorBody = new StringBuilder();
                int position = 0;
                constructorBody.append("{");
                for (Field field : fields) {
                    StringBuilder sb = new StringBuilder();
                    sb.append("public void set").append(toUpperFristChar(field.getName())).append("(").append(field.getType().getSimpleName()).append(" haHaParam").
                            append(")").append("{").append("this.").append(field.getName()).
                            append("=").append(field.getName()).append(";}");
                    CtMethod methodSet = CtMethod.make(sb.toString(), ctClass);
                    sb.setLength(0);
                    sb.append("public ").append(field.getType().getSimpleName()).append(" get").append(toUpperFristChar(field.getName())).append("()").append("{").append("return this.").append(field.getName()).append(";}");
                    CtMethod methodGet = CtMethod.make(sb.toString(), ctClass);
                    ctClasses[position] = pool.getCtClass(field.getType().getName());
                    constructorBody.append("this.").append(field.getName()).append("=").append("$").append(position + 1).append(";");
                    ctClass.addMethod(methodSet);
                    ctClass.addMethod(methodGet);
                    ++position;
                }
                constructorBody.append("}");
                CtConstructor ctConstructorWithParam = new CtConstructor(ctClasses, ctClass);
                ctConstructorWithParam.setBody(constructorBody.toString());
                ctClass.addConstructor(ctConstructorWithParam);
                ctClass.writeFile("out/production/javaproj/");
                Class c = Class.forName("utilTest41.Pay");
                Constructor[] constructors = c.getConstructors();
                for (Constructor constructor : constructors) {
                    System.out.println(constructor.getName());
                }
                Constructor con = c.getDeclaredConstructor(String.class, Long.class);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (CannotCompileException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    private static String toUpperFristChar(String string) {
        char[] charArray = string.toCharArray();
        charArray[0] -= 32;
        return String.valueOf(charArray);
    }
}

最后执行这个修改过的文件:

public class Run04 {
    public static void main(String[] args) {
        Class c = null;
        try {
            c = Class.forName("utilTest41.Pay");
            Constructor[] constructors = c.getConstructors();
            for (Constructor constructor : constructors) {
                System.out.println(constructor.getName());
            }
            Constructor con = c.getDeclaredConstructor(String.class, Long.class);
            Pay pay = (Pay) con.newInstance("21321321", Long.valueOf(12));
            System.out.println(pay.getPayId());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}

getPayId()方法会标红,但是这并不影响执行,我们直接运行查看结果:

javassist简单介绍以及如何使用它来实现lombok的功能_第3张图片

最后,如果我们不对jar包做一些加密以及混淆的工作,其实很有可能会被一些人做一些修改,威胁我们的系统,所以我们很有必要对产品做一些加密的工作,当然,就算加密了其实也可以通过dump 内存的方式来进行分析,不过破解的难度会有所提升。

你可能感兴趣的:(java基础)