如何使Spring接受流利(非虚空)的设置者?

时间:2010-05-24 23:54:34

标签: java spring reflection introspection fluent-interface

我有一个API,我正在变成一个内部DSL。因此,我的PoJos中的大多数方法返回对此的引用,以便我可以声明性地将方法链接在一起(语法糖)。

myComponent
    .setID("MyId")
    .setProperty("One")
    .setProperty2("Two")
    .setAssociation(anotherComponent)
    .execute();

我的API不依赖于Spring,但我希望通过PoJo友好,零参数构造函数,getter和setter使其成为“Spring-Friendly”。问题是当我有一个非void返回类型时,Spring似乎没有检测到我的setter方法。

在将命令链接在一起时,返回类型非常方便,因此我不想破坏我的编程API,只是为了与Spring注入兼容。

Spring中是否有设置允许我使用非空的setter?

克里斯

6 个答案:

答案 0 :(得分:9)

  

Spring中是否有设置允许我使用非空的setter?

简单的答案是否 - 没有这样的设置。

Spring旨在与JavaBeans规范兼容,这需要setter返回void

有关讨论,请参阅this Spring Forums thread。有可能解决这个限制在论坛中提到,但没有简单的解决方案,我认为没有人真正报告他们已经尝试过这个并且它有效。

答案 1 :(得分:9)

感谢所有人(尤其是Espen,他花了很多精力向我展示Spring中的各种选项)。

最后,我找到了一个不需要Spring配置的解决方案。

我按照Stephen C的链接,然后在该组Thread中找到了对SimpleBeanInfo类的引用。此类允许用户通过将另一个类放在与具有非标准setter / getters的类相同的包中来编写自己的bean方法解析代码,以覆盖附加到类名并将'BeanInfo'附加到类名并实现'BeanInfo的逻辑'界面。

然后我在Google上进行了搜索,发现这个blog指明了方向。博客上的解决方案非常基础,所以我为了我的目的填写了它。

每班(使用流利的设定者)

public class MyComponentBeanInfo<T> extends SimpleBeanInfo {

private final static Class<?> _clazz = MyComponent.class;
PropertyDescriptor[] _properties = null;

public synchronized PropertyDescriptor[] getPropertyDescriptors() {
    if (_properties == null) {
        _properties = Helpers.getPropertyDescriptionsIncludingFluentSetters(_clazz);
    }
    return _properties;
}

public BeanDescriptor getBeanDescriptor() {
    return new BeanDescriptor(_clazz);
}
}

PropertyDescriptor生成方法

public static PropertyDescriptor[] getPropertyDescriptionsIncludingFluentSetters( Class<?> clazz) {
    Map<String,Method> getterMethodMap = new HashMap<String,Method>();
    Map<String,Method> setterMethodMap = new HashMap<String,Method>();
    Set<String> allProperties = new HashSet<String>();
    PropertyDescriptor[] properties = null;
    try {
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            String name = m.getName();
            boolean isSetter = m.getParameterTypes().length == 1 && name.length() > 3 && name.substring(0,3).equals("set") && name.charAt(3) >= 'A' && name.charAt(3) <= 'Z';
            boolean isGetter = (!isSetter) && m.getParameterTypes().length == 0 && name.length() > 3 && name.substring(0,3).equals("get") && name.charAt(3) >= 'A' && name.charAt(3) <= 'Z';

            if (isSetter || isGetter) {
                name = name.substring(3);
                name = name.length() > 1
                        ? name.substring(0,1).toLowerCase() + name.substring(1)
                        : name.toLowerCase();

                if (isSetter) {
                    setterMethodMap.put(name, m);
                } else {
                    getterMethodMap.put(name, m);
                }
                allProperties.add(name);
            }
        }

        properties = new PropertyDescriptor[allProperties.size()];
        Iterator<String> iterator = allProperties.iterator();
        for (int i=0; i < allProperties.size(); i++) {
            String propertyName = iterator.next();
            Method readMethod = getterMethodMap.get(propertyName);
            Method writeMethod = setterMethodMap.get(propertyName);
            properties[i] = new PropertyDescriptor(propertyName, readMethod, writeMethod);
        }
    } catch (IntrospectionException e) {
        throw new RuntimeException(e.toString(), e);
    }
    return properties;
}

此方法的优点:

  • 没有自定义弹簧配置(Spring不知道非标准的setter并且看起来正常)。不依赖于任何Spring .jar文件,但可以从Spring访问。
  • 似乎工作正常。

此方法的缺点:

  • 我必须使用非标准setter为我的所有API类创建一个BeanInfo类。幸运的是,只有大约10个这样的类,通过将方法解析逻辑移动到一个单独的类中,我只有一个地方可以维护。

结束思考

在我看来,Spring应该本地处理流利的设置者,他们不会伤害任何人,它应该忽略返回值。

要求制定者严格无效,它迫使我写出比我本来需要的更多的锅炉板代码。我很欣赏Bean规范,但是使用反射的bean解析很简单,甚至没有使用标准的bean解析器,所以Spring应该提供自己的bean解析器选项来处理这种情况。

