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()方法前面添加一处打印的语句,然后执行代码查看结果:

 

除了修改已经存在的类,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 内存的方式来进行分析,不过破解的难度会有所提升。

发布了225 篇原创文章 · 获赞 191 · 访问量 89万+
展开阅读全文

使用javassist实现动态修改注解,在tomcat下无法获取class路径

12-08

public ExcelBean createExcelBean(String[] propertyArray, String[] typeArray, String[] columnNameArray) throws NotFoundException, CannotCompileException, InstantiationException, IllegalAccessException { ExcelBean excelBean = null; ClassPool pool = ClassPool.getDefault(); //在tomcat环境下,向pool插入一个类对象的搜索路径 pool.insertClassPath("com.camb.common.web.bean.ExcelBean"); //获取要修改的类 CtClass ctClass = pool.get("com.camb.common.web.bean.ExcelBean"); for (int i = 0; i < propertyArray.length; i++) { //生成属性以及get和set方法 String property = propertyArray[i]; String method = property.substring(0, 1).toUpperCase() + property.substring(1, property.length()); String type = typeArray[i]; String column = columnNameArray[i]; ctClass.addField(CtField.make("private " + type + " " + property + ";", ctClass)); ctClass.addMethod(CtMethod.make("public void set" + method + "(" + type + " " + property + "){this." + property + " = " + property + ";}", ctClass)); ctClass.addMethod(CtMethod.make("public " + type + " get" + method + "(){return this." + property + ";}", ctClass)); //获取类里的属性 CtField ctField = ctClass.getField(property); FieldInfo fieldInfo = ctField.getFieldInfo(); System.out.println("属性名:" + ctField.getName()); ConstPool cp = fieldInfo.getConstPool(); //获取注解信息 AnnotationsAttribute attribute = new AnnotationsAttribute(cp, AnnotationsAttribute.visibleTag); Annotation annotation = new Annotation("com.camb.common.web.util.Excel", cp); //修改名称为name的注解 annotation.addMemberValue("name", new StringMemberValue(column, cp)); attribute.setAnnotation(annotation); fieldInfo.addAttribute(attribute); //打印修改后注解 Annotation annotation2 = attribute.getAnnotation("com.camb.common.web.util.Excel"); String value = ((StringMemberValue) annotation2.getMemberValue("name")).getValue(); System.out.println("修改后的注解参数===" + value); } //修改后的class Class<?> c = ctClass.toClass(); excelBean = (ExcelBean) c.newInstance(); ctClass.detach(); return excelBean; } pool.insertClassPath("com.camb.common.web.bean.ExcelBean");这个设置并不能找到class的位置,如果使用pool.insertClassPath(new ClassClassPath(ExcelBean.class));虽然能找class,但是会产生异常 com.camb.common.web.exception.ProcessException: javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of org/apache/catalina/loader/WebappClassLoader): attempted duplicate class definition for name: "com/camb/common/web/bean/ExcelBean" 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览