使用 ASM 或 Javassist 提高字段获取和设置性能

2022-09-03 15:09:46

我想避免在我正在开发的开源项目中出现反思。在这里,我有如下课程。

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;
}

我扫描注释以确定我可以设置的内容,并反思性地从“购买订单”中获取内容。有许多这样的类都使用 和 。@Propertyjava.lang.reflect.Field.get()java.lang.reflect.Field.set()

理想情况下,我希望为每个属性生成一个调用程序,如下所示。

public interface PropertyAccessor<S, V> {
   public void set(S source, V value);
   public V get(S source);
}

现在,当我扫描类时,我可以创建一个像这样的静态内部类。PurchaseOrder

static class customer_Field implements PropertyAccessor<PurchaseOrder, Customer> {
   public void set(PurchaseOrder order, Customer customer) {
      order.customer = customer;
   }  
   public Customer get(PurchaseOrder order) {
      return order.customer;
   }
}

有了这些,我完全避免了反思的成本。我现在可以设置和获取具有本机性能的实例。谁能告诉我我会怎么做。一个代码示例会很棒。我在网上搜索了一个很好的例子,但找不到这样的东西。ASM和Javasist的例子也非常差。

这里的关键是我有一个可以传递的界面。所以我可以有各种实现,也许一个默认使用Java Reflection,一个使用ASM,一个使用Javassist?

任何帮助将不胜感激。


答案 1

断续器

使用 ,您可以确切地看到需要编写哪些代码来生成内部类:ASMifierClassVisitor

ASMifierClassVisitor.main(new String[] { PurchaseOrder.customer_Field.class
    .getName() });

剩下的只是确定您需要在生成器代码中参数化的位。将成为文件的示例输出:PurchaseOrder$customer_Fieldinject/PurchaseOrder$customer_Field.class

public static byte[] dump () throws Exception {

  ClassWriter cw = new ClassWriter(0);
  FieldVisitor fv;
  MethodVisitor mv;
  AnnotationVisitor av0;

  cw.visit(V1_6, ACC_SUPER, "inject/PurchaseOrder$customer_Field",
      "Ljava/lang/Object;"+
      "Linject/PropertyAccessor<Linject/PurchaseOrder;Linject/Customer;>;", 
      "java/lang/Object",
      new String[] { "inject/PropertyAccessor" });
//etc

(我使用“inject”作为包。

您还必须使用 ASM 的访问者类创建综合访问器:

{
mv = cw.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "access$0", 
          "(Linject/PurchaseOrder;Linject/Customer;)V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitFieldInsn(PUTFIELD, "inject/PurchaseOrder",
          "customer", "Linject/Customer;");
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
{
mv = cw.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "access$1", 
          "(Linject/PurchaseOrder;)Linject/Customer;", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, "inject/PurchaseOrder", "
          customer", "Linject/Customer;");
mv.visitInsn(ARETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}

有关如何注入方法的示例,请参阅此项目


有了这些,我完全避免了反思的成本。

由于这一切都将在运行时完成:

  • 这种解析和代码生成有前期成本
  • 您需要以某种方式发现和内省这些生成的类型

答案 2

使用Javassist的示例,但是它确实要求您的属性具有包级保护,而不是私有的

public class AccessorGenerator {

    private final ClassPool pool;

    public PropertyGenerator() {
        pool = new ClassPool();
        pool.appendSystemPath();
    }

    public Map<String, PropertyAccessor> createAccessors(Class<?> klazz) throws Exception {
        Field[] fields = klazz.getDeclaredFields();

        Map<String, PropertyAccessor> temp = new HashMap<String, PropertyAccessor>();
        for (Field field : fields) {
            PropertyAccessor accessor = createAccessor(klazz, field);
            temp.put(field.getName(), accessor);
        }

        return Collections.unmodifiableMap(temp);
    }

    private PropertyAccessor createAccessor(Class<?> klazz, Field field) throws Exception {
        final String classTemplate = "%s_%s_accessor";
        final String getTemplate = "public Object get(Object source) { return ((%s)source).%s; }";
        final String setTemplate = "public void set(Object dest, Object value) { return ((%s)dest).%s = (%s) value; }";

        final String getMethod = String.format(getTemplate, 
                                               klazz.getName(),
                                               field.getName());
        final String setMethod = String.format(setTemplate, 
                                               klazz.getName(), 
                                               field.getName(), 
                                               field.getType().getName());

        final String className = String.format(classTemplate, klazz.getName(), field.getName());

        CtClass ctClass = pool.makeClass(className);
        ctClass.addMethod(CtNewMethod.make(getMethod, ctClass));
        ctClass.addMethod(CtNewMethod.make(setMethod, ctClass));
        ctClass.setInterfaces(new CtClass[] { pool.get(PropertyAccessor.class.getName()) });
        Class<?> generated = ctClass.toClass();
        return (PropertyAccessor) generated.newInstance();
    }

    public static void main(String[] args) throws Exception {
        AccessorGenerator generator = new AccessorGenerator();

        Map<String, PropertyAccessor> accessorsByName = generator.createAccessors(PurchaseOrder.class);

        PurchaseOrder purchaseOrder = new PurchaseOrder("foo", new Customer());

        accessorsByName.get("name").set(purchaseOrder, "bar");
        String name = (String) accessorsByName.get("name").get(purchaseOrder);
        System.out.println(name);
    }
}

推荐