Pull to refresh

Comments 15

Поздравляю, автор оригинала изобрел то, что в разных видах существует уже лет 10 :) Причем было сделано задолго до Java 8.

Этим пользуются очень и очень многие широко известные продукты. Ну хоть Lambdaj например. Или Guava. Или Hamcrest.

В сущности, достаточно статического метода, которому вы передаете Address.class, чтобы на выходе вернуть обертку, которая позволяет типобезопасно работать со свойствами и методами этого класса, и без всяких там city в виде строкового литерала. Тот факт, что конкретный Bean Validation API этого не умеет, говорит вероятно лишь о том, что его создавали во времена, когда еще не было Generics. Или он просто плохо написан.
Найдите, пожалуйста, как это сделано в Guava для свойств. Статья ничего не говорит про класс литералы. Тут речь о том, чтобы получить доступ к аннотациям над полями, при этом типобезопасно. BeanValidation как раз нуждается в аннотациях, а не только в значении поля.
Не исключаю, что не так что-то понял, но где у вас про аннотации? Где-то ближе к концу?

Тут действительно автор этот момент не поясняет. Например, в C# nameOf используется для биндинга данных с учетом атрибутов свойства, а для их получения нужна рефлексия.

В WPF для биндинга используется такая вещь как DependencyProperty, да и биндинги существовали задолго до nameOf
А еще для этих целей используется метамодель, и пишется проще: users.get(User_.username). Особенно если сборку настроить для автоматической генерации метамодели
А о какой метамодели речь? Нужно ли повторять код классов? В Kotlin вот вообще круто, прямо в компилятор встроено.
Да, это тоже нормальное решение, но получается, что мы генерируем ещё код по классам сущностей, дополнительный boilerplate. Это всё мог бы давать и компилятор, как в случае с C#.

Boilerplate это когда ты его сам пишешь — когда он генерируется кем вместо тебя уже сложно его называть boilerplate.


Ну и JPA metamodel генерируется в compile time и там же и компилируется.
А в вашем варианте эта работа производится в runtime, что уже совсем другое дело.

Не все любят генерировать код дополнительный, когда все эти сведения и так есть в коде сущностей. Согласитесь, если бы была поддержка компилятором — было бы всем лучше.

Генерация метамодели выполняется в compiletime, а создание и инстанцирование проксей — в runtime.
По мне так уж лучше compiletime.

Я для этого создаю в классе специальный enum, тело которого автоматически генерируется. Дальше делаю так: User.field.password Но конечно на уровне ЯП было бы лучше.

Теоретически имя вызываемого метода можно было бы получить из байткод лямбды.
Примерно вот так:


nameOf() in Java
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;

import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;

public class Util {

    private static final String FUNCTION_APPLY_METHOD_NAME = "apply";
    private static final String FUNCTION_APPLY_METHOD_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;";

    private static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.install();

    public static byte[] getClassBytes(Class<?> clazz) {

        try {
            ClassFileLocator classFileLocator = ClassFileLocator.AgentBased.of(INSTRUMENTATION, clazz);
            TypeDescription.ForLoadedType typeDefinitions = new TypeDescription.ForLoadedType(clazz);
            ClassFileLocator.Resolution resolution = classFileLocator.locate(typeDefinitions.getName());
            return resolution.resolve();
        } catch (IOException e) {
            return null;
        }

    }

    public static <T, P> String nameOf(@SuppressWarnings("unused") Class<T> type, Function<? super T, P> property) {

        byte[] bytes = getClassBytes(property.getClass());

        if (bytes != null) {

            final AtomicReference<String> calledMethodName = new AtomicReference<>(null);

            ClassReader classReader = new ClassReader(bytes);
            classReader.accept(new ClassVisitor(Opcodes.ASM5) {
                @Override
                public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {

                    if (name.equals(FUNCTION_APPLY_METHOD_NAME) && descriptor.equals(FUNCTION_APPLY_METHOD_SIGNATURE)) {
                        return new MethodVisitor(Opcodes.ASM5) {
                            @Override
                            public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
                                calledMethodName.set(name);
                                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
                            }
                        };
                    }

                    return super.visitMethod(access, name, descriptor, signature, exceptions);

                }
            }, 0);

            return calledMethodName.get();
        }

        return null;
    }
}

public class Test {

    public static void main(String... args) {
        System.out.println("String::toString()  : " + Util.nameOf(String.class, String::toString));  // toString
        System.out.println("new String(String)  : " + Util.nameOf(String.class, String::new));       // <init>
        System.out.println("(Object) -> { ... } : " + Util.nameOf(String.class, (Object object) -> { // Something like `lambda$main$0`
            System.out.println("Hello, lambda!");
            return object;
        }));
    }

}

Практически же этому препятствует отсутствие способа получить байткод лямбды без хаков.
В примере выше ByteBuddy получает его при помощи агента.

Sign up to leave a comment.

Articles