tags:

views:

1257

answers:

4

Hi,

I'm wondering that if there is a way for binding a spring bean's property to another bean's property so if any change on binded property occurs in runtime, what i expect is referencing bean's property also changes. I'll explain more with a little code snippet.

<bean id="johnHome" class="example.Contact">
    <property name="phone" value="5551333" />
</bean>

<bean id="johnWork" class="example.Contact">
    <property name="phone">
        <util:property-path path="johnHome.phone" />
    </property>
</bean>

OK. This works at initial bean wiring but what i exactly want is to bind property so if the property changes at runtime the referencing bean also changes. If i should like to show with a metaphor it will seem like this.

<bean id="johnHome" class="example.Contact">
    <property name="phone" value="5551333" />
</bean>

<bean id="johnWork" class="example.Contact">
    <property name="phone">
        <util:bind path="johnHome.phone" />
    </property>
</bean>

Am i overloading the spring's concept too much or is this possible without a lot of tricks?

Thanks..

A: 

I don't think what you're doing is possible in Spring 2.5. It may be possible in Spring 3, using the new expression syntax, but I don't think so.

Even if it were, it'd be confusing, I think. Better to stick your shared value into its own class and inject an instance of that class into the other beans that need to share it.

skaffman
A: 

I can think of two possibilities.

One is (it is kind of a hack), if you don't have very many beans that need to be linked like the ones in your example, you could inject johnWork into the johnHome bean, and in johnHome.setPhone you could update the johnWork phone property, something like:

public class Contact {
    private Contact myWorkContact;
    private String phone;

    public void setPhone(String phone) {
        this.phone = phone;
        if (this.myWorkContact != null) {
            this.myWorkContact.setPhone(phone);
        }
    }

    public void setWorkContact(Contact c) {
        this.myWorkContact = c;
    }
}

Or you could have HomeContact and WorkContact both extend a class Contact and do the same injection with that.

If you have tons and tons of beans that will need this (like if your application actually IS dealing with contact information), with AOP (you'll need AspectJ for the example given) I think you could do something like this (it will be a bit memory intensive if you get a ton of objects, but you can see how something like it would work):

Warning: this actually got complicated fast, but I'm pretty sure it would work after you worked out a few kinks

public class Contact {
    ...

    private String phone;
    private String name;
    private Integer id;

    public Contact(Integer id, String name, String phone) {
        this.phone = phone;
        this.name = name;
        this.id = id;
    }

    public void setPhone(String phone) {
        this.phone = phone.
    }

    //Other getters, setters, etc

    ...
}


@Aspect
public class ContactPhoneSynchronizer {
    //there is probably a more efficient way to keep track of contact objects
    //but right now i can't think of one, because for things like a tree, we need to 
    //be able to identify objects with the same name (John Smith), but that
    //have different unique ids, since we only want one of each Contact object
    //in this cache.

    private List<Contact> contacts = Collections.synchronizedList(new ArrayList<Contact>());

    /**
        This method will execute every time someone makes a new Contact object.
        If it already exists, return it from the cache in this.contacts.  Otherwise,
        proceed with the object construction and put that object in the cache.
    **/

    @Around("call(public Contact.new(Integer,String,String)) && args(id,name,phone)")
    public Object cacheNewContact(ProceedingJoinPoint joinPoint, Integer id, String name, String phone) {
        Contact contact = null;

        for (Contact c : contacts) {
            if (id.equals(c.getId()) {
                contact = c;
                break;
            }
        }

        if (contact == null) {
            contact = (Contact) joinPoint.proceed();
            this.contacts.add(contact);            
        }

        return contact;
    }

    /**This should execute every time a setPhone() method is executed on 
        a contact object.  The method looks for all Contacts of the same
        name in the cache and then sets their phone number to the one being passed
        into the original target class.

        Because objects are passed by reference until you do a reassociation, 
        calling c.setPhone on the object in the cache should update the actual
        instance of the object in memory, so whoever has that reference will
        get the updated information.
    **/

    @After("execution(example.Contact.setPhone(String) && args(phone)")
    public void syncContact(JoinPoint joinPoint, String phone) {
        Contact contact = joinPoint.getTarget();

        for (Contact c : this.contacts) {
            if (c.getName().equals(contact.getName()) {
                c.setPhone(phone);
            }
        }
    }
}

Again, there is probably 100 ways you could optimize this, since I'm typing it off the top of my head; that is, if you wanted to go this route in the first place. In theory it should work but I haven't tested it at all.

Anyway, Happy Springing!

Alex Beardsley
+1  A: 

Simplest way - make that property a bean which is referenced by the two other beans, e.g. for a String value have a StringHolder class:

public class StringHolder {
     private String value;

     // setter and getter elided due to author's lazyness

}
Robert Munteanu
+1  A: 

The whole idea behind Spring is (was?) to keep a clean object-oriented design consisting of plain old java objects and use the spring framework to handle the tedious object creation. As for AOP, this should only handle cross-cutting concerns. I'm not at all convinced that this is one of those cases where AOP is a good idea. Your application relies on the behaviour of these phone numbers getting synced to each other, it's one of the main functionalities. As such, your design should reflect this.

Probably the most logical way to handle this specific problem is to make phone numbers their own class (which is also handy if you ever want to distinguish different types of phone numbers).

If you have a PhoneNumber object which takes the number as a constructor argument the mapping becomes trivial:

<bean id="johnFirstPhone" class="example.PhoneNumber">
  <constructor-arg value="5551333" />
</bean>

<bean id="johnHome" class="example.Contact">
  <property name="phone" ref="johnFirstPhone" />
</bean>

<bean id="johnWork" class="example.Contact">
  <property name="phone" ref="johnFirstPhone" />
</bean>

Of course whether you'd map it like this in a static file is another matter, but the thing is in this situation you pretty clearly just need a reference/pointer.

wds