如何让 Spring 接受流畅的(非空的)设置器?

发布于 2024-09-02 23:48:21 字数 501 浏览 5 评论 0原文

我有一个 API,我正在将其转变为内部 DSL。因此,我的 PoJo 中的大多数方法都会返回对此的引用,以便我可以像这样以声明方式将方法链接在一起(语法糖)。

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

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

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

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

克里斯

I have an API which I am turning into an internal DSL. As such, most methods in my PoJos return a reference to this so that I can chain methods together declaratively as such (syntactic sugar).

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

My API does not depend on Spring but I wish to make it 'Spring-Friendly' by being PoJo friendly with zero argument constructors, getters and setters. The problem is that Spring seems to not detect my setter methods when I have a non-void return type.

The return type of this is very convenient when chaining together my commands so I don't want to destroy my programmatic API just be to compatible with Spring injection.

Is there a setting in Spring to allow me to use non-void setters?

Chris

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

梦归所梦 2024-09-09 23:48:21

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

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

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

然后我在 Google 上进行了搜索,找到了这个 博客 指明了方向。博客上的解决方案非常基本,因此我根据自己的目的对其进行了填充。

每个类(具有流畅的 setter)

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 不知道非标准配置)设置者并认为它们是正常的)。不依赖于任何 Spring .jar 文件,但可以从 Spring 访问。
  • 似乎有效。

这种方法的缺点:

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

结束语

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

通过要求设置器严格无效,它迫使我编写比我原本需要的更多的样板代码。我很欣赏 Bean 规范,但是使用反射来解析 Bean 是微不足道的,甚至不需要使用标准 Bean 解析器,因此 Spring 应该提供自己的 Bean 解析器选项来处理这种情况。

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

Thanks to all (and especially Espen who went to a lot of effort to show me the various options within Spring).

In the end, I found a solution myself that doesn't require Spring configuration.

I followed the link from Stephen C then found a reference to the SimpleBeanInfo class within that set of Threads. This class allows a user to write their own bean method resolution code by placing another class in the same package as the class with the non-standard setters/getters to override the logic of with 'BeanInfo' appended onto the classname and implementing the 'BeanInfo' interface.

I then did a search on Google and found this blog which pointed the way. The solution on the blog was quite basic so I padded it out for my purposes.

Per Class (with fluent setters)

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 generation method

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;
}

Advantages to this approach:

  • No custom spring configuration (Spring is not aware of the non-standard setters and sees them as normal). No dependancy on any Spring .jar files but accessible from Spring.
  • Just seems to work.

Disadvantages to this approach:

  • I have to place create a BeanInfo class for all of my API classes with non-standard setters. Luckily there are only around 10 such classes and by moving the method resolution logic into a seperate class I only have one place to maintain.

Closing Thoughts

In my opinion, Spring should deal with fluent setters natively, they don't hurt anyone and it should just ignore the return value.

By requiring that setters be rigidly void, it has forced me to write a lot more boiler plate code than I would have needed otherwise. I appreciate the Bean Specification, but bean resolution is trivial using reflection without even using the standard bean resolver so Spring should offer the option of its own bean resolver that will handle this situations.

By all means, leave the standard mechanism as the default, but offer a one-line configuration option. I look forward to future versions where this might be optionally relaxed.

情愿 2024-09-09 23:48:21

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

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

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

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

Is there a setting in Spring to allow me to use non-void setters?

The simple answer is No - there is no such setting.

Spring is designed to be compatible with the JavaBeans spec, and that requires the setters to return void.

For a discussion, refer to this Spring Forums thread. There are possible ways around this limitation mentioned in the forum, but there is no simple solution, and I don't think anyone actually reported that they had tried this and that it worked.

谎言 2024-09-09 23:48:21

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 实现。在本例中使用 元素。

Spring can also be configured with Java configuration.

An example:

@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;
}

You have a nice immutable object. You have actually implemented the Builder pattern!

Updated to respond to Chris's comment:

I guess it's not exactly what you want, but using properties files solves some issues. See the id field in the example above.

