14

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

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

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

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

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

克里斯

4

6 回答 6

9

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

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

Spring 被设计为与 JavaBeans 规范兼容,这要求设置器返回void.

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

于 2010-05-25T00:08:49.833 回答
9

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

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

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

然后我在谷歌上进行了搜索,发现了这个为我指明方向的博客。博客上的解决方案非常基本,因此我将其填充为我的目的。

每个班级(使用流利的设置器)

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 不知道非标准设置器并将它们视为正常)。不依赖于任何 Spring .jar 文件,但可以从 Spring 访问。
  • 只是似乎工作。

这种方法的缺点:

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

结束的想法

在我看来,Spring 应该原生处理 fluent setter,它们不会伤害任何人,它应该忽略返回值。

通过要求 setter 严格无效,它迫使我编写了比我需要的更多的样板代码。我很欣赏 Bean 规范,但是即使不使用标准 bean 解析器,使用反射也很容易解决 bean,因此 Spring 应该提供自己的 bean 解析器选项来处理这种情况。

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

于 2010-05-25T01:59:29.093 回答
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;
}

你有一个很好的不可变对象。您实际上已经实现了 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 />元素。

于 2010-05-25T00:09:29.527 回答
4

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

component.id("MyId")
    .property("One")
    .property2("Two")
    .association(anotherComponent)
    .execute();
于 2010-05-25T04:18:53.767 回答
3

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

编辑:查看 Spring 代码,BeanWrapperImpl它被硬连接到 bean 工厂中,没有简单的方法可以用另一个实现替换它。然而,由于 spring 使用自省,我们可以努力让java.beans.Introspector产生我们想要的结果。以下是减轻疼痛的顺序:

  1. 更改设置器上的方法签名以符合要求。
  2. BeanInfo为每个 bean实现自己的类
  3. 使用反射将动态生成BeanInfo的类插入自省。

前两个选项对您来说可能不是真正的选项,因为它们涉及很多更改。更详细地探索第三个选项:

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

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

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

  4. 当 spring 向 Introspector 询问您的 bean 类的 BeanInfo 时,introspector 返回您修改后的 beanInfo,并带有映射到您的带有返回值的 setter 的 setter 方法。

于 2010-05-25T00:13:24.297 回答
2

正如其他人所说,您可能失去的不仅仅是对春天的友好。就 JavaBeans 而言,非 void setter 并不是真正的 setter,并且各种其他工具(验证器、编组器、查看器、持久化器,以及您能想到的任何其他工具)可能会使用IntrospectorBeanInfo,它们需要 setter为空。

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

这个with词看起来有点奇怪,但是当你在构造函数旁边看到它时,它读起来真的很好。

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

service.send(r);

AWS SDK for Java就是一个这样的 API ,您可以查阅它以获取示例。一个离题的警告是boolean可能会调用getter isX,但Boolean必须调用 getter getX

于 2010-05-25T04:21:26.960 回答