Sucedió históricamente que en Java para las propiedades de los objetos (propiedades) no se proporciona una entidad física. Las propiedades en Java son algunas convenciones en campos de nombres y métodos de acceso (accesores) para ellos. Y, aunque la presencia de propiedades físicas en el lenguaje simplificaría muchos casos (a partir de la generación tonta de getter-setters), parece que en un futuro cercano la situación en Java no cambiará.
Sin embargo, cuando se desarrollan aplicaciones comerciales multicapa y se utilizan diversos marcos para mapear y vincular datos, a menudo es necesario pasar una referencia a una propiedad de objeto. Consideremos qué opciones hay para esto.
Usar nombre de propiedad
Hasta ahora, la única forma generalmente aceptada de referirse a una propiedad de objeto es una cadena con su nombre. La biblioteca subyacente usa reflexión o introspección para buscar métodos de acceso y campos de acceso. Para hacer referencia a objetos anidados, generalmente se usa la siguiente notación:
person.contact.address.city
El problema con este método es la falta de control sobre la ortografía del nombre y el tipo de propiedad con todo lo que sigue:
- No hay control de errores en la etapa de compilación. Puede cometer un error en el nombre, puede aplicarlo a la clase incorrecta, el tipo de propiedad no está controlado. Además, tenemos que escribir pruebas bastante estúpidas.
- Sin apoyo del IDE. Muy cansado cuando mepe más de 200 campos. Es bueno si hay un junio para esto, que se puede desactivar.
- Sofisticada refactorización de código. Cambie el nombre del campo y de inmediato se caerán muchas cosas. Los buenos IDE también mostrarán cientos de lugares donde se produce una palabra similar.
- Soporte y análisis de código. Queremos ver dónde se usa la propiedad, pero "Buscar usos" no mostrará la cadena.
Como resultado, todavía queremos tener una referencia de propiedad estática de tipo seguro. Un captador es el mejor candidato para este papel, porque:
- Obligado a una clase específica
- Contiene el nombre de la propiedad.
- Tiene tipo
¿Cómo puedo referirme a un getter?
Proxy
Una de las formas interesantes es proxying (o moking) objetos para interceptar la cadena de llamada getter, que se utiliza en algunas bibliotecas: Mockito , QueryDSL , BeanPath . Sobre el último sobre Habré había un artículo del autor.
La idea es bastante simple, pero no es trivial de implementar (un ejemplo del artículo mencionado).
Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) );
Mediante la generación de código dinámico, se crea una clase proxy especial que hereda de la clase bean e intercepta todas las llamadas getter en la cadena, construyendo una ruta en la variable ThreadLocal. En este caso, no se produce la llamada de estos captadores del objeto.
En este artículo consideraremos un método alternativo.
Enlaces de métodos
Con el advenimiento de Java 8, llegaron las lambdas y la capacidad de usar referencias de métodos. Por lo tanto, sería natural tener algo como:
Person person = … assertEquals("name", $(Person::getName).getPath());
El método $ acepta la siguiente lambda en la que se pasa la referencia del captador:
public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda)
El problema es que debido a la eliminación de tipo, no hay forma de obtener los tipos BEAN y TYPE en tiempo de ejecución, y tampoco hay información sobre el nombre del captador: el método que se llama "fuera" es Function.apply ().
Sin embargo, hay un cierto truco: este es el uso de lambda serializada.
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();
La clase SerializedLambda contiene toda la información necesaria sobre la clase y el método llamados. Lo siguiente es una cuestión de tecnología.
Como trabajo mucho con estructuras de datos, este método me animó a escribir una pequeña biblioteca para acceso estático a las propiedades.
Biblioteca BeanRef
Usar la biblioteca se parece a esto:
Person person = ...
y no requiere la magia de la generación de código y dependencias de terceros. En lugar de una cadena getter, se usa una cadena lambda con referencia a getters. Al mismo tiempo, se respeta la seguridad de tipos y la finalización automática basada en IDE funciona bastante bien:

Puede usar el nombre del captador en notación estándar (getXXX () / isXXX ()) y no estándar (xxx ()). La biblioteca intentará encontrar el setter correspondiente, y si está ausente, la propiedad se declara de solo lectura.
Para acelerar el rendimiento, las propiedades resueltas se almacenan en caché, y cuando lo vuelve a llamar con el mismo lambda, el resultado ya está guardado.
Además del nombre de propiedad / ruta, utilizando el objeto BeanPath, puede acceder al valor de propiedad del objeto:
Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person);
Además, si el objeto intermedio en la cadena es nulo, la llamada correspondiente también devolverá nulo en lugar de NPE. Esto simplificará enormemente el código sin requerir verificación.
A través de BeanPath, también puede cambiar el valor de una propiedad de objeto si no es de solo lectura:
personCityProperty.set(person, “Madrid”);
Siguiendo la misma idea, con el menor NPE posible, en este caso, si uno de los objetos intermedios de la cadena es nulo, la biblioteca intentará crearlo automáticamente y guardarlo en el campo. Para hacer esto, la propiedad correspondiente debe ser grabable y la clase de objeto debe tener un constructor público sin parámetros.
Como característica experimental, se ofrece la oportunidad de trabajar con colecciones. Para algunos casos especiales, a veces es necesario construir rutas, haciendo referencia a objetos dentro de la colección. Para hacer esto, se proporciona el método $$, que construye un enlace al último elemento de la colección (considerando que es el ú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());
El proyecto está alojado aquí: https://github.com/throwable/beanref , los binarios están disponibles en el repositorio jcenter maven.
Útil
java.beans.Introspector
La clase Introspector del estándar Java Java le permite resolver las propiedades del contenedor.
Apache Commons BeanUtils
La biblioteca más completa para trabajar con Java Beans.
Beanpath
Biblioteca mencionada que hace lo mismo a través de proxy.
Objenesis
Instanciamos un objeto de cualquier clase con cualquier conjunto de constructores.
Alias DSL de consulta
Uso de clases proxy para establecer criterios en QueryDSL
Jinq
Una biblioteca interesante que utiliza lambdas para establecer criterios en JPA. Mucha magia: proxy, serialización de lambdas, interpretación de bytecode.