【问题标题】:List with <set> tag in beans xml-configuration behaves as Setbean xml-configuration 中带有 <set> 标记的列表表现为 Set
【发布时间】:2013-09-04 20:33:14
【问题描述】:

我正在阅读“Spring Recipes”一书,并尝试通过示例来研究 Spring 的“魔力”。这就是我所拥有的。 Bean 类SequenceGenerator

public class SequenceGenerator {
    private List<Object> suffixes;
    //..... 
    public void setSuffixes(List<Object> suffixes) {
        this.suffixes = suffixes;
    }
    public synchronized String getSequence() {
        StringBuffer buffer = new StringBuffer();            
        for (Object suffix : suffixes) {
            buffer.append(suffix);
            buffer.append("-");
        }
        return buffer.toString();
    }
}

主类:

public class Main {
    public static void main(String[] args) {
        ApplicationContext context =
                new ClassPathXmlApplicationContext("beans.xml");
        SequenceGenerator generator =
                (SequenceGenerator) context.getBean("sequenceGeneratorSet");
        System.out.println(generator.getSequence());
    }
}

Xml 配置:

<bean id="sequenceGeneratorSet" class="com.apress.springrecipes.sequence.SequenceGenerator">
        <property name="initial" value="100000"/>
        <property name="suffixes">
            <set>
                <value>A</value>
                <value>A</value>
                <bean class="java.net.URL">
                    <constructor-arg value="http" />
                    <constructor-arg value="www.apress.com" />
                    <constructor-arg value="/" />
                </bean>
            </set>
        </property>
    </bean>

在 xml 中,我有意使用标签来查看结果。我也写了两次相同的值“A”。我注意到 Spring 为“后缀”属性注入了 ArrayList,作为 bean 类中的类型定义。但它的行为类似于输出设置,仅包含一个“A”值。有人知道 Spring 是如何在内部解决这个问题的吗?

【问题讨论】:

  • 可能首先使用您设置的对象创建一个Set&lt;Object&gt;,然后使用其值基于此集合的列表设置suffixes 变量,代码为suffixes = new ArrayList&lt;Object&gt;(theSet);
  • @LuiggiMendoza 比这要长一点,但你的想法是对的。
  • @SotiriosDelimanolis 是的,我想这背后一定有更多的逻辑,因为它正在解析 XML 文件并可能使用反射创建数据,但我真的不喜欢详细介绍案例,只要理解主要思想:)。
  • @LuiggiMendoza 我最近通过调试器和源代码回答了其中一些深入的问题,这真的很有启发性。花了大约 25 分钟,但我认为这是值得的:P。
  • @SotiriosDelimanolis 顺便说一句,我认为应该重新设计 Spring 代码以停止使用过多的 if-else 并使用 Mapenum 来增强当前的设计。

标签: java spring


【解决方案1】:

Spring 有大量的方法调用来生成上下文。当它解析您的 XML 上下文时,Spring 生成一个 RootBeanDefinition 对象来描述您的 bean(类、属性等)和包含属性名称 (&lt;property&gt;) 及其值的 PropertyValues 对象。

在这种情况下,它将创建一个ManagedSet,这是一个

用于保存托管 Set 值的标记集合类,它可以 包含运行时 bean 引用(被解析为 bean 对象)

保存您的 AURL 值。

以上是在任何实际 bean 对象的字段被初始化(BeanPostProcessors 做他们的事情,代理等)之前完成的,尽管创建了实例本身(默认字段值为 null)。您可以通过创建一个空的构造函数并在调试时添加一个断点来看到这一点。

更进一步,在AbstractApplicationContext#refresh() 中,finishBeanFactoryInitialization() 被调用,最终发生初始化。对于之前创建的每个 BeanDefinition 和相应的 PropertyValues,Spring 在 BeanFactory 上调用 applyPropertyValues() 创建您的 bean。

对于您的 suffixes 字段,Spring 发现预期类型与实际类型不匹配。 TypeConverterDelegate 将确定使用哪个 PropertyEditorConversionServicePropertyValuesManagedSet)转换为实际的 Field 类型,即。 List。在这种情况下,它使用CustomCollectionEditor。该编辑器调用 createCollection() 并使用您的 Field 声明为的集合类型。对你来说,那就是List。所以

protected Collection createCollection(Class collectionType, int initialCapacity) {
    if (!collectionType.isInterface()) {
        try {
            return (Collection) collectionType.newInstance();
        }
        catch (Exception ex) {
            throw new IllegalArgumentException(
                    "Could not instantiate collection class [" + collectionType.getName() + "]: " + ex.getMessage());
        }
    }
    else if (List.class.equals(collectionType)) { // US HERE
        return new ArrayList(initialCapacity);
    }
    else if (SortedSet.class.equals(collectionType)) {
        return new TreeSet();
    }
    else {
        return new LinkedHashSet(initialCapacity);
    }
}

它创建一个ArrayList。对于我们之前的Set 中的每个元素,它会在必要时尝试转换元素,然后将其添加到ArrayList。然后使用BeanWrapperImpl 设置属性值。为此,它会通过反射找到您的 setter Method 并使用 ArrayList 调用它。

Spring 对上下文中声明的每个 bean 和每个属性执行相同的逻辑。

【讨论】:

    【解决方案2】:

    所有的魔法都发生在 org.springframework.beans.propertyeditors.CustomCollectionEditor 中。 它负责从上下文提供的对象(在您的示例中为 Set)创建某种类型的属性(在您的情况下为 List):

    public void setValue(Object value) {
        if (value == null && this.nullAsEmptyCollection) {
            super.setValue(createCollection(this.collectionType, 0));
        }
        else if (value == null || (this.collectionType.isInstance(value) && !alwaysCreateNewCollection())) {
            // Use the source value as-is, as it matches the target type.
            super.setValue(value);
        }
        else if (value instanceof Collection) {
            // Convert Collection elements.
            Collection source = (Collection) value;
            Collection target = createCollection(this.collectionType, source.size());
            for (Object elem : source) {
                target.add(convertElement(elem));
            }
            super.setValue(target);
        }
        else if (value.getClass().isArray()) {
            // Convert array elements to Collection elements.
            int length = Array.getLength(value);
            Collection target = createCollection(this.collectionType, length);
            for (int i = 0; i < length; i++) {
                target.add(convertElement(Array.get(value, i)));
            }
            super.setValue(target);
        }
        else {
            // A plain value: convert it to a Collection with a single element.
            Collection target = createCollection(this.collectionType, 1);
            target.add(convertElement(value));
            super.setValue(target);
        }
    }
    
    
    protected Collection createCollection(Class collectionType, int initialCapacity) {
        if (!collectionType.isInterface()) {
            try {
                return (Collection) collectionType.newInstance();
            }
            catch (Exception ex) {
                throw new IllegalArgumentException(
                        "Could not instantiate collection class [" + collectionType.getName() + "]: " + ex.getMessage());
            }
        }
        else if (List.class.equals(collectionType)) {
            return new ArrayList(initialCapacity);
        }
        else if (SortedSet.class.equals(collectionType)) {
            return new TreeSet();
        }
        else {
            return new LinkedHashSet(initialCapacity);
        }
    }
    

    因此,第一步是从您的上下文定义中创建一个 Set,然后通过 PropertyEditor 实现将其转换为 List。查看同一包中的其他 PropertyEditor,其中一些执行类似的隐式转换,例如地图 属性等等。

    【讨论】:

      猜你喜欢
      • 2022-11-20
      • 2021-10-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-18
      • 2021-06-22
      • 2019-05-07
      • 2020-01-26
      相关资源
      最近更新 更多