java - 有什么方法可以使用自定义 BeanWrapper 实现来加载应用程序上下文

标签 java spring scala javabeans

我希望能够通过 setter 注入(inject)到 Scala 组件中来使用 Spring。不幸的是,Scala 的本地 setter 的命名方式与 JavaBeans 标准不同,foo_= 而不是 setFoo。 Scala 确实为此提供了一些解决方法,强制创建 JavaBeans setter/getter 以及原生 Scala 的注释,但这需要注释我希望注入(inject)的每个组件。更方便的方法是用一个知道如何处理 Scala 风格的 getter 和 setter 的工具覆盖 Spring 使用的 BeanWrapper

似乎没有关于如何做这样的事情或它是否可行的任何文档,也没有任何其他人这样做的在线示例。所以在深入研究源代码之前,我想我应该在这里查看

最佳答案

看起来 AbstractAutowireCapableBeanFactory(BeanWrapper 的大部分工作在这里完成)被硬编码为使用 BeanWrapperImpl。那里没有扩展点。 BeanWrapperImpl 使用 CachedIntrospectionResults ,后者又使用 Introspector。看起来没有办法配置任何这些依赖项。我们可以尝试使用标准的扩展点:BeanPostProcessorBeanFactoryPostProcessor

只使用 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 是我们要注入(inject)的 bean,然后我们将在 BeanPostProcessor 有机会介入之前捕获异常。Bean 在任何回调之前填充值调用了 BeanPostProcessor

但是我们可以使用 BeanFactoryPostProcessor 来“隐藏”预期通过类似 Scala 的 setter 注入(inject)的属性,然后再应用它们。

类似这样的东西:

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 的 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:

<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 注释。

关于java - 有什么方法可以使用自定义 BeanWrapper 实现来加载应用程序上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3268488/

相关文章:

java - 使用 Apache OpenNLP 查找以空格分隔的名称

java - 如何在 mybatis 和 spring boot 中使用一对一映射?

scala - 误解的Akka变得/变得不受欢迎

java - 运行 junit 测试失败。无法创建新的浏览器 session

java - 从Java类访问struts bean的值

java - Wildfly 10.1.0 - 找不到 Mysql 数据源

java - Spring表单绑定(bind)下拉对象

xml - 如何在 Spring MVC 中配置自定义 MediaType?

scala - akka http 中的 cors 存在 headerValueByName 问题 (akka-http-cors)

scala - SBT:为具有不同依赖项的两个 Scala 版本交叉构建项目