我有一个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?
克里斯
答案 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应该本地处理流利的设置者,他们不会伤害任何人,它应该忽略返回值。
要求制定者严格无效,它迫使我写出比我本来需要的更多的锅炉板代码。我很欣赏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产生我们想要的结果。以下是减少疼痛的替代方案:
BeanInfo
类BeanInfo
类插入到introspector中。前两个选项可能不适合你,因为它们涉及很多变化。更详细地探索第三个选项:
要知道spring正在实例化哪些bean,请实现自己的BeanFactoryPostProcessor。这可以在BeanFactory使用之前查看所有bean定义。您的实现迭代因子中的所有BeanDefinitions,并从每个定义中获取bean类。现在你知道了所有正在使用的类。
使用类列表,您可以设置为这些类创建自己的BeanInfos。您可以使用Introspector为每个类生成默认BeanInfo,这将为您提供具有返回值设置器的属性的只读属性。然后,您可以根据原始BeanInfo创建一个新的BeanInfo,但使用PropertyDescriptors引用setter方法 - 返回值设置器。
为每个类生成新的beanInfos,您需要确保在询问您的类的beaninfo时,Introspector会返回这些内容。 introspector有一个私有Map,用于缓存beanInfos。您可以通过反射获取此信息,启用访问 - setAccessible(true) - 并将BeanInfo实例添加到其中 - map.put(Class,BeanInfo)
。
当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
。