最近在学习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()方法前面添加一处打印的语句,然后执行代码查看结果:
除了修改已经存在的类,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()方法会标红,但是这并不影响执行,我们直接运行查看结果:
最后,如果我们不对jar包做一些加密以及混淆的工作,其实很有可能会被一些人做一些修改,威胁我们的系统,所以我们很有必要对产品做一些加密的工作,当然,就算加密了其实也可以通过dump 内存的方式来进行分析,不过破解的难度会有所提升。