有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java如何让Spring接受流畅(非类)的setter?

我有一个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中是否有允许我使用非无效设置器的设置

克里斯


共 (6) 个答案

  1. # 1 楼答案

    感谢所有人(尤其是埃斯彭,他花了很大的努力在春季向我展示了各种选择)

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

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

    然后我在谷歌上搜索了一下,找到了这个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);
    }
    }
    

    属性描述符生成方法

    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不知道非标准的setter,并且认为它们是正常的)。不依赖任何弹簧。jar文件,但可以从Spring访问
    • 看起来很管用

    这种方法的缺点:

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

    结束思考

    在我看来,Spring应该与流畅的二传手打交道,他们不会伤害任何人,应该忽略返回值

    通过要求setter严格无效,它迫使我编写了比其他情况下需要的多得多的锅炉板代码。我很欣赏Bean规范,但是使用反射而不使用标准的Bean解析器,Bean解析是微不足道的,因此Spring应该提供自己的Bean解析器选项来处理这种情况

    无论如何,将标准机制保留为默认机制,但提供单线配置选项。我期待着未来的版本,在那里这可能是随意放松

  2. # 2 楼答案

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

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

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

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

  3. # 3 楼答案

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

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

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

    前两个选项可能并不适合你,因为它们涉及很多变化。更详细地探讨第三个选项:

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

    2. 有了一个类列表,你就可以开始为这些类创建自己的BeanInfos了。您可以使用内省器为每个类生成默认BeanInfo,这将为带有返回值设置器的属性提供只读属性。然后,基于原始BeanInfo创建一个新的BeanInfo,但PropertyDescriptor引用setter方法,即返回值setter

    3. 在为每个班级生成新的beanInfos后,你需要确保内省者在向你的班级索要beanInfos时返回这些信息。内省者有一个用于缓存beanInfos的私有地图。您可以通过反射来获取这个信息,启用access-setAccessible(true),并将BeanInfo实例添加到其中

    4. 当spring向内省者请求bean类的BeanInfo时,内省者返回修改后的BeanInfo,并使用返回值将setter方法映射到setter

  4. # 4 楼答案

    Spring也可以配置为Java configuration

    举个例子:

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

    你有一个很好的不可变对象。实际上,您已经实现了构建器模式

    更新以回应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 />元素

  5. # 5 楼答案

    正如其他人所说,你可能失去的不仅仅是春天的友好。就JavaBeans而言,非void setter并不是真正的setter,其他各种工具(验证器、封送器、查看器、持久器,以及任何你能想到的工具)可能会使用IntrospectorBeanInfo,它们期望setter为null

    考虑到这一点,要求它们被称为setX有多灵活?Java中很多流畅的接口都使用withX。如果您使用的是Eclipse,您可能可以创建一个代码生成模板来为您生成X getX()void setX(X x)X withX(X x)。如果您使用的是其他一些codegen工具,我可以想象添加withXfluent 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,您可以参考它来获取示例。一个离题的警告是booleangetter可以被称为isX,但是Booleangetter必须被称为getX

  6. # 6 楼答案

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

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