Улучшение поля получает и устанавливает производительность с помощью ASM или Javassist
Я бы хотел избежать размышлений в проекте с открытым исходным кодом, который я разрабатываю. Здесь у меня есть классы вроде следующего.
public class PurchaseOrder {
@Property
private Customer customer;
@Property
private String name;
}
Я просматриваю аннотацию @Property
, чтобы определить, что я могу установить и получить из PurchaseOrder. Существует много таких классов с использованием java.lang.reflect.Field.get()
и java.lang.reflect.Field.set()
.
В идеале я хотел бы генерировать для каждого свойства invoker, как показано ниже.
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
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();
}
См. этот проект для примера того, как вводить методы.
При этом я полностью избегаю затрат на отражение.
Так как это все будет сделано во время выполнения:
- для этого синтаксического анализа и генерации кода есть начальная стоимость.
- вам нужно будет как-то обнаружить и наследовать эти сгенерированные типы
Ответ 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);
}
}
Ответ 3
Вы также можете использовать Обработчики аннотаций, тем самым избегая сложности манипуляции байт-кодами.
(см. эта статья о javabeat)
Ответ 4
Я удивляюсь, что размышление настолько медленнее. Если вы разогреваете JVM, это не должно быть более чем на 5 раз медленнее, чем прямой доступ. BTW Микро-бенчмарк может дать неверные результаты, потому что простой геттер/сеттер можно легко оптимизировать до нуля, если он не выполняет реальной работы.
Другой способ избежать отражения и байт-кода - использовать класс sun.misc.Unsafe. Его нужно обрабатывать с осторожностью и не переносить на все JVM, но он в 2-3 раза быстрее, чем отражение. Для примера см. Мой проект essence-rmi.
Другой вариант - сгенерировать код и скомпилировать его на лету. Вы можете использовать API компилятора или библиотеку, такую как BeanShell.
Примечание. Если у вас есть личное поле, к нему нельзя получить доступ из другого класса с использованием байтового кода. Это ограничение JVM. Внутренние и вложенные классы избегают этого, создавая методы доступа для вас, как доступ к классу $100 в классе с частными методами (возможно, вы видели их в стеке вызовов). Однако это означает, что вы не можете добавить класс для доступа к закрытым полям без изменения оригинала класс.
Ответ 5
Цель - производительность!
Да, это во многих случаях цель. Но что вы делаете сейчас с 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 Runtime Environment (много методов
java.lang.Math
, например sqrt()
). Они могли бы программировать его на Java, но это было бы медленнее)
- вещи, которые не могут выполнять Java, например: выход из приложения, создание сокетов, запись/чтение файлов, вызов других процессов и т.д. Это не чистая Java, которая является кодом Native Machine, который это делает.
Поэтому я надеюсь, что убедил вас, и вы сохраните его на Java.