2010-07-16 11 views
7

Me gustaría poder usar Spring mediante la inyección de setter en los componentes de Scala. Desafortunadamente, los setters nativos de Scala tienen un nombre diferente del estándar JavaBeans, foo_= en lugar de setFoo. Scala proporciona un par de soluciones para esto, anotaciones que fuerzan la creación de setters/getters de JavaBeans así como de Scala nativos, pero eso requiere anotar cada componente que deseo inyectar. Mucho más conveniente sería anular el BeanWrapper utilizado por Spring con uno que supiera cómo manejar getters y setters de estilo Scala.¿Hay alguna manera de cargar un contexto de aplicación utilizando una implementación personalizada de BeanWrapper?

No parece haber ninguna documentación sobre cómo hacer tal cosa o si es factible, ni ejemplos en línea de que alguien más lo esté haciendo. Así que antes de sumergirse en la fuente, pensé que había que comprobar aquí

+0

Una idea interesante, me gustaría saber a dónde va – skaffman

+0

Idealmente, será un componente de un conjunto de bibliotecas que actualmente llamo "spork", que proporcionará adaptadores y proxenetas amigables con Scala. Bibliotecas de empresa Java. –

+0

También podría considerar la anotación BeanInfo. No estoy seguro de si Spring usa la información de bean o solo busca getters y setters. – mkneissl

Respuesta

3

Parece que AbstractAutowireCapableBeanFactory (donde se realiza la mayor parte del trabajo con BeanWrapper) está codificado para usar BeanWrapperImpl. No hay punto de extensión allí. BeanWrapperImpl usa CachedIntrospectionResults que usa Introspector a su vez. Parece que no hay forma de configurar ninguna de estas dependencias. Podemos tratar de usar puntos de extensión estándar: BeanPostProcessor o BeanFactoryPostProcessor.

Utilizando sólo BeanPostProcessor no va a funcionar, porque si estamos haciendo algo como esto:

<bean id="beanForInjection" class="com.test.BeanForInjection"> 
    <property name="bean" ref="beanToBeInjected"/>   
</bean> 

donde BeanForInjection es una clase Scala

package com.test 
import com.other.BeanToBeInjected 

class BeanForInjection { 
    var bean : BeanToBeInjected = null; 
} 

y BeanToBeInjected es un grano que queremos inyectar, entonces atraparemos la excepción antes de que BeanPostProcessor tenga la oportunidad de intervenir. Los beans se llenan con valores antes de llamar a cualquier devolución de llamada de BeanPostProcessor.

Pero podemos usar BeanFactoryPostProcessor para 'ocultar' propiedades que se espera que se inyecten a través de setters similares a Scala y aplicarlas más tarde.

Algo lilke esto:

package com.other; 

import ... 

public class ScalaAwareBeanFactoryPostProcessor implements BeanFactoryPostProcessor, PriorityOrdered { 

    ... PriorityOrdered related methods... 

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 
     String[] beanNames = beanFactory.getBeanDefinitionNames(); 
     for (String currentName : beanNames) { 
      BeanDefinition beanDefinition = beanFactory.getBeanDefinition(currentName); 
      processScalaProperties(beanDefinition); 
     } 
    } 

    protected void processScalaProperties(BeanDefinition beanDefinition) { 
     String className = beanDefinition.getBeanClassName(); 
     try { 
      Set<PropertyValue> scalaProperties = new HashSet<PropertyValue>(); 
      for (PropertyValue propertyValue : beanDefinition.getPropertyValues().getPropertyValueList()) { 
       String scalaSetterName = ScalaAwarePostProcessorUtils.getScalaSetterName(propertyValue.getName()); 

       BeanInfo beanInfo = getBeanInfo(className); 
       PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); 
       MethodDescriptor[] methodDescriptors = beanInfo.getMethodDescriptors(); 
       for (MethodDescriptor md : methodDescriptors) { 

        if (scalaSetterName.equals(md.getName())) { 
         boolean isScalaProperty = true; 
         for (PropertyDescriptor pd : propertyDescriptors) { 
          if (propertyValue.getName().equals(pd.getName())) { 
           isScalaProperty = false; 
          } 
         } 
         if (isScalaProperty) { 
          scalaProperties.add(propertyValue); 
         } 
        } 
       } 
      } 

      if (!scalaProperties.isEmpty()) { 
       beanDefinition.setAttribute(ScalaAwarePostProcessorUtils.SCALA_ATTRIBUTES_KEY, scalaProperties); 
      } 

      for (PropertyValue propertyValue : scalaProperties) { 
       beanDefinition.getPropertyValues().removePropertyValue(propertyValue); 
      } 
     } catch (ClassNotFoundException e) { 
     } catch (IntrospectionException e) { 
     } 
    } 

    private BeanInfo getBeanInfo(String className) throws ClassNotFoundException, IntrospectionException { 
     Class beanClass = Class.forName(className); 
     BeanInfo beanInfo = Introspector.getBeanInfo(beanClass); 
     cleanIntrospectorCache(beanClass); 
     return beanInfo; 
    } 

    private void cleanIntrospectorCache(Class beanClass) { 
     Class classToFlush = beanClass; 
     do { 
      Introspector.flushFromCaches(classToFlush); 
      classToFlush = classToFlush.getSuperclass(); 
     } 
     while (classToFlush != null); 
    } 
} 

