Комментарии 15
Поздравляю, автор оригинала изобрел то, что в разных видах существует уже лет 10 :) Причем было сделано задолго до Java 8.
Этим пользуются очень и очень многие широко известные продукты. Ну хоть Lambdaj например. Или Guava. Или Hamcrest.
В сущности, достаточно статического метода, которому вы передаете Address.class, чтобы на выходе вернуть обертку, которая позволяет типобезопасно работать со свойствами и методами этого класса, и без всяких там city в виде строкового литерала. Тот факт, что конкретный Bean Validation API этого не умеет, говорит вероятно лишь о том, что его создавали во времена, когда еще не было Generics. Или он просто плохо написан.
Этим пользуются очень и очень многие широко известные продукты. Ну хоть Lambdaj например. Или Guava. Или Hamcrest.
В сущности, достаточно статического метода, которому вы передаете Address.class, чтобы на выходе вернуть обертку, которая позволяет типобезопасно работать со свойствами и методами этого класса, и без всяких там city в виде строкового литерала. Тот факт, что конкретный Bean Validation API этого не умеет, говорит вероятно лишь о том, что его создавали во времена, когда еще не было Generics. Или он просто плохо написан.
Найдите, пожалуйста, как это сделано в Guava для свойств. Статья ничего не говорит про класс литералы. Тут речь о том, чтобы получить доступ к аннотациям над полями, при этом типобезопасно. BeanValidation как раз нуждается в аннотациях, а не только в значении поля.
Не исключаю, что не так что-то понял, но где у вас про аннотации? Где-то ближе к концу?
А еще для этих целей используется метамодель, и пишется проще: users.get(User_.username). Особенно если сборку настроить для автоматической генерации метамодели
А о какой метамодели речь? Нужно ли повторять код классов? В Kotlin вот вообще круто, прямо в компилятор встроено.
Речь о canonical metamodel в JPA
Да, это тоже нормальное решение, но получается, что мы генерируем ещё код по классам сущностей, дополнительный boilerplate. Это всё мог бы давать и компилятор, как в случае с C#.
мышки плакали и кололись, но продолжали. Чем JPA MetaModel не устраивает? в том же, упомянутом в статье, Hibernate всё уже давно есть: https://docs.jboss.org/hibernate/orm/5.0/topical/html/metamodelgen/MetamodelGenerator.html
Я для этого создаю в классе специальный 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 получает его при помощи агента.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Эмуляция литералов свойств с Java 8 Method Reference