¿Por qué cambió el comportamiento de PropertyDescriptor de Java 1.6 a 1.7?

Actualización: Oracle ha confirmado esto como un error.

Resumen: Cierta costumbreBeanInfos yPropertyDescriptors que funcionan en JDK 1.6 fallan en JDK 1.7, y algunos solo fallan después de que Garbage Collection se haya ejecutado y borrado ciertas SoftReferences.

Editar: Esto también romperá laExtendedBeanInfo en la primavera 3.1 como se indica en la parte inferior del post.

Editar: Si invoca las secciones 7.1 u 8.3 de la especificación de JavaBeans, explique exactamente dónde se encuentran esas partes de la especificación.exigir cualquier cosa. El lenguaje no es imperativo ni normativo en esas secciones. El lenguaje en esas secciones es el de los ejemplos, que en el mejor de los casos son ambiguos como una especificación. Además, elBeanInfo API específicamente permite que uno cambie el comportamiento predeterminado, y está claramente roto en el segundo ejemplo a continuación.

La especificación de Java Beans busca métodos de establecimiento por defecto con un tipo de retorno nulo, pero permite la personalización de los métodos de obtención y establecimiento mediante unjava.beans.PropertyDescriptor. La forma más sencilla de usarlo ha sido especificar los nombres del captador y el configurador.

new PropertyDescriptor("foo", MyClass.class, "getFoo", "setFoo");

Esto ha funcionado en JDK 1.5 y JDK 1.6 para especificar el nombre del configurador, incluso cuando su tipo de retorno no es nulo como en el caso de prueba a continuación:

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import org.testng.annotations.*;

/**
 * Shows what has worked up until JDK 1.7.
 */
public class PropertyDescriptorTest
{
    private int i;
    public int getI() { return i; }
    // A setter that my people call "fluent".
    public PropertyDescriptorTest setI(final int i) { this.i = i; return this; }

    @Test
    public void fluentBeans() throws IntrospectionException
    {
        // This throws an exception only in JDK 1.7.
        final PropertyDescriptor pd = new PropertyDescriptor("i",
                           PropertyDescriptorTest.class, "getI", "setI");

        assert pd.getReadMethod() != null;
        assert pd.getWriteMethod() != null;
    }
}

El ejemplo de la costumbre.BeanInfos, que permiten el control programático dePropertyDescriptors en la especificación de Java Beans, todos utilizan tipos de retorno nulo para sus definidores, pero nada en la especificación indica que esos ejemplos son normativos, y ahora el comportamiento de esta utilidad de bajo nivel ha cambiado en las nuevas clases de Java, que se rompió Algún código en el que estoy trabajando.

Hay numerosos cambios en eljava.beans el paquete entre JDK 1.6 y 1.7, pero el que hace que esta prueba falle parece estar en esta diferencia:

@@ -240,11 +289,16 @@
        }

        if (writeMethodName == null) {
-       writeMethodName = "set" + getBaseName();
+                writeMethodName = Introspector.SET_PREFIX + getBaseName();
        }

