Aconteceu historicamente que em Java para as propriedades dos objetos (propriedades) nenhuma entidade física é fornecida. As propriedades em Java são algumas convenções nos campos de nomenclatura e métodos de acesso (acessadores) para eles. E, embora a presença de propriedades físicas na linguagem simplifique muitos casos (a partir da geração boba de getter-setters), parece que em um futuro próximo a situação em Java não mudará.
No entanto, ao desenvolver aplicativos de negócios multicamadas e usar várias estruturas para mapear e vincular dados, geralmente é necessário passar uma referência a uma propriedade do objeto. Vamos considerar quais são as opções para isso.
Usar nome da propriedade
Até o momento, a única maneira geralmente aceita de se referir a uma propriedade de objeto é uma string com seu nome. A biblioteca subjacente usa reflexão ou introspecção para procurar métodos de acesso e campos de acesso. Para fazer referência a objetos aninhados, a seguinte notação é geralmente usada:
person.contact.address.city
O problema com esse método é a falta de controle sobre a ortografia do nome e do tipo de propriedade com tudo o que isso implica:
- Não há controle de erros no estágio de compilação. Você pode cometer um erro no nome, pode aplicá-lo à classe errada, o tipo da propriedade não é controlado. Além disso, temos que escrever testes bastante estúpidos.
- Não há suporte do IDE. Muito cansado quando você mepe 200 + campos. É bom que exista um junho para isso, que pode ser desativado.
- Refatoração sofisticada de código. Mude o nome do campo e, imediatamente, muitas coisas cairão. Bons IDEs também trarão centenas de lugares onde uma palavra semelhante ocorre.
- Suporte e análise de código. Queremos ver onde a propriedade é usada, mas "Encontrar usos" não mostrará a sequência.
Como resultado, ainda queremos ter uma referência estática de propriedade com segurança de tipo. Um getter é o melhor candidato para essa função, porque:
- Vinculado a uma classe específica
- Contém o nome da propriedade.
- Tem tipo
Como posso me referir a um getter?
Proxying
Uma das maneiras interessantes é proxy (ou se molhar) objetos para interceptar a cadeia de chamadas do getter, que é usada em algumas bibliotecas: Mockito , QueryDSL , BeanPath . Sobre o último em Habré, havia um artigo do autor.
A ideia é bastante simples, mas não é trivial de implementar (um exemplo do artigo mencionado).
Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) );
Usando a geração dinâmica de código, é criada uma classe de proxy especial que herda da classe de bean e intercepta todas as chamadas getter na cadeia, construindo um caminho na variável ThreadLocal. Nesse caso, a chamada desses getters do objeto não ocorre.
Neste artigo, consideraremos um método alternativo.
Links de método
Com o advento do Java 8, lambdas e a capacidade de usar referências de métodos surgiram. Portanto, seria natural ter algo como:
Person person = … assertEquals("name", $(Person::getName).getPath());
O método $ aceita a seguinte lambda na qual a referência getter é passada:
public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda)
O problema é que, devido ao apagamento do tipo, não há como obter os tipos BEAN e TYPE em tempo de execução, e também não há informações sobre o nome do getter: o método chamado "fora" é Function.apply ().
No entanto, existe um certo truque - esse é o uso de lambda serializado.
MethodReferenceLambda<Person,String> lambda = Person::getName(); Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeMethod.setAccessible(true); SerializedLambda serLambda = (SerializedLambda) writeMethod.invoke(lambda); String className = serLambda.getImplClass().replaceAll("/", "."); String methodName = serLambda.getImplMethodName();
A classe SerializedLambda contém todas as informações necessárias sobre a classe e o método chamados. Em seguida é uma questão de tecnologia.
Como trabalho muito com estruturas de dados, esse método me incentivou a escrever uma pequena biblioteca para acesso estático às propriedades.
Biblioteca BeanRef
O uso da biblioteca é mais ou menos assim:
Person person = ...
e não requer a mágica da geração de código e dependências de terceiros. Em vez de uma cadeia getter, uma cadeia lambda é usada com referência aos getters. Ao mesmo tempo, a segurança de tipo é respeitada e o preenchimento automático baseado em IDE funciona muito bem:

Você pode usar o nome do getter na notação padrão (getXXX () / isXXX ()) e não padrão (xxx ()). A biblioteca tentará encontrar o setter correspondente e, se estiver ausente, a propriedade será declarada somente leitura.
Para acelerar o desempenho, as propriedades resolvidas são armazenadas em cache e, quando você a chama novamente com o mesmo lambda, o resultado já está salvo.
Além do nome da propriedade / caminho, usando o objeto BeanPath, você pode acessar o valor da propriedade do objeto:
Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person);
Além disso, se o objeto intermediário na cadeia for nulo, a chamada correspondente também retornará nulo em vez de NPE. Isso simplificará bastante o código sem a necessidade de verificação.
Por meio do BeanPath, você também pode alterar o valor de uma propriedade do objeto se ela não for somente leitura:
personCityProperty.set(person, “Madrid”);
Seguindo a mesma idéia - o mínimo de NPE possível - nesse caso, se um dos objetos intermediários da cadeia for nulo, a biblioteca tentará criá-lo automaticamente e salvá-lo no campo. Para fazer isso, a propriedade correspondente deve ser gravável e a classe de objeto deve ter um construtor público sem parâmetros.
Como um recurso experimental, é oferecida a oportunidade de trabalhar com coleções. Para alguns casos especiais, às vezes é necessário construir caminhos, referindo-se a objetos dentro da coleção. Para isso, é fornecido o método $$, que constrói um link para o último elemento da coleção (considerando-o o único).
final BeanPath<Person, String> personPhonePath = $(Person::getContact).$$(Contact::getPhoneList).$(Phone::getPhone); assertEquals("contact.phoneList.phone", personPhonePath.getPath()); assertEquals(personPhonePath.get(person), person.getContact().getPhoneList() .get(person.getContact().getPhoneList().size()-1).getPhone());
O projeto está hospedado aqui: https://github.com/throwable/beanref , os binários estão disponíveis no repositório do jcenter maven.
Útil
java.beans.Introspector
A classe Introspector do Java Java padrão permite resolver as propriedades da lixeira.
Apache Commons BeanUtils
A biblioteca mais abrangente para trabalhar com Java Beans.
Caminho de feijão
Biblioteca mencionada que faz o mesmo por meio de proxy.
Objenesis
Instanciamos um objeto de qualquer classe com qualquer conjunto de construtores.
Aliases do QueryDSL
Usando classes em proxy para definir critérios no QueryDSL
Jinq
Uma biblioteca interessante que usa lambdas para definir critérios na JPA. Muita mágica: proxying, serializando lambdas, interpretando bytecode.