2010-05-24 14 views
14

Tengo una API que estoy convirtiendo en una DSL interna. Como tal, la mayoría de los métodos en mi PoJos devuelven una referencia a esto para poder encadenar los métodos declarativamente como tales (azúcar sintáctico).¿Cómo hacer que Spring acepte facilitadores fluidos (no-vacíos)?

myComponent 
    .setID("MyId") 
    .setProperty("One") 
    .setProperty2("Two") 
    .setAssociation(anotherComponent) 
    .execute(); 

Mi API no depende de la primavera pero deseo que sea 'Primavera-friendly' por ser amigable con los constructores PoJo argumento cero, captadores y definidores. El problema es que Spring parece no detectar mis métodos setter cuando tengo un tipo de devolución no nulo.

El tipo de retorno de esto es muy conveniente cuando encadenar mis comandos, por lo que no quiero destruir mi API programática solo para que sea compatible con la inyección de Spring.

¿Hay alguna configuración en Spring que me permita utilizar setters no válidos?

Chris

+0

Esa es en realidad una idea algo interesante. – alternative

Respuesta

9

Gracias a todos (y especialmente a Espen que hizo un gran esfuerzo para mostrarme las diversas opciones dentro de Spring).

Al final, he encontrado una solución a mí mismo que no requiere configuración de la primavera.

I seguido el enlace de Stephen C entonces encontró una referencia a la clase SimpleBeanInfo dentro de ese conjunto de hilos. Esta clase permite a un usuario escribir su propio código de resolución de método de bean mediante la colocación de otra clase en el mismo paquete que la clase con los fijadores no estándar/getters para anular la lógica de con 'BeanInfo' anexado en el nombre de clase y la aplicación de la 'BeanInfo ' interfaz.

luego hice una búsqueda en Google y encontré este blog el que señaló el camino. La solución en el blog era bastante básica, así que la completé para mis propósitos.

por clase (con emisores de fluidez)

public class MyComponentBeanInfo<T> extends SimpleBeanInfo { 

private final static Class<?> _clazz = MyComponent.class; 
PropertyDescriptor[] _properties = null; 

public synchronized PropertyDescriptor[] getPropertyDescriptors() { 
    if (_properties == null) { 
     _properties = Helpers.getPropertyDescriptionsIncludingFluentSetters(_clazz); 
    } 
    return _properties; 
} 

public BeanDescriptor getBeanDescriptor() { 
    return new BeanDescriptor(_clazz); 
} 
} 

método de generación de PropertyDescriptor

public static PropertyDescriptor[] getPropertyDescriptionsIncludingFluentSetters(Class<?> clazz) { 
    Map<String,Method> getterMethodMap = new HashMap<String,Method>(); 
    Map<String,Method> setterMethodMap = new HashMap<String,Method>(); 
    Set<String> allProperties = new HashSet<String>(); 
    PropertyDescriptor[] properties = null; 
    try { 
     Method[] methods = clazz.getMethods(); 
     for (Method m : methods) { 
      String name = m.getName(); 
      boolean isSetter = m.getParameterTypes().length == 1 && name.length() > 3 && name.substring(0,3).equals("set") && name.charAt(3) >= 'A' && name.charAt(3) <= 'Z'; 
      boolean isGetter = (!isSetter) && m.getParameterTypes().length == 0 && name.length() > 3 && name.substring(0,3).equals("get") && name.charAt(3) >= 'A' && name.charAt(3) <= 'Z'; 

      if (isSetter || isGetter) { 
       name = name.substring(3); 
       name = name.length() > 1 
         ? name.substring(0,1).toLowerCase() + name.substring(1) 
         : name.toLowerCase(); 

       if (isSetter) { 
        setterMethodMap.put(name, m); 
       } else { 
        getterMethodMap.put(name, m); 
       } 
       allProperties.add(name); 
      } 
     } 

     properties = new PropertyDescriptor[allProperties.size()]; 
     Iterator<String> iterator = allProperties.iterator(); 
     for (int i=0; i < allProperties.size(); i++) { 
      String propertyName = iterator.next(); 
      Method readMethod = getterMethodMap.get(propertyName); 
      Method writeMethod = setterMethodMap.get(propertyName); 
      properties[i] = new PropertyDescriptor(propertyName, readMethod, writeMethod); 
     } 
    } catch (IntrospectionException e) { 
     throw new RuntimeException(e.toString(), e); 
    } 
    return properties; 
} 

ventajas de este enfoque:

  • Sin configuración personalizada de muelles (Spring no tiene conocimiento de los incubadores no estándar y los considera normales). No depende de ningún archivo .jar de Spring pero accesible desde Spring.
  • Parece que funciona.

