Olá pessoal. Hoje, queremos compartilhar com você a tradução de um artigo preparado especialmente para os alunos do curso 
Java Developer .
No meu artigo 
Padrão de especificação , não mencionei especificamente o componente subjacente que ajudou muito na implementação. Aqui vou falar mais sobre a classe 
JavaBeanUtil que usei para obter o valor de um campo de objeto. Nesse exemplo, era 
FxTransaction .

Obviamente, você dirá que pode usar o 
Apache Commons BeanUtils ou uma de suas alternativas para obter o mesmo resultado. Mas eu estava interessado em investigar isso e o que aprendi funciona muito mais rápido do que qualquer biblioteca criada com base no conhecido 
Java Reflection . 
Uma tecnologia que evita reflexões muito lentas é a 
invokedynamic bytecode 
invokedynamic . Em resumo, a manifestação de 
invokedynamic (ou "indy") foi a inovação mais séria no Java 7, que abriu o caminho para a implementação de linguagens dinâmicas na parte superior da JVM usando invocações de métodos dinâmicos. Posteriormente, no Java 8, isso também permitiu 
expressões lambda e referências a métodos, além de concatenação de cadeias aprimorada no Java 9.
Em poucas palavras, a técnica que vou descrever abaixo usa 
LambdaMetafactory e 
MethodHandle para criar dinamicamente uma implementação da interface 
Function . Function é o 
único método que delega uma chamada para o método de destino real com código definido dentro do lambda.
Nesse caso, o método target é um getter que tem acesso direto ao campo que queremos ler. Além disso, devo dizer que, se você conhece bem as inovações que apareceram no Java 8, encontrará os trechos de código abaixo bastante simples. Caso contrário, o código pode parecer complicado à primeira vista.
Dê uma olhada no JavaBeanUtil improvisado
O método 
getFieldValue é um método utilitário usado para ler valores de um campo JavaBean. É necessário um objeto JavaBean e um nome de campo. O nome do campo pode ser simples (por exemplo, 
fieldA ) ou aninhado, separado por pontos (por exemplo, 
nestedJavaBean.nestestJavaBean.fieldA ).
 private static final Pattern FIELD_SEPARATOR = Pattern.compile("\\."); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); private static final ClassValue<Map<String, Function>> CACHE = new ClassValue<Map<String, Function>>() { @Override protected Map<String, Function> computeValue(Class<?> type) { return new ConcurrentHashMap<>(); } }; public static <T> T getFieldValue(Object javaBean, String fieldName) { return (T) getCachedFunction(javaBean.getClass(), fieldName).apply(javaBean); } private static Function getCachedFunction(Class<?> javaBeanClass, String fieldName) { final Function function = CACHE.get(javaBeanClass).get(fieldName); if (function != null) { return function; } return createAndCacheFunction(javaBeanClass, fieldName); } private static Function createAndCacheFunction(Class<?> javaBeanClass, String path) { return cacheAndGetFunction(path, javaBeanClass, createFunctions(javaBeanClass, path) .stream() .reduce(Function::andThen) .orElseThrow(IllegalStateException::new) ); } private static Function cacheAndGetFunction(String path, Class<?> javaBeanClass, Function functionToBeCached) { Function cachedFunction = CACHE.get(javaBeanClass).putIfAbsent(path, functionToBeCached); return cachedFunction != null ? cachedFunction : functionToBeCached; } 
Para melhorar o desempenho, eu 
fieldName cache uma função criada dinamicamente que realmente lê um valor de um campo chamado 
fieldName . No método 
getCachedFunction , como você pode ver, existe um caminho "rápido" usando o 
ClassValue para armazenamento em cache e um caminho 
createAndCacheFunction "lento", que é executado se um valor não for encontrado no cache.
O método 
createFunctions chama um método que retorna uma lista de funções que serão encadeadas usando 
Function::andThen . As funções de vinculação entre si em uma cadeia podem ser representadas como chamadas aninhadas, semelhantes a 
getNestedJavaBean().getNestJavaBean().getNestJavaBean().getFieldA() . Depois disso, simplesmente colocamos a função no cache chamando o método 
cacheAndGetFunction .
Se você observar atentamente a criação da função, precisamos percorrer os campos no 
path seguinte maneira:
 private static List<Function> createFunctions(Class<?> javaBeanClass, String path) { List<Function> functions = new ArrayList<>(); Stream.of(FIELD_SEPARATOR.split(path)) .reduce(javaBeanClass, (nestedJavaBeanClass, fieldName) -> { Tuple2<? extends Class, Function> getFunction = createFunction(fieldName, nestedJavaBeanClass); functions.add(getFunction._2); return getFunction._1; }, (previousClass, nextClass) -> nextClass); return functions; } private static Tuple2<? extends Class, Function> createFunction(String fieldName, Class<?> javaBeanClass) { return Stream.of(javaBeanClass.getDeclaredMethods()) .filter(JavaBeanUtil::isGetterMethod) .filter(method -> StringUtils.endsWithIgnoreCase(method.getName(), fieldName)) .map(JavaBeanUtil::createTupleWithReturnTypeAndGetter) .findFirst() .orElseThrow(IllegalStateException::new); } 