Esta aplicación es simplemente comprueba cualquier frijol tiene propiedades que no se muestran como propiedades y también tienen emisores de Scala-like. Todas las propiedades que coinciden con este contrato se eliminan de la lista de propiedades y se guardan como atributos del bean. Ahora, todo lo que necesitamos es extraer estos atributos (si los hay) para cada grano y aplicarlos. Ahí es donde necesitamos BeanPostProcessor (AutowiredAnnotationBeanPostProcessor puede ser un buen ejemplo de BeanPostProcessor).

package com.other; 

public class ScalaAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter 
    implements PriorityOrdered, BeanFactoryAware { 

    private ConfigurableListableBeanFactory beanFactory; 

    ... Order related stuff... 

    public void setBeanFactory(BeanFactory beanFactory) { 
     if (beanFactory instanceof ConfigurableListableBeanFactory) { 
      this.beanFactory = (ConfigurableListableBeanFactory) beanFactory; 
     } 
    } 

    @Override 
    public PropertyValues postProcessPropertyValues(PropertyValues pvs,  PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException { 
     try { 
      InjectionMetadata metadata = findScalaMetadata(beanFactory.getBeanDefinition(beanName), bean.getClass()); 
      metadata.inject(bean, beanName, pvs); 
     } 
     catch (Throwable ex) { 
      throw new BeanCreationException(beanName, "Injection of Scala dependencies failed", ex); 
     } 
     return pvs; 
    } 

    private InjectionMetadata findScalaMetadata(BeanDefinition beanDefinition, Class<?> beanClass) throws IntrospectionException { 
     LinkedList<InjectionMetadata.InjectedElement> elements = new LinkedList<InjectionMetadata.InjectedElement>(); 

     Set<PropertyValue> scalaProperties = (Set<PropertyValue>) beanDefinition.getAttribute(ScalaAwarePostProcessorUtils.SCALA_ATTRIBUTES_KEY); 
     if (scalaProperties != null) { 
      for (PropertyValue pv : scalaProperties) { 
       Method setter = ScalaAwarePostProcessorUtils.getScalaSetterMethod(beanClass, pv.getName()); 
       if (setter != null) { 
        Method getter = ScalaAwarePostProcessorUtils.getScalaGetterMethod(beanClass, pv.getName()); 
        PropertyDescriptor pd = new PropertyDescriptor(pv.getName(), getter, setter); 
        elements.add(new ScalaSetterMethodElement(setter, pd)); 
       } 
      } 
     } 
     return new InjectionMetadata(beanClass, elements); 
    } 

    private class ScalaSetterMethodElement extends InjectionMetadata.InjectedElement { 

     protected ScalaSetterMethodElement(Member member, PropertyDescriptor pd) { 
      super(member, pd); 
     } 

     @Override 
     protected Object getResourceToInject(Object target, String requestingBeanName) { 
      Method method = (Method) this.member; 
      MethodParameter methodParam = new MethodParameter(method, 0); 
      DependencyDescriptor dd = new DependencyDescriptor(methodParam, true); 
      return beanFactory.resolveDependency(dd, requestingBeanName); 
     } 
    } 
} 

Basta con crear estos dos granos en su contexto:

<bean class="com.other.ScalaAwareBeanFactoryPostProcessor"/> 

<bean class="com.other.ScalaAwareBeanPostProcessor"/> 

Nota:

Esto no es una solución definitiva.Se trabajará para las clases, pero no va a funcionar para tipos simples:

<bean id="beanForInjection" class="com.test.BeanForInjection"> 
    <property name="bean" ref="beanToBeInjected"/>   
    <property name="name" value="skaffman"/> 
</bean> 

solución funcionará para bean, pero no para name. Esto puede arreglarse, pero en este momento creo que será mejor que simplemente use la anotación @BeanInfo.

+0

¿Qué pasa con la siguiente idea? Implemente un cargador de clases personalizado que solo reaccione a los intentos de cargar clases que terminen en * BeanInfo, verifique si la clase maestra es una clase Scala y genere el objeto BeanInfo apropiado sobre la marcha. Este enfoque debería funcionar no solo para Spring, sino también para el código general conforme a las especificaciones de JavaBeans. –

Cuestiones relacionadas