Las desventajas de este enfoque:

  • tengo que colocar crear una clase BeanInfo para todas mis clases de la API con los emisores no estándar. Afortunadamente solo hay alrededor de 10 de estas clases y al mover la lógica de resolución de métodos a una clase separada, solo tengo un lugar para mantener.

Pensamientos finales

En mi opinión, Primavera debería hacer frente a los emisores con fluidez nativa, que no hacen daño a nadie y que sólo se debe pasar por alto el valor de retorno.

Al requerir que los colocadores sean rígidamente nulos, me ha obligado a escribir mucho más código de placa de caldera de lo que hubiera necesitado de otra manera. Aprecio la especificación de frijol, pero la resolución de frijol es trivial usando la reflexión sin siquiera usar el resolutor de frijol estándar, por lo que Spring debería ofrecer la opción de su propio solucionador de frijol que manejará estas situaciones.

Por supuesto, deje el mecanismo estándar como predeterminado, pero ofrezca una opción de configuración de una línea. Espero con ansias futuras versiones donde esto podría ser opcionalmente relajado.

+0

Debo mencionar que este es solo el primer corte de una solución (necesito investigar 'es' y 'puedo' también escribir getol booleanos). Actualizaré a medida que avance más. – Chris

+0

+1: ¡Una alternativa bastante interesante! Pero pagas un alto precio en complejidad para evitar que Spring esté totalmente en tu código. – Espen

+0

N.B. Spring acepta setters "fluidos" (siempre que el método tenga el nombre y los parámetros correctos, puede devolver cualquier cosa) desde 3.1. –

9

¿Hay un ajuste en la primavera para permitir que utilice set no vacío?

La respuesta simple es No, no existe tal configuración.

Spring está diseñado para ser compatible con la especificación JavaBeans, y eso requiere que los ajustadores devuelvan void.

Para un debate, consulte this Spring Forums thread. Hay formas posibles en torno a esta limitación mencionadas en el foro, pero no hay una solución simple, y no creo que nadie informara realmente que habían intentado esto y que funcionó.

+0

Gracias por el enlace al Foro, pero afortunadamente, la respuesta no es difícil, no. Hay formas y medios de evitar las limitaciones dentro de las clases de Introspector de Java sin ser considerado un hack (como la implementación de la interfaz BeanConfig). – Chris

7

El muelle también se puede configurar con Java configuration.

Un ejemplo:

@Configuration 
public class Config { 
    @Bean 
    public MyComponent myComponent() { 
     return MyComponent 
      .setID(id) 
      .setProperty("One", "1") 
      .setProperty("Two", "2") 
      .setAssociation(anotherConfig.anotherComponent()) 
      .execute(); 
    } 

    @Autowired 
    private AnotherConfig anotherConfig; 

    @Value("${id}") 
    private String id; 
} 

tiene un objeto inmutable agradable. ¡Has implementado el patrón de Builder!

Actualizado a responder a los comentarios de Chris:

supongo que no es exactamente lo que quiere, pero utilizando archivos de propiedades soluciona algunos problemas. Ver el campo de id en el ejemplo de arriba.

Si no, puede utilizar FactoryBean patrón de primavera:

public class MyComponentFactory implements FactoryBean<MyComponent> { 

    private MyComponent myComponent; 

    public MyComponentFactory(String id, Property propertyOne, ..) { 
     myComponent = MyComponent 
      .setID(id) 
      .setProperty("One", "1") 
      .set(..) 
      .execute(); 
    } 

    public MyComponent getObject() throws Exception { 
     return myComponent; 
    } 

    public Class<MyComponent> getObjectType() { 
     return MyComponent.class; 
    } 

    public boolean isSingleton() { 
     return false; 
    } 
} 

Con la FactoryBean, usted protege la configuración del objeto devuelto por el método getObject().

En la configuración XML, configura la implementación de FactoryBean. En este caso con <constructor-arg /> elementos.

+0

Gracias, pero parece que estoy forzando a los usuarios de Spring a usar el enfoque de Configuración Java y perdería compatibilidad con el enfoque de configuración XML. Personalmente, prefiero el enfoque de Java como lo ha documentado, pero no quiero hacer esa elección en nombre de los usuarios. – Chris

+0

Gracias de nuevo, pero mi preferencia es hacer que esto funcione sin tener que hacer referencia a Spring de mi producto, ya que Spring no debería recibir ningún tratamiento especial, ya que es solo un método para inyectar propiedades. He encontrado una solución a mi problema ahora, lo publicaré en breve. – Chris

3

Por lo que sé, no hay un simple interruptor. Spring usa la convención de Beans, y espera un setter de vacío. Spring funciona con beans en el nivel de propiedad a través de una instancia de la interfaz BeanWrapper. La implementación predeterminada, BeanWrapperImpl, utiliza la introspección, pero puede crear su propia versión modificada que use la reflexión para encontrar métodos que coincidan con su patrón.