O método 
createFunctions acima para cada campo 
fieldName e a classe em que é declarado chama o método 
createFunction , que procura o getter desejado usando 
javaBeanClass.getDeclaredMethods() . Depois que o getter é encontrado, ele é convertido em uma tupla de tupla (tupla da biblioteca 
Vavr ), que contém o tipo retornado pelo getter e uma função criada dinamicamente que se comportará como se fosse um próprio getter.
Uma tupla é criada com o método 
createTupleWithReturnTypeAndGetter em combinação com o método 
createCallSite seguinte maneira:
 private static Tuple2<? extends Class, Function> createTupleWithReturnTypeAndGetter(Method getterMethod) { try { return Tuple.of( getterMethod.getReturnType(), (Function) createCallSite(LOOKUP.unreflect(getterMethod)).getTarget().invokeExact() ); } catch (Throwable e) { throw new IllegalArgumentException("Lambda creation failed for getterMethod (" + getterMethod.getName() + ").", e); } } private static CallSite createCallSite(MethodHandle getterMethodHandle) throws LambdaConversionException { return LambdaMetafactory.metafactory(LOOKUP, "apply", MethodType.methodType(Function.class), MethodType.methodType(Object.class, Object.class), getterMethodHandle, getterMethodHandle.type()); } 
Nos dois métodos acima, eu uso uma constante chamada 
LOOKUP , que é apenas uma referência a 
MethodHandles.Lookup . Com ele, posso criar um 
link direto para um método (manipulação direta de método) com base em um getter encontrado anteriormente. E, finalmente, o 
MethodHandle criado é passado para o método 
createCallSite , no qual o corpo lambda é criado para a função usando 
LambdaMetafactory . A partir daí, finalmente, podemos obter uma instância do 
CallSite , que é o "guardião" da função.
Observe que para setters você pode usar uma abordagem semelhante usando 
BiFunction em vez de 
Function .
Referência
Para medir o desempenho, usei a maravilhosa ferramenta JMH ( 
Java Microbenchmark Harness ), que provavelmente faz parte do JDK 12 ( 
nota do tradutor: sim, o jmh está incluído no java 9 ). Como você provavelmente sabe, o resultado depende da plataforma, portanto, para referência: usarei 
1x6 i5-8600K 3,6 Linux x86_64, Oracle JDK 8u191 GraalVM EE 1.0.0-rc9 .
Para comparação, escolhi a 
biblioteca BeanUtils do 
Apache Commons , amplamente conhecida pela maioria dos desenvolvedores de Java, e uma de suas alternativas chamada 
Jodd BeanUtil , que é alegadamente 
20% mais rápida .
O código de referência é o seguinte:
 @Fork(3) @Warmup(iterations = 5, time = 3) @Measurement(iterations = 5, time = 1) @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @State(Scope.Thread) public class JavaBeanUtilBenchmark { @Param({ "fieldA", "nestedJavaBean.fieldA", "nestedJavaBean.nestedJavaBean.fieldA", "nestedJavaBean.nestedJavaBean.nestedJavaBean.fieldA" }) String fieldName; JavaBean javaBean; @Setup public void setup() { NestedJavaBean nestedJavaBean3 = NestedJavaBean.builder().fieldA("nested-3").build(); NestedJavaBean nestedJavaBean2 = NestedJavaBean.builder().fieldA("nested-2").nestedJavaBean(nestedJavaBean3).build(); NestedJavaBean nestedJavaBean1 = NestedJavaBean.builder().fieldA("nested-1").nestedJavaBean(nestedJavaBean2).build(); javaBean = JavaBean.builder().fieldA("fieldA").nestedJavaBean(nestedJavaBean1).build(); } @Benchmark public Object invokeDynamic() { return JavaBeanUtil.getFieldValue(javaBean, fieldName); }  @Benchmark public Object apacheBeanUtils() throws Exception { return PropertyUtils.getNestedProperty(javaBean, fieldName); }  @Benchmark public Object joddBean() { return BeanUtil.declared.getProperty(javaBean, fieldName); } public static void main(String... args) throws IOException, RunnerException { Main.main(args); } } 
A referência define quatro cenários para diferentes níveis de aninhamento do campo. Para cada campo, o JMH executará 5 iterações de 3 segundos para aquecer e, em seguida, 5 iterações de 1 segundo para a medição real. Cada cenário será repetido 3 vezes para obter melhores medições.
Resultados
Vamos começar com os resultados compilados para o JDK 
8u191 :
 Oracle JDK 8u191
Oracle JDK 8u191O pior cenário possível usando a abordagem 
invokedynamic é muito mais rápido que o mais rápido das outras duas bibliotecas. Essa é uma diferença enorme e, se você duvida dos resultados, sempre pode baixar o 
código-fonte e brincar com ele como quiser.
Agora vamos ver como o mesmo teste funciona com o 
GraalVM EE 1.0.0-rc9. GraalVM EE 1.0.0-rc9
GraalVM EE 1.0.0-rc9Os resultados completos podem ser vistos 
aqui com o belo JMH Visualizer.
Observações
Essa grande diferença ocorreu devido ao fato de o compilador JIT conhecer bem o 
CallSite e o 
MethodHandle e poder incorporá-los, diferentemente da abordagem de reflexão. Além disso, você pode ver como o 
GraalVM é promissor. Seu compilador faz um trabalho realmente impressionante que pode melhorar significativamente o desempenho da reflexão.
Se você estiver curioso e quiser se aprofundar, encorajo você a pegar o código do meu repositório do 
Github . Lembre-se de que eu não aconselho que você faça um 
JavaBeanUtil fabricação própria para usá-lo na produção. Meu objetivo é simplesmente mostrar meu experimento e as possibilidades que podemos obter do 
invokedynamic .
A tradução chegou ao fim e convidamos todos a um 
seminário on- 
line gratuito em 13 de junho, no qual examinaremos como o Docker pode ser útil para um desenvolvedor Java: como criar uma imagem do docker com um aplicativo java e como interagir com ele.