2010-07-16 42 views
7

我希望能够使用Spring将setter注入到Scala组件中。不幸的是,Scala的本地setter命名与JavaBeans标准foo_=不同,而不是setFoo。 Scala确实提供了一些解决方法,这些注解强制创建JavaBeans setter/getters以及本地Scala,但这需要注释我希望注入的每个组件。更方便的是覆盖Spring使用的BeanWrapper,其中一个知道如何处理Scala风格的getter和setter。有什么办法使用自定义BeanWrapper实现来加载应用程序上下文

似乎没有任何关于如何做这样的事情的文档,或者它是否可行,也没有任何其他人的在线示例。于是潜入源之前,我想我会点击这里

+0

一个有趣的想法,我很想知道它去哪里 – skaffman 2010-07-16 20:25:55

+0

理想情况下,它将是我目前称为“spork”的一组库的一个组件,它将提供适用于Scala的适配器和普通的皮条客Java企业库。 – 2010-07-16 20:37:30

+0

您也可以考虑BeanInfo注释。我不确定Spring是使用bean信息还是仅查找getter和setter。 – mkneissl 2010-07-16 22:01:56

回答

3

它看起来像AbstractAutowireCapableBeanFactory以下链接(大部分BeanWrapper的工作都已完成)被硬编码为使用BeanWrapperImpl。那里没有延伸点。 BeanWrapperImpl使用CachedIntrospectionResults,其依次使用Introspector。看起来像没有办法配置任何这些依赖关系。我们可以尝试使用标准的延伸点:BeanPostProcessorBeanFactoryPostProcessor

只需使用BeanPostProcessor将无法​​正常工作,因为如果我们正在做这样的事情:

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

其中BeanForInjection是斯卡拉类

package com.test 
import com.other.BeanToBeInjected 

class BeanForInjection { 
    var bean : BeanToBeInjected = null; 
} 

BeanToBeInjected是我们要注入一个bean,那么我们会在BeanPostProcessor有机会介入之前发现异常。在调用BeanPostProcessor的任何回调之前,Bean会填充值。

但是我们可以使用BeanFactoryPostProcessor'隐藏'预计将通过Scala-like setters注入的属性并将其应用于后者。

东西lilke这样的:

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

此实现只是简单地检查任何bean有没有被列为性质,也有斯卡拉,像setter属性。所有符合此合同的属性都将从属性列表中删除并保存为该bean的属性。现在,我们需要的是为每个bean提供这些属性(如果有的话)并应用它们。有我们需要的地方BeanPostProcessor(AutowiredAnnotationBeanPostProcessor可以是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); 
     } 
    } 
} 

在你的情况下简单地创建这两个豆类:

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

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

注:

这不是一个最终的解决方案。它将为类的工作,但它不会简单的工种:

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

解决方案会为bean工作,但不是为name。这可以被修复,但是在这一点上,我认为使用@BeanInfo注释会更好。

+0

下面的想法是什么......实现一个自定义的类加载器,它只会对加载以* BeanInfo结尾的类的尝试作出反应,检查主类是否为Scala类并在运行中生成适当的BeanInfo对象。这种方法不仅适用于Spring,而且适用于符合JavaBeans规范的通用代码。 – 2012-01-10 07:32:57

相关问题