It seems that AbstractAutowireCapableBeanFactory (where most of the work with BeanWrapper is completed) is hard-coded to use BeanWrapperImpl . There is no distribution point. BeanWrapperImpl uses the CachedIntrospectionResults , which in turn uses the Introspector . There seems to be no way to configure any of these dependencies. We can try to use standard extension points: BeanPostProcessor or BeanFactoryPostProcessor .
Using only BeanPostProcessor will not work, because if we do something like this:
<bean id="beanForInjection" class="com.test.BeanForInjection"> <property name="bean" ref="beanToBeInjected"/> </bean>
where BeanForInjection is the Scala class
package com.test import com.other.BeanToBeInjected class BeanForInjection { var bean : BeanToBeInjected = null; }
and BeanToBeInjected is the bean we want to introduce, then we will catch the exception before the BeanPostProcessor will be able to enter. Beans gets values ββbefore all BeanPostProcessor callbacks.
But we can use the BeanFactoryPostProcessor properties to hide, which are expected to be inserted through Scala-like setters and apply them last.
Something lilke is:
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); } }
This implementation simply checks to see if any bean has properties that are not specified as properties and also have Scala-like setters. All properties matching this contract are removed from the property list and stored as bean attributes. Now all we need to do is extend these attributes (if any) for each bean and apply them. Where we need a BeanPostProcessor (AutowiredAnnotationBeanPostProcessor can be a good example of a 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); } } }
Just create these two Beans in your context:
<bean class="com.other.ScalaAwareBeanFactoryPostProcessor"/> <bean class="com.other.ScalaAwareBeanPostProcessor"/>
Note:
This is not a final decision. It will work for classes, but it will not work for simple types:
<bean id="beanForInjection" class="com.test.BeanForInjection"> <property name="bean" ref="beanToBeInjected"/> <property name="name" value="skaffman"/> </bean>
The solution will work for bean , but not for name . This can be fixed, but at this point, I think you would be better off just using the @BeanInfo annotation.