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

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

最近在学习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的功能
文章图片


除了修改已经存在的类,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 = https://www.it610.com/article/(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的功能
文章图片

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

    推荐阅读