15

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

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;
}

我扫描@Property注释以确定我可以从 PurchaseOrder 反射性地设置和获取什么。有很多这样的类都使用java.lang.reflect.Field.get()and 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?

任何帮助将不胜感激。

4

5 回答 5

10

ASM

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

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

剩下的只是确定您需要在生成器代码中参数化哪些位。PurchaseOrder$customer_Field将成为文件的示例输出inject/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();
}

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


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

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

  • 这种解析和代码生成有前期成本
  • 你需要以某种方式发现和反省这些生成的类型
于 2010-06-15T11:30:24.770 回答
4

一个使用 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);
    }
}
于 2010-06-19T09:26:53.773 回答
1

您也可以使用注释处理器,从而避免字节码操作的复杂性。(见这篇关于 javabeat 的文章

于 2010-06-11T12:59:40.497 回答
0

我很惊讶反射这么慢。如果你预热 JVM,它不应该比直接访问慢 5 倍。顺便说一句,微基准测试可能会产生误导性的结果,因为如果一个简单的 getter/setter 不做实际工作,它可以很容易地优化到一无所有。

避免反射和字节码的另一种方法是使用 sun.misc.Unsafe 类。它必须小心处理,并且不能移植到所有 JVM,但它比反射快 2-3 倍。有关示例,请参见我的 essential-rmi 项目。

另一种选择是生成代码并即时编译。您可以使用 Compiler API 或 BeanShell 之类的库。

注意:如果您有一个私有字段,则不能使用字节码从另一个类访问它。这是 JVM 限制。内部和嵌套类通过为您生成访问器方法来避免这种情况,例如使用私有方法的类中的 access$100(您可能已经在调用堆栈中看到了这些)但是,这意味着您不能添加一个类来访问私有字段而不改变原始班级。

于 2010-06-12T09:40:00.290 回答
-2

目标是性能!

是的,在很多情况下,这就是目标。但是您现在使用 PropertyAccessor 所做的事情会导致您的性能下降!每次想要获取或设置属性时,都必须为customer_Field. 或者你必须保留你的实例。我看不出简单的 getter 或 setter 有什么问题。

public class PurchaseOrder {

   @Property
   private Customer customer;

   @Property
   private String name;

   pulic void setCustomer(Customer c)
   {
       this.customer = c;
   }

   public Customer getCustomer()
   {
       return customer;
   }

   // The same for name
}

这就是表演!本机代码可能快 14 倍,但你真的需要这么快吗?Java很棒。为什么?因为它平台独立。如果你要制作原生的东西,Java 的力量就消失了。那么,等待一分钟完成程序需要做的所有事情和等待 50 秒之间有什么区别。“我的14倍速度在哪里?” 您不仅需要获取和设置。您需要对所有数据进行处理。

而且我认为它不会更快,因为您只是获取和设置对象实例和原语。本机 Java 是为:

  • 必须计算在机器代码中比使用 Java 运行时环境更快的方法(很多java.lang.Math方法,例如sqrt()。他们可以用 Java 对其进行编程,但速度会更慢)
  • Java 自己无法完成的事情,例如:退出应用程序、创建套接字、写入/读取文件、调用其他进程等……这不是纯 Java,而是本机机器代码。

所以我希望我说服了你,你会用 Java 保留它。

于 2010-06-15T11:07:20.630 回答