2010-07-16 11 views
7

Vorrei poter utilizzare Spring utilizzando setter injection nei componenti di Scala. Sfortunatamente, i setter nativi di Scala hanno un nome diverso rispetto allo standard JavaBeans, foo_= piuttosto che setFoo. Scala fornisce un paio di soluzioni alternative per questo, annotazioni che costringono la creazione di JavaBeans setter/getter e di quelli nativi di Scala, ma che richiede di annotare ogni componente che desidero iniettare. Molto più conveniente sarebbe l'override del BeanWrapper usato da Spring con uno che sapeva come gestire getter e setter in stile Scala.Esiste un modo per caricare un contesto applicativo utilizzando un'implementazione personalizzata di BeanWrapper

Non sembra esserci alcuna documentazione su come fare una cosa del genere o se sia fattibile, né alcun esempio online di chiunque altro lo faccia. Quindi, prima di immergersi nella fonte, ho pensato di controllare qui

+0

Un'idea interessante, sarei curioso di vedere dove va – skaffman

+0

Idealmente, sarà un componente di un set di librerie che sto attualmente chiamando "spork", che fornirà adattatori e papponi di scala comuni alla Scala Librerie aziendali Java. –

+0

Si potrebbe anche considerare l'annotazione BeanInfo. Non sono sicuro che Spring usi le informazioni sui bean o cerchi solo getter e setter. – mkneissl

risposta

3

Esso si presenta come AbstractAutowireCapableBeanFactory (dove la maggior parte del lavoro con BeanWrapper è fatto) è hardcoded per usare BeanWrapperImpl. Nessun punto di estensione lì. BeanWrapperImpl utilizza CachedIntrospectionResults che utilizza a sua volta Introspector. Sembra che non ci sia modo di configurare nessuna di queste dipendenze. Possiamo provare a utilizzare i punti di estensione standard: BeanPostProcessor o BeanFactoryPostProcessor.

Utilizzando solo BeanPostProcessor non funzionerà, perché se stiamo facendo qualcosa di simile:

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

dove BeanForInjection è una classe Scala

package com.test 
import com.other.BeanToBeInjected 

class BeanForInjection { 
    var bean : BeanToBeInjected = null; 
} 

e BeanToBeInjected è un fagiolo che vogliamo iniettare, quindi otterremo un'eccezione prima che BeanPostProcessor abbia la possibilità di intervenire. I bean vengono popolati con valori prima che vengano richiamati i callback di BeanPostProcessor.

Ma possiamo usare BeanFactoryPostProcessor per "nascondere" le proprietà che dovrebbero essere iniettate tramite setter tipo Scala e applicarle.

Qualcosa lilke questo:

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); 
    } 
} 

Questa implementazione controlla semplicemente è un qualsiasi fagiolo ha proprietà che non sono elencati come proprietà e hanno anche setter Scala-like. Tutte le proprietà che corrispondono a questo contratto vengono rimosse dall'elenco delle proprietà e salvate come attributi del bean. Ora, tutto ciò di cui abbiamo bisogno è di estrarre questi attributi (se ce ne sono) per ogni bean e applicarli. C'è dove abbiamo bisogno di BeanPostProcessor (AutowiredAnnotationBeanPostProcessor può essere un buon esempio di 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); 
     } 
    } 
} 

È sufficiente creare questi due fagioli nel vostro contesto:

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

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

Nota:

Questa non è una soluzione definitiva.Si lavorerà per le classi, ma non funziona per i tipi semplici:

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

soluzione funzionerà per bean, ma non per name. Questo può essere risolto, ma a questo punto penso che starai meglio usando l'annotazione @BeanInfo.

+0

E la seguente idea ... Implementa un classloader personalizzato che reagirebbe solo ai tentativi di caricare le classi che terminano in * BeanInfo, controlla se la classe master è una classe Scala e genera l'oggetto BeanInfo appropriato al volo. Questo approccio dovrebbe funzionare non solo per Spring, ma anche per codice generale conforme alle specifiche JavaBeans. –