EDIT: mirando el código de Spring, BeanWrapperImpl está cableado en las fábricas de frijoles, no hay una manera simple de reemplazar esto con otra implementación. Sin embargo, como la primavera utiliza la introspección, podemos trabajar para obtener java.beans.Introspector para producir los resultados que queremos. Aquí están las alternativas para disminuir el dolor:

  1. cambie la firma del método en sus incubadoras para cumplir.
  2. implementar su propio BeanInfo clases para cada uno de sus granos de
  3. utilizar la reflexión para tapar generadas dinámicamente BeanInfo clases en el introspector.

Las dos primeras opciones probablemente no sean realmente opciones para usted, ya que implican muchos cambios. Explorar la tercera opción con más detalle:

  1. para saber qué granos se están ejemplificados en la primavera, implementar su propio BeanFactoryPostProcessor. Esto permite ver todas las definiciones de bean antes de ser utilizadas por BeanFactory. Su implementación itera sobre todas las definiciones de Bean en el factor, y obtiene la clase de bean de cada definición. Ahora conoces todas las clases que se usan.

  2. Con una lista de clases, se puede establecer sobre la creación de sus propios BeanInfos para estas clases. Utiliza el Introspector para generar el BeanInfo predeterminado para cada clase, lo que le daría propiedades de solo lectura para sus propiedades con los setters de valor devuelto. A continuación, crea un BeanInfo nuevo, basado en el original, pero con PropertyDescriptors haciendo referencia a los métodos setter - sus ajustadores de valor devuelto.

  3. Con nuevas beanInfos generados para cada clase, es necesario asegurarse de que el Introspector vuelve éstos cuando se le preguntó por el BeanInfo para su clase. El introspector tiene un Mapa privado que se usa para almacenar en caché beanInfos. Puede hacerse con esto a través de la reflexión, permitir el acceso - setAccessible (verdadero) - y añadir sus instancias BeanInfo a ella - map.put(Class,BeanInfo).

  4. Cuando Spring pregunta al Introspector por BeanInfo para su clase de bean, el introspector devuelve su beanInfo modificado, completo con los métodos setter asignados a sus setters con valores devueltos.

+0

Gracias por un análisis detallado. Muy fácil de entender Mi solución es su solución sugerida n. ° 2 (estaba escribiendo esto como su edición). # 3 es una solución interesante, pero requiere que escriba la placa de la caldera Spring (no deseo escribir una sola línea de código específico de Spring o he fallado). Creo que posiblemente, podría escribir un complemento Ant para emitir los objetos BeanInfo automáticamente para las clases relevantes (según el patrón en mi respuesta). Esto se compilaría pero no sería parte de la fuente. – Chris

+0

No hay una plantilla repetitiva en el n. ° 3, solo una clase para encontrar las clases para las que quiere que se genere el beaninfo. (Puede omitir esto y generar una lista de clases en tiempo de compilación, pero generar BeanInfos en tiempo de ejecución). Básicamente es la solución que adoptó, pero lo hizo en tiempo de ejecución en lugar de compilar. Personalmente, estoy a favor de generar beaninfos en tiempo de ejecución, hace que la ejecución/prueba/compilación sea más rápida y la recarga en caliente durante la depuración, pero si por supuesto conoce su situación mejor, así es su decisión. – mdma

4

Una sugerencia simple, es costumbre no usar setters, pero sí los nombres de las propiedades. Así que tener un organismo, y tienen otro método para el constructor:

component.id("MyId") 
    .property("One") 
    .property2("Two") 
    .association(anotherComponent) 
    .execute(); 
2

Como otros han dicho, no se trata sólo de Primavera de uso se arriesga a perder. Un setter no-vacío no es realmente un setter en lo que concierne a JavaBeans, y todo tipo de otras herramientas (validadores, marshallers, viewers, persisters, cualquier otra cosa que pueda soñar) probablemente usará Introspector y BeanInfo, que esperan setters ser nulo

Teniendo esto en cuenta, ¿cuán flexible es el requisito de que se les llame setX? En su lugar, muchas interfaces fluidas en Java usan withX. Si está utilizando Eclipse, probablemente pueda crear una plantilla de generación de código para hacer X getX(), void setX(X x) y X withX(X x) para usted. Si está utilizando alguna otra herramienta de codegen, me imagino que agregar withX métodos de establecimiento/obtención fluidos también sería fácil.

La palabra with parece un poco extraña, pero cuando la ve junto con un constructor se lee muy bien.

Request r = new Request().withEndpoint("example.com") 
         .withPort(80) 
         .withSSL(false) 
         .withFoo("My Foo"); 

service.send(r); 

Uno de estos es la API AWS SDK for Java, que puede consultar para ver ejemplos. Una advertencia fuera del tema es que los getters boolean se pueden llamar isX, pero los getters Boolean deben llamarse getX.

Cuestiones relacionadas