-       writeMethod = Introspector.findMethod(cls, writeMethodName, 1, 
-                 (type == null) ? null : new Class[] { type });
+            Class[] args = (type == null) ? null : new Class[] { type };
+            writeMethod = Introspector.findMethod(cls, writeMethodName, 1, args);
+            if (writeMethod != null) {
+                if (!writeMethod.getReturnType().equals(void.class)) {
+                    writeMethod = null;
+                }
+            }
        try {
        setWriteMethod(writeMethod);
        } catch (IntrospectionException ex) {

En lugar de simplemente aceptar el método con el nombre y los parámetros correctos, elPropertyDescriptor ahora también está verificando el tipo de retorno para ver si es nulo, por lo que el establecedor de fluidos ya no se usa. losPropertyDescriptor arroja unIntrospectionException en este caso: "Método no encontrado: setI".

Sin embargo, el problema es mucho más insidioso que la simple prueba anterior. Otra forma de especificar los métodos getter y setter en elPropertyDescriptor para una costumbreBeanInfo es utilizar el realMethod objetos:

@Test
public void fluentBeansByMethod()
    throws IntrospectionException, NoSuchMethodException
{
    final Method readMethod = PropertyDescriptorTest.class.getMethod("getI");
    final Method writeMethod = PropertyDescriptorTest.class.getMethod("setI",
                                                                 Integer.TYPE);

    final PropertyDescriptor pd = new PropertyDescriptor("i", readMethod,
                                                         writeMethod);

    assert pd.getReadMethod() != null;
    assert pd.getWriteMethod() != null;
}

Ahora el código anteriorserá pase una prueba de unidad tanto en 1.6 como en 1.7, pero el código comenzará a fallar en algún momento durante la vida de la instancia de JVM debido al mismo cambio que hace que el primer ejemplo falle inmediatamente. En el segundo ejemplo, la única indicación de que algo ha salido mal viene cuando se intenta utilizar la costumbre.PropertyDescriptor. El configurador es nulo y la mayoría del código de utilidad considera que la propiedad es de solo lectura.

El código en el diff está dentro.PropertyDescriptor.getWriteMethod(). Se ejecuta cuando elSoftReference sosteniendo el setter realMethod esta vacio. Este código es invocado por elPropertyDescriptor Constructor en el primer ejemplo que toma el método de acceso.nombres arriba porque inicialmente no hayMethod guardado en elSoftReferences sosteniendo el getter real y setter.

En el segundo ejemplo, el método de lectura y el método de escritura se almacenan enSoftReference objetos en elPropertyDescriptor por el constructor, y al principio estos contendrán referencias a lareadMethod ywriteMethod getter y setterMethodSe le da al constructor. Si en algún momento esas referencias blandas se borran, ya que el recolector de basura puede hacer (y lo hará), entonces elgetWriteMethod() código verá que elSoftReference Devuelve nulo, y tratará de descubrir el setter.Esta vez, usando la misma ruta de código dentroPropertyDescriptor eso hace que el primer ejemplo falle en JDK 1.7, establecerá la escrituraMethod anull porque el tipo de retorno no esvoid. (El tipo de retorno esno parte de un Javamétodo de firma.)

Cambiar el comportamiento de esta manera a lo largo del tiempo cuando se usa una costumbreBeanInfo puede ser extremadamente confuso Intentando duplicar las condiciones que hacen que el recolector de basura borre esas particularidadesSoftReferences también es tedioso (aunque tal vez ayuden algunas burlas instrumentales).

La primaveraExtendedBeanInfo La clase tiene pruebas similares a las anteriores. Aquí hay una prueba unitaria de primavera 3.1.1 deExtendedBeanInfoTest que pasará en modo de prueba de unidad, pero el código que se está probando fallará en el modo insidioso posterior a GC:

@Test
public void nonStandardWriteMethodOnly() throws IntrospectionException {
    @SuppressWarnings("unused") class C {
        public C setFoo(String foo) { return this; }
    }

    BeanInfo bi = Introspector.getBeanInfo(C.class);
    ExtendedBeanInfo ebi = new ExtendedBeanInfo(bi);

    assertThat(hasReadMethodForProperty(bi, "foo"), is(false));
    assertThat(hasWriteMethodForProperty(bi, "foo"), is(false));

    assertThat(hasReadMethodForProperty(ebi, "foo"), is(false));
    assertThat(hasWriteMethodForProperty(ebi, "foo"), is(true));
}

Una sugerencia es que podemos mantener el código actual en funcionamiento con los configuradores no nulos evitando que los métodos de establecimiento solo sean fácilmente accesibles. Parece que funcionaría, pero es más bien un truco en torno al cambio de comportamiento en JDK 1.7.

P: ¿Existe alguna especificación definitiva que establezca que los emisores no nulos deben ser un anatema? No he encontrado nada, y actualmente considero que esto es un error en las bibliotecas JDK 1.7. ¿Estoy equivocado, y por qué?

Respuestas a la pregunta(4)

Su respuesta a la pregunta