Есть ли способ загрузить контекст приложения, используя пользовательскую реализацию BeanWrapper - PullRequest
7 голосов
/ 17 июля 2010

Я хотел бы иметь возможность использовать Spring, используя сеттерную инъекцию в компоненты Scala. К сожалению, собственные сеттеры Scala названы не так, как стандарт JavaBeans, foo_=, а не setFoo. В Scala есть несколько обходных путей: аннотации, которые заставляют создавать установщики / получатели JavaBeans, а также собственные Scala, но для этого требуется аннотировать каждый компонент, который я хочу внедрить. Гораздо удобнее было бы переопределить BeanWrapper, используемый Spring, с тем, который знал, как обрабатывать методы получения и установки в стиле Scala.

Похоже, что нет никакой документации о том, как сделать такую ​​вещь, возможно ли это сделать, и нет онлайн-примеров того, как кто-то другой это делает. Поэтому, прежде чем погрузиться в источник, я решил проверить здесь

Ответы [ 2 ]

3 голосов
/ 18 июля 2010

Похоже, 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 получит шанс вмешаться. Бины будут заполнены значениями до вызова любых обратных вызовов 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-подобные установщики. Все свойства, соответствующие этому контракту, удаляются из списка свойств и сохраняются как атрибуты компонента. Теперь все, что нам нужно, это получить эти атрибуты (если таковые имеются) для каждого компонента и применить их. Вот где нам нужно 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.

1 голос
/ 18 июля 2010

Интересный вопрос. Вам могут пригодиться следующие ссылки

http://www.grails.org/Extended+Data+Binding+Plugin#Application-wide Конфигурация DataBinder и BeanWrapper

http://blog.krecan.net/2008/06/17/spring-field-injection/

...