我想避免在我正在开发的开源项目中反思。在这里,我有类似以下的课程。
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 Reflection,一个使用ASM,一个使用Javassist?
非常感谢任何帮助。
答案 0 :(得分: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
(我使用“注入”作为包。)
您还必须使用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。
有了这些,我完全避免了反思的代价。
因为这一切都将在运行时完成:
答案 1 :(得分: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);
}
}
答案 2 :(得分:1)
您也可以使用注释处理器,从而避免字节码操作的复杂性。 (见this article on javabeat)
答案 3 :(得分:0)
我很惊讶反思是这么慢。如果你预热JVM,它不应该比直接访问慢5倍。 BTW微型基准测试可以提供误导的结果,因为如果没有真正的工作,简单的getter / setter可以轻松优化为零。
另一种避免反射和字节代码的方法是使用sun.misc.Unsafe类。它必须小心处理,并且不能移植到所有JVM,但它比反射快2-3倍。请参阅我的本质 - rmi项目中的示例。
另一个选择是生成代码并动态编译。您可以使用Compiler API或BeanShell等库。
注意:如果您有私有字段,则无法使用字节代码从另一个类访问它。这是JVM限制。内部类和嵌套类通过为您生成访问器方法来避免这种情况,例如使用私有方法在类中访问$ 100(您可能已经在调用堆栈中看到过这些)但是,这意味着您无法在不更改原始字段的情况下添加类来访问私有字段类。
答案 4 :(得分:-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倍?”您不仅需要获取和设置。您需要对所有数据执行某些操作。
我认为它不会更快,因为你只是在设置对象实例和基元。 Native Java是为:
创建的java.lang.Math
方法,如sqrt()
。他们可以用Java编程,但它会是慢)所以我希望我说服你,你会用Java保留它。