Есть ли любой способ загрузить контекст приложения с помощью пользовательской реализации BeanWrapper

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

Кажется, нет никакой документации относительно того, как сделать такую вещь или выполнимо ли это даже, ни какие-либо примеры онлайн кого-либо еще делающего его. Таким образом, прежде, чем погрузиться в источник, я полагал, что проверю здесь

7
задан skaffman 16 July 2010 в 20:08
поделиться

2 ответа

Похоже, что 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.

3
ответ дан 7 December 2019 в 14:28
поделиться

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

http://www.grails.org/Extended+Data+Binding+Plugin#Application-wide DataBinder и BeanWrapper configuration

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

1
ответ дан 7 December 2019 в 14:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: