I would like to avoid reflection in an open source project I am developing. Here I have classes like the following.
public class PurchaseOrder {
@Property
private Customer customer;
@Property
private String name;
}
I scan for the @Property
annotation to determine what I can set and get from the PurchaseOrder reflectively. There are many such classes all using java.lang.reflect.Field.get()
and java.lang.reflect.Field.set()
.
Ideally I would like to generate for each property an invoker like the following.
public interface PropertyAccessor<S, V> {
public void set(S source, V value);
public V get(S source);
}
Now when I scan the class I can create a static inner class of PurchaseOrder
like so.
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;
}
}
With these I totally avoid the cost of reflection. I can now set and get from my instances with native performance. Can anyone tell me how I would do this. A code example would be great. I have searched the net for a good example but can find nothing like this. The ASM and Javasist examples are pretty poor also.
The key here is that I have an interface that I can pass around. So I can have various implementations, perhaps one with Java Reflection as a default, one with ASM, and one with Javassist?
Any help would be greatly appreciated.
Javassist enables Java programs to define a new class at runtime and to modify a class file when the JVM loads it. Unlike other similar bytecode editors, Javassist provides two levels of API: source level and bytecode level.
Bytecode manipulation consists in modifying the classes - represented by bytecode - compiled by the Java compiler, at runtime. It is used extensively for instance by frameworks such as Spring (IoC) and Hibernate (ORM) to inject dynamic behaviour to Java objects at runtime.
ASM
Using ASMifierClassVisitor
, you can see exactly what code you need to write to generate the inner classes:
ASMifierClassVisitor.main(new String[] { PurchaseOrder.customer_Field.class
.getName() });
The rest is just determining what bits you need to parameterize in your generator code. Example output for PurchaseOrder$customer_Field
which will become the file 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
(I used "inject" as the package.)
You'll also have to create synthetic accessors using ASM's visitor classes:
{
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();
}
See this project for an example of how to inject methods.
With these I totally avoid the cost of reflection.
Since this is all going to be done at runtime:
An example using Javassist, however it does require that your properties have package level protection instead of be private
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With