Как связать свойство bean-компонента с другим и наблюдать за изменениями в Spring Framework

Мне интересно, если есть способ привязать свойство bean-компонента spring к свойству другого bean-компонента, поэтому, если какое-либо изменение связанного свойства происходит во время выполнения, я ожидаю, что ссылка на свойство bean-компонента также изменится. Я объясню больше с небольшим фрагментом кода.

<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. Это работает при начальном подключении bean-компонента, но я точно хочу связать свойство, поэтому, если свойство изменяется во время выполнения, ссылочный bean-компонент также изменяется. Если я захочу показать с помощью метафоры, это будет выглядеть так.

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

Я слишком сильно перегружаю концепцию пружины или это возможно без особых ухищрений?

Спасибо..


person Community    schedule 21.05.2009    source источник


Ответы (4)


Самый простой способ - сделать это свойство bean-компонентом, на который ссылаются два других bean-компонента, например. для значения String есть класс StringHolder:

public class StringHolder {
     private String value;

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

}
person Robert Munteanu    schedule 01.06.2009

Вся идея Spring заключается (была ли?) в том, чтобы сохранить чистый объектно-ориентированный дизайн, состоящий из простых старых объектов Java, и использовать структуру Spring для обработки утомительного создания объектов. Что касается АОП, это должно касаться только сквозных проблем. Я совсем не уверен, что это один из тех случаев, когда АОП — хорошая идея. Ваше приложение зависит от поведения этих телефонных номеров, которые синхронизируются друг с другом, это одна из основных функций. Таким образом, ваш дизайн должен отражать это.

Вероятно, наиболее логичный способ решить эту конкретную проблему — сделать телефонные номера отдельным классом (что также удобно, если вы когда-нибудь захотите различать разные типы телефонных номеров).

Если у вас есть объект PhoneNumber, который принимает номер в качестве аргумента конструктора, сопоставление становится тривиальным:

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

Конечно, отобразите ли вы его таким образом в статическом файле, это другой вопрос, но дело в том, что в этой ситуации вам просто нужна ссылка/указатель.

person wds    schedule 01.07.2009

Я не думаю, что то, что вы делаете, возможно в Spring 2.5. Это может быть возможным в Spring 3 с использованием нового синтаксиса выражений, но я так не думаю.

Даже если бы это было так, я думаю, это бы сбивало с толку. Лучше вставить ваше общее значение в его собственный класс и внедрить экземпляр этого класса в другие компоненты, которые должны его разделить.

person skaffman    schedule 21.05.2009

Я могу думать о двух возможностях.

Один из них (это своего рода хак), если у вас не очень много bean-компонентов, которые необходимо связать, как в вашем примере, вы можете внедрить johnWork в bean-компонент johnHome, а в johnHome.setPhone вы можете обновить Свойство телефона johnWork, например:

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

Или вы можете сделать так, чтобы HomeContact и WorkContact расширяли класс Contact и делали с ним одно и то же внедрение.

Если у вас есть тонны и тонны bean-компонентов, которым это понадобится (например, если ваше приложение действительно имеет дело с контактной информацией), с AOP (вам понадобится AspectJ для приведенного примера), я думаю, вы могли бы сделать что-то вроде этого (это будет немного интенсивно использует память, если вы получаете тонну объектов, но вы можете видеть, как что-то подобное будет работать):

Предупреждение: на самом деле это быстро усложнилось, но я почти уверен, что это сработает после того, как вы устраните несколько недочетов

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

Опять же, есть, вероятно, 100 способов оптимизировать это, так как я печатаю это сразу; то есть, если вы хотели пойти по этому пути в первую очередь. Теоретически должно работать, но я не проверял.

В любом случае, счастливой весны!

person Alex Beardsley    schedule 01.06.2009