无论如何,请将标准机制保留为默认值,但提供单行配置选项。我期待未来的版本可以选择放宽。

答案 2 :(得分:7)

也可以使用Java configuration配置Spring。

一个例子:

@Configuration
public class Config {
    @Bean
    public MyComponent myComponent() {
        return MyComponent
            .setID(id)
            .setProperty("One", "1")
            .setProperty("Two", "2")
            .setAssociation(anotherConfig.anotherComponent())
            .execute();
    }

    @Autowired
    private AnotherConfig anotherConfig;

    @Value("${id}")
    private String id;
}

你有一个很好的不可变对象。您实际上已实现了Builder模式!

更新以回应Chris的评论:

我想这不是你想要的,但使用属性文件可以解决一些问题。请参阅上面示例中的id字段。

否则,您可以使用Spring的FactoryBean模式:

public class MyComponentFactory implements FactoryBean<MyComponent> {

    private MyComponent myComponent;

    public MyComponentFactory(String id, Property propertyOne, ..) {
        myComponent = MyComponent
            .setID(id)
            .setProperty("One", "1")
            .set(..)
            .execute();
    }

    public MyComponent getObject() throws Exception {
        return myComponent;
    }

    public Class<MyComponent> getObjectType() {
        return MyComponent.class;
    }

    public boolean isSingleton() {
        return false;
    }
}

使用FactoryBean,可以保护配置与从getObject()方法返回的对象。

在XML配置中,您配置FactoryBean实现。在这种情况下使用<constructor-arg />元素。

答案 3 :(得分:4)

一个简单的建议,习惯上不使用setter,但属性名称本身。所以有一个setter,并为构建器提供另一种方法:

component.id("MyId")
    .property("One")
    .property2("Two")
    .association(anotherComponent)
    .execute();

答案 4 :(得分:3)

据我所知,没有简单的开关。 Spring使用Beans约定,并期望一个void setter。 Spring通过BeanWrapper接口的实例在属性级别使用bean。默认实现BeanWrapperImpl使用内省,但您可以创建自己的修改版本,使用反射来查找与您的模式匹配的方法。

编辑:看看Spring代码,BeanWrapperImpl已经很难连接到bean工厂,没有简单的方法可以用另一个实现替换它。但是,当spring使用内省时,我们可以努力让java.beans.Introspector产生我们想要的结果。以下是减少疼痛的替代方案:

  1. 更改您的setter上的方法签名以符合规定。
  2. 为每个bean实现自己的BeanInfo
  3. 使用反射将动态生成的BeanInfo类插入到introspector中。
  4. 前两个选项可能不适合你,因为它们涉及很多变化。更详细地探索第三个选项:

    1. 要知道spring正在实例化哪些bean,请实现自己的BeanFactoryPostProcessor。这可以在BeanFactory使用之前查看所有bean定义。您的实现迭代因子中的所有BeanDefinitions,并从每个定义中获取bean类。现在你知道了所有正在使用的类。

    2. 使用类列表,您可以设置为这些类创建自己的BeanInfos。您可以使用Introspector为每个类生成默认BeanInfo,这将为您提供具有返回值设置器的属性的只读属性。然后,您可以根据原始BeanInfo创建一个新的BeanInfo,但使用PropertyDescriptors引用setter方法 - 返回值设置器。

    3. 为每个类生成新的beanInfos,您需要确保在询问您的类的beaninfo时,Introspector会返回这些内容。 introspector有一个私有Map,用于缓存beanInfos。您可以通过反射获取此信息,启用访问 - setAccessible(true) - 并将BeanInfo实例添加到其中 - map.put(Class,BeanInfo)

    4. 当spring向Introspector询问bean类的BeanInfo时,introspector会返回修改后的beanInfo,并使用setter方法将其映射到具有返回值的setter。

答案 5 :(得分:2)

正如其他人所说,不仅仅是春天友好,你还有失败的风险。就JavaBeans而言,非void setter实际上并不是一个setter,各种其他工具(验证器,marshallers,查看器,persisters,以及其他任何你能想到的东西)可能会使用Introspector和{ {3}},期望setter为null。

考虑到这一点,他们被称为setX的要求有多灵活? Java中许多流畅的接口使用withX代替。如果您正在使用Eclipse,则可以创建一个代码生成模板,为您制作X getX()void setX(X x)X withX(X x)。如果您正在使用其他一些codegen工具,我可以想象添加withX流畅的setter / getter方法也很容易。

with这个词看起来有点奇怪,但当你看到它与构造函数一起时,它读得非常好。

Request r = new Request().withEndpoint("example.com")
                         .withPort(80)
                         .withSSL(false)
                         .withFoo("My Foo");

service.send(r);

一个这样的API是BeanInfo,您可以参考示例。一个偏离主题的警告是boolean获取者可能被称为isX,但Boolean获取者必须被称为getX