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

Chr*_*ris 14 java reflection spring fluent-interface introspection

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

myComponent
    .setID("MyId")
    .setProperty("One")
    .setProperty2("Two")
    .setAssociation(anotherComponent)
    .execute();
Run Code Online (Sandbox Code Playgroud)

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

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

Spring中有一个设置允许我使用非void setter吗?

克里斯

Ste*_*n C 9

Spring中有一个设置允许我使用非void setter吗?

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

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

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


Chr*_*ris 9

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

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

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

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

每班(有流利的设定者)

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);
}
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

这种方法的优点:

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

这种方法的缺点:

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

闭幕思考

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

通过要求定位器严格无效,它迫使我编写了比我本来需要的更多的锅炉板代码.我很欣赏Bean规范,但是即使没有使用标准的bean解析器,bean的解析也很简单,所以Spring应该提供自己的bean解析器选项来处理这种情况.

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


Esp*_*pen 7

Spring也可以配置Java配置.

一个例子:

@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;
}
Run Code Online (Sandbox Code Playgroud)

你有一个很好的不可变对象.您实际上已经实现了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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

在XML配置中,您可以配置FactoryBean实现.在这种情况下与<constructor-arg />元素.