Я хотел бы смочь использовать Spring с помощью инжекции метода set в компоненты Scala. К сожалению, по-другому по сравнению с собственными методами set Scala называют, чем стандарт JavaBeans, foo_=
вместо setFoo
. Scala действительно обеспечивает несколько обходных решений для этого, аннотации, которые вызывают создание методов set/методов считывания JavaBeans, а также собственного компонента Scala, но это требует аннотирования каждого компонента, который я хочу ввести. Намного более удобный должен был бы переопределить BeanWrapper
используемый Spring с тем, который знал, как обработать методы считывания Scala-стиля и методы set.
Кажется, нет никакой документации относительно того, как сделать такую вещь или выполнимо ли это даже, ни какие-либо примеры онлайн кого-либо еще делающего его. Таким образом, прежде, чем погрузиться в источник, я полагал, что проверю здесь
Похоже, что AbstractAutowireCapableBeanFactory
(где выполняется большая часть работы с BeanWrapper) жестко запрограммирован для использования BeanWrapperImpl
. Нет смысла расширяться. BeanWrapperImpl
использует CachedIntrospectionResults
, который, в свою очередь, использует Introspector
. Похоже, что нет возможности настроить любую из этих зависимостей. Мы можем попробовать использовать стандартные точки расширения: BeanPostProcessor
или BeanFactoryPostProcessor
.
Использование только BeanPostProcessor
не сработает, потому что если мы сделаем что-то вроде этого:
<bean id="beanForInjection" class="com.test.BeanForInjection">
<property name="bean" ref="beanToBeInjected"/>
</bean>
где BeanForInjection
- это класс Scala
package com.test
import com.other.BeanToBeInjected
class BeanForInjection {
var bean : BeanToBeInjected = null;
}
и BeanToBeInjected
] - это bean-компонент, который мы хотим внедрить, тогда мы перехватим исключение до того, как BeanPostProcessor
получит возможность вмешаться. Bean-компонент заполняется значениями до того, как будут вызваны какие-либо обратные вызовы BeanPostProcessor
.
Но мы можем использовать BeanFactoryPostProcessor
, чтобы «скрыть» свойства, которые, как ожидается, будут введены через Scala-подобные сеттеры, и применить их позже.
Что-то вроде этого:
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-компонента свойства, которые не указаны как свойства, а также есть ли у них сеттеры, подобные Scala. Все свойства, соответствующие этому контракту, удаляются из списка свойств и сохраняются как атрибуты компонента. Теперь все, что нам нужно, это извлечь эти атрибуты (если есть) для каждого 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-компонента в своем контексте:
<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.
Интересный вопрос, Вам могут быть полезны следующие ссылки
http://www.grails.org/Extended+Data+Binding+Plugin#Application-wide DataBinder и BeanWrapper configuration