代码之家  ›  专栏  ›  技术社区  ›  ng.

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

  •  15
  • ng.  · 技术社区  · 16 年前

    我希望避免在我正在开发的开源项目中出现反射。这里我有如下的课程。

    public class PurchaseOrder {
    
       @Property
       private Customer customer;
    
       @Property
       private String name;
    }
    

    @Property 注释来确定我可以从PurchaseOrder反射地设置和获取什么。有许多这样的类都使用 java.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反射作为默认值的,一个是用ASM的,还有一个是用Javassist的?

    任何帮助都将不胜感激。

    5 回复  |  直到 15 年前
        1
  •  10
  •   McDowell rahul gupta    16 年前

    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();
    }
    

    this project 有关如何注入方法的示例。


    有了这些,我完全避免了反思的代价。

    因为这一切都将在运行时完成:

    • 这种解析和代码生成有一个前期成本
    • 你需要以某种方式发现并反省这些生成的类型
        2
  •  4
  •   Michael Barker    16 年前

    例如使用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);
        }
    }
    
        3
  •  1
  •   Olivier Croisier    16 年前
        4
  •  0
  •   Peter Lawrey    16 年前

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

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

        5
  •  -2
  •   Martijn Courteaux    16 年前

    是的,这在很多情况下都是目标。但是你现在用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
    }
    

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

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