views:

229

answers:

6

Hi,

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

+5  A: 

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.

Stephen C
Thanks for the link to the Forum, but luckily, the answer is not a hard-no. There are ways and means to get around the limitations within the Java Introspector classes themselves without being considered a hack (such as implementing the BeanConfig interface).
Chris
+5  A: 

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.

Espen
Thanks, but it seems then that I am forcing Spring users to use the Java Configuration approach and I would lose compatibility with the XML configuration approach.Personally, I prefer the Java approach as you have documented, but I don't want to make that choice on behalf of the users.
Chris
Thanks again, but my preference is to get this working without referencing Spring from my product as Spring should not get any special treatment as it is just one method of many for injecting properties. I have found a solution to my own problem now, I will post shortly.
Chris
+2  A: 

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.

mdma
Thanks for a detailed analysis. Very easy to understand.My solution is your #2 suggested solution (I was writing this as your did your edit). #3 is an interesting solution but requires me to write Spring boiler plate (I do not wish to write a single line of Spring specific code or I've failed).I think possibly, I could write an Ant plugin to emit the BeanInfo objects automatically for the relevant classes (based on the pattern in my answer). This would be compiled in but not be part of the source.
Chris
There's no boilerplate involved in #3, just a class to find the classes you want beaninfo generated for. (You could skip this and generate a list of classes at compile time, but generate BeanInfos at runtime.) It's basically the solution you adopted but done at runtime rather than compile time. Personally, I'd favor generating beaninfos at runttime - makes run/test/compile quicker, and hot-reloading during debugging possible, but if course you know your situation best so it's your call.
mdma
+4  A: 

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.

Chris
I should mention that this is just my first cut of a solution (need to investigate 'is' and 'can' type boolean getters too). I'll update as I develop further.
Chris
+1: A quite interesting alternative! But you pay a high price in complexity to avoid Spring totally in your code.
Espen
+1  A: 

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();
Bozho
+2  A: 

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.

jasonmp85