Else, you can use Spring's FactoryBean pattern:

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;
    }
}

With the FactoryBean, you shield the configuration from the object returned from the getObject() method.

In the XML configuration, you configure the FactoryBean implementation. In this case with <constructor-arg /> elements.

甲如呢乙后呢 2024-09-09 23:48:21

一个简单的建议是,通常不使用 setter,而是使用属性名称本身。因此,有一个设置器,并为构建器提供另一种方法:

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

One simple suggestion, it is customary not to use setters, but the properties names themselves. So have a setter, and have another method for the builder:

component.id("MyId")
    .property("One")
    .property2("Two")
    .association(anotherComponent)
    .execute();
猫卆 2024-09-09 23:48:21

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

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

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

前两个选项可能并不适合您,因为它们涉及相当多的更改。更详细地探索第三个选项:

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

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

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

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

As far as I know, there is no simple switch. Spring uses the Beans convention, and expects a void setter. Spring works with beans at the property level via an instance of the BeanWrapper interface. The default implementation, BeanWrapperImpl, uses introspection, but you could create your own modified version that uses reflection to find methods matching your pattern.

EDIT: Looking at the Spring code, BeanWrapperImpl is hard-wired into the bean factories, there is no simple way to replace this with another implementation. However, as spring uses introspection, we can work on getting java.beans.Introspector to produce the results we want. Here are the alternatives in order of decreasing pain:

  1. change the method signature on your setters to comply.
  2. implement your own BeanInfo classes for each of your beans
  3. Use reflection to plug dynamically generated BeanInfo classes into the introspector.

The first two options are probably not really options for you, as they involve quite a lot of changes. Exploring the third option in more detail:

  1. To know which beans are being instantiated by spring, implement your own BeanFactoryPostProcessor. This gets to see all the bean definitions before they are used by the BeanFactory. Your implementation iterates over all the BeanDefinitions in the factor, and fetches the bean class from each definition. Now you know all the classes that are being used.

  2. With a list of classes, you can set about creating your own BeanInfos for these classes. You use the Introspector to generate the default BeanInfo for each class, which would give you read-only properties for your properties with return value setters. You then create a new BeanInfo, based on the original, but with PropertyDescriptors referencing setter methods - your return value setters.

  3. With new beanInfos generated for each class, you need to make sure that the Introspector returns these when asked for the beaninfo for your class. The introspector has a private Map that is used to cache beanInfos. You can get hold of this via reflection, enable access - setAccessible(true) - and add your BeanInfo instances to it - map.put(Class,BeanInfo).

  4. When spring asks the Introspector for the BeanInfo for your bean class, the introspector returns your modified beanInfo, complete with setter methods mapped to your setters with return values.

静谧幽蓝 2024-09-09 23:48:21

正如其他人所说,您可能失去的不仅仅是春天的友好性。就 JavaBean 而言,非 void setter 并不是真正的 setter,各种其他工具(验证器、编组器、查看器、持久器,以及您能想到的任何其他工具)可能会使用 IntrospectorBeanInfo,它期望 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 是 AWS SDK for Java,您可以参考其中的示例。一个离题的警告是,boolean getters 可能被称为 isX,但 Boolean getters 必须被称为 getX

As others have said, it's not just Spring-friendliness you risk losing. A non-void setter isn't really a setter as far as JavaBeans are concerned, and all sorts of other tools (validators, marshallers, viewers, persisters, whatever else you can dream up) will probably use Introspector and BeanInfo, which expect setters to be null.

With this in mind, how flexible is the requirement that they be called setX? A lot of fluent interfaces in Java use withX instead. If you're using Eclipse, you can probably create a code generation template to make X getX(), void setX(X x), and X withX(X x) for you. If you're using some other codegen tool, I can imagine adding withX fluent setter/getter methods would also be easy.

The with word seems a bit odd, but when you see it alongside a constructor it reads really well.

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

service.send(r);

One such API is the AWS SDK for Java, which you can consult for examples. An off-topic caveat is that boolean getters may be called isX, but Boolean getters must be called getX.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文