Глава 3 Spring in Action 2th edition |
РАСШИРЕННОЕ СВЯЗЫВАНИЕ КОМПОНЕНТОВ
Эта глава охватывает
- Создание бинов родитель/потомок
- Пользовательские редакторы свойств
- Пост-обработка бинов
- Динамически скриптовые бины
Большинство людей имеют по крайней мере один ящик (а иногда даже целый шкаф или кабинет) в своем доме, где хранятся разные мелочи. Хотя его часто называют просто - "выдвижной ящик", но довольно много разных весьма полезных штуковин в конечном итоге, может оказаться, там. Такие вещи, как рулетка, зажимы, ручки, карандаши, чертежные кнопки, несколько запасных батарей, и т.д. как правило, всегда найдутся дома в подобном месте. Обычно, вы не используете эти предметы каждый день, но вы точно знаете что если отключат электричество, вы пороетесь в этом ящике и обязательно найдете батареи, которые вставите в ваш фонарь.
В главе 2 я показал Вам базовые механизмы связывания компонентов Spring, применяемые - ежедневно. Нет сомнения, что у Вас будет много возможностей использовать эти методы в Ваших приложениях основанных на Spring. Однако в данной главе мы немного пороемся в выдвижном ящике Spring контейнеров.
Хотя темы охватываемые в ней вполне практичны для ваших потребностей, они почти не найдут такого же применения, как те, что мы обсуждали в главе 2. Не часто вам будет необходимо изменить метод в компоненте или создать бин который знает свое имя. И не в каждом проекте необходимо вводить класс Ruby в Java приложение, основанное на Spring.
Так как эта глава касается ряда довольно необычных способностей Spring, вы можете захотеть пропустить ее и двигаться вперед к аспектно-ориентированным функциям Spring в главе 4. Но эта глава будет все еще здесь, ожидая , когда вы начнете нуждаться в ней. И если вы окажетесь тут, то увидите, как можно получить ваши компоненты разными экстремальными способами. А начнем мы ее с того, как создать и расширить абстрактные компоненты.
Source(s): Глава 3 Spring in Action 2th edition
Объявление родителей и потомков бинов.
Одна из основ объектно-ориентированного программирования - возможность создать класс, который расширяет другой класс. Новый класс наследует все свойства и методы родительского класса, но он в состоянии представить новые свойства и методы или даже переопределить свойства и методы родителя. Если вы читаете эту книгу, вы вероятно Java программист и подклассы для вас отнюдь не новость .
Но знаете ли вы, что ваши Spring компоненты подобным же образом могут "под-бинить" другие Spring бины? Объявление <bean> в Spring типично содержит атрибут class, чтобы указать тип бина, и некоторое количество(иногда- нулевое) <property>-элементов, чтобы внедрять их значения в свойства бина. И абсолютно все об этом бине объявлено в одном месте: в нем самом, в его декларации - <bean>.
Но создание множества отдельных объявлений бинов может сделать вашу конфигурацию Spring громоздкой и хрупкой. Иерархия классов в Java, создается — чтобы собрать общую функциональность и свойства в родительских классах, которые могут быть затем наследованы дочерними классами
Именно по тем же самым причинам, вы сочтете полезным создавать бины, которые бы расширяли и наследовали другие определения бинов.
И это - отличный способ сократить количество избыточного определения контекста Spring в конфигурационных XML файлах. Чтобы обеспечить функционал "бинов-потомков", элемент <bean> предоставляет два специальных атрибута:
- parent - указывает на идентификатор бина, который будет предком бина с атрибутом parent. Атрибут parent указывает, что бин расширяет Java класс.
- abstract — если установлено в true, указывает, что бин объявлен как абстрактный. То есть, он никогда не должен быть создан Spring.
Чтобы показать возможности под-компонентов Spring, вернемся к соревнованиям Spring Idol
Source(s): Глава 3 Spring in Action 2th edition
Абстрактный тип бинов
Как вы помните, в главе 2 Кенни был соперником тех, кто выступал в соревновании как Музыкант.
Определенно, специализация Кенни это - саксофон. Кенни был объявлен в Spring как бин используя следующий XML
<bean id="kenny" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="Jingle Bells" />
<property name="instrument" ref="saxophone" />
</bean>
За то время, что вы читали главу 2, новый соперник вступил в соревнования. Так совпало, что Давид тоже саксофонист. Более того, он должен играть тоже самое, что и Кенни. Бин Давида был объявлен в Spring следующим образом:
<bean id="david" class="com.springinaction.springidol.Instrumentalist">
<property name="song" value="Jingle Bells" />
<property name="instrument" ref="saxophone" />
</bean>
Сейчас у нас есть два бина, объявленные в Spring фактически одинаковыми XML. Как показано на рисунке 3.1, Единственное различие между двумя этими компонентами - только их идентификаторы. Сейчас это может показаться не большой проблемой, но представьте, что может произойти, если у нас будет на конкурсе более 50 саксофонистов , которые все как один захотят исполнить одну и ту же мелодию. Как тут не сойти с ума объявляя их в конфигурации ?
Очевидным решением проблемы может быть запрет нескольким участникам далее вступать в состязание. Но мы уже создали прецедент, разрешив выступление Давида. Кроме того, нам нужен пример "бина-потомка". Так что мы вынуждены отложить начало состязания. Другое решение это создать бин, который будет родителем для всех наших соперничающих саксофонистов.
Компонент baseSaxophonist должен будет исполнть этот трюк:
<bean id="baseSaxophonist"
class="com.springinaction.springidol.Instrumentalist" abstract="true">
<property name="instrument" ref="saxophone" />
<property name="song" value="Jingle Bells" />
</bean>
Бин baseSaxophonist почти не отличается от бинов kenny и david на первый взгляд. Но заметьте, что его атрибут abstract установлен в true.
Это сообщает Spring не пытаться инстанциировать этот компонент ... даже если будет явный запрос от контейнера.
Во многом это тот же абстрактный Java класс, который не может быть создан Хотя компонент baseSaxophonist невозможно инстанциировать, он все еще очень полезен, потому что он содержит общие свойства, которые присущи Кенни и Давиду. Поэтому сейчас мы можем объявить компоненты kenny и david следующим образом:
<bean id="kenny" parent="baseSaxophonist" />
<bean id="david" parent="baseSaxophonist" />
Атрибут parent показывает, что оба компонента kenny и david будут наследовать свое определение от компонента baseSaxophonist. Заметьте, атрибута class нет. Это потому, что компоненты kenny и david наследуют класс родительского компонента а также его свойства. Сейчас избыточные XML ушел и эти бины стали выглядеть более простыми и краткими.
Здесь уместно упомянуть, что родительские бины могут быть и не абстрактными. Разумеется, вполне возможно создать "бин-потомок" который расширяет конкретный бин. Но в нашем случае мы знаем, что у Spring нет никаких причин создавать экземпляр бина baseSaxophonist, поэтому мы объявили его как abstract.
Переопределение наследуемых свойств.
Предположим, что другой саксофонист вступил в состязание (я говорил вам, что это будет происходить) Но этот саксофонист будет исполнять “Mary Had a Little Lamb” вместо “Jingle Bells.” Означает ли это, что мы не можем переопределить baseSaxophonist когда будем объявлять нового участника?
Конечно, нет. Мы все еще можем переопределять baseSaxophonist. Но вместо того, чтобы применить все значения наследуемых свойств, мы должны переопределить свойство song.
Следующий XML объявляет нового саксофониста:
<bean id="frank" parent="baseSaxophonist">
<property name="song" value="Mary had a little lamb" />
</bean>
Компонент frank будет по-прежнему наследовать класс и свойства компонента baseSaxophonist. Но, как видно на рисунке 3.3, frank переопределяет свойство song так, что он может исполнять песню о девочке и её мохнатом друге. Во многих отношениях объявления родительских и дочерних бинов отражают возможность родительских и дочерних классов, определенных в Java. Но затаите дыхание ... я покажу вам, что Spring наследование может делать нечто, чего невозможно сделать в Java.
Общие абстрактные свойства.
В конкурсе талантов Spring Idol может быть несколько соперников с музыкальными способностями. Как вы уже видели, у нас есть несколько музыкантов, которые исполняют песню на своих инструментах. Но они могут быть также исполнителями, которые используют свои голоса чтобы петь эти песни.
Предположим, что в конкурсе талантов Spring Idol есть два участника, первый - вокалист, а другой - гитарист, которые могут исполнять какую-то песню. Сконфигурированные как разные компоненты, эти участники могут быть похожи на такие конфигурационные файлы:
<bean id="taylor" class="com.springinaction.springidol.Vocalist">
<property name="song" value="Somewhere Over the Rainbow" />
</bean>
<bean id="stevie" class="com.springinaction.springidol.Instrumentalist">
<property name="instrument" ref="guitar" />
<property name="song" value="Somewhere Over the Rainbow" />
</bean>
Как и прежде, эти два компонента разделяют какую-то общую конфигурацию. Определенно, они оба имеют одно и тоже значение атрибута song. Но образцы в предыдущих секциях не похожи, два компонента не разделяют общий класс. Так что мы не можем создать общий родительский компонент для них.
Или можем? В Java, потомки наследуют общий базовый тип, который определен их родительским компонентом. В том смысле, что в Java нет способа для класса потомка расширить общий тип, наследуя свойства и/или методы но не наследуя класс общего родителя. Однако в Spring, под-компонент не обязан наследовать тип общего родителя. Два бина с совершенно разными значениями атрибута class все еще могут разделять общие настройки свойств, которые унаследованы от родительского компонента.
Рассмотрим объявление следующего родительского бина:
<bean id="basePerformer" abstract="true">
<property name="song" value="Somewhere Over the Rainbow" />
</bean>
Этот компонент basePerformer объявляет общее свойство song, которое разделят наши два исполнителя. Но заметьте, что он не имеет установленного атрибута class. Это нормально, потому-что каждый потомок будет определять свой тип в своем собственном атрибуте class. Вот новое определение taylor и stevie:
<bean id="taylor"
class="com.springinaction.springidol.Vocalist" parent="basePerformer" />
<bean id="stevie"
class="com.springinaction.springidol.Instrumentalist" parent="basePerformer">
<property name="instrument" ref="guitar" />
</bean>
Отметьте, что оба эти компонента используют атрибуты class и parent вместе с другими. Такое разделение и наследование свойств общего базового бина возможно для двух и более компонентов
Как показано на рисунке 3.4, свойство song наследовано из компонента basePerformer, даже если каждый наследник имеет полностью различный и неродственный тип. Бин-наследование обычно используют для уменьшения объема XML, необходимого для объявления аспектов и транзакций. В главе 6 (раздел 6.4.2) вы увидите, как бин-наследование может значительно уменьшить количество избыточного XML, когда объявляются транзакции с TransactionProxyFactoryBean.
Но сейчас самое время продолжить рыться в ящике, чтобы посмотреть, какие другие полезные инструменты мы можем найти. Мы уже увидели setter injection (внедрение установкой свойств) и constructor injection (внедрение с использованием конструктора) в главе 2. Затем давайте посмотрим, как Spring обеспечивает другие виды внедрения, которые позволяют вам декларативно вводить методы в ваши компоненты, эффективно изменять функциональные возможности компонентов, без необходимости менять лежащий в их основе Java код
Внедрение методов
В главе 2 вы наблюдали две базовые формы внедрения зависимости (DI). Внедрение с использованием конструктора позволяет вам настраивать ваш бин внедряя значения в его конструкторы.
Тем временем, внедрение установкой свойств, позволяет вам настраивать ваш компонент вводя значения сеттер методами. Прежде чем вы закончите с этой книгой, вы увидите сотни примеров внедрения установкой свойств, и может быть немного поменьше примеров внедрения с использованием конструктора.
Но в этом разделе, я хотел бы показать вам необычную форму DI названную - "внедрением методов". Используя внедрение установкой свойств и внедрение конструктора, вы вводили значения в свойства компонента. Но как показано на рисунке 3.5, внедрение метода совершенно от этого отличается, потому-что позволяет вам вводить в компонент полностью определенный метод.
Некоторые языки, такие как Ruby, позволяют вам добавлять новые методы в любой класс непосредственно во время выполнения, без изменения определения класса. Например, вы хотите добавить в класс Ruby новый метод String, который будет печатать длину строки.
Вот простой вариант определения нового метода:
class String
def print_length
puts "This string is #{self.length} characters long"
end
end
Когда метод определен, вы можете вызвать его из любого созданного вами String.
Например:
message = "Hello"
message.print_length
будет напечатано “This string is 5 characters long” в стандартном выводе.
Но это Ruby. Язык Java не такой гибкий. Пускай все и не столь элегантно, как в конструкциях для внедрения методов языка Ruby, но все-же это шаг в нужном направлении.
Spring поддерживает две формы внедрения методов:
- Method replacement - возможность замены во время выполнения существующего метода (абстрактного или реального) на его новую произвольную реализацию,.
- Getter injection - возможность замены во время выполнения существующего метода (абстрактного или реального) на его новую реализацию, которая ограничивается тем, что возвращает какой-либо компонент из контекста Spring.
Чтобы начать работать с внедрением методов, давайте посмотрим, как Spring поддерживает замещение методов общего назначения
Основы замещения методов.
Вам нравятся шоу иллюзионистов? Фокусники используют ловкость рук и отвлечение внимания чтобы прямо на наших глазах делать казалось бы невозможные вещи.
Один из наших любимых трюков это когда фокусник помещает своего ассистента в ящик, кружит вокруг ящика, бубнит какие-то магические слова, потом...вуаля! Ящик открывается чтобы показать, что тигр заменил ассистента.
Только-что так случилось, что Гарри, обещанный нами фокусник, вступил в конкурс талантов Spring Idol и будет исполнять наш любимый фокус. Позвольте мне представить вам - Гарри, первого из класса Magician представленного вам в листинге 3.1
Листинг 3.1 Фокусник и его магическая коробка.
package com.springinaction.springidol;
public class Magician implements Performer {
public Magician() {}
public void perform() throws PerformanceException {
System.out.println(magicWords);
System.out.println("The magic box contains...");
System.out.println(magicBox.getContents());
}
// injected
private MagicBox magicBox;
public void setMagicBox(MagicBox magicBox) {
this.magicBox = magicBox;
}
private String magicWords;
public void setMagicWords(String magicWords) {
this.magicWords = magicWords;
}
}
Как вы можете видеть, класс Magician имеет два свойства, которые могут быть установлены используя Spring DI. Для Magician нужны какие-нибудь магические слова, чтобы делать иллюзию, так что хотелось бы установить свойство magicWords. Но что более важно, мы должны дать ему магический ящик используя свойство magicBox. Говоря о магическом ящике, вы можете найти его реализацию в листинге 3.2.
Листинг 3.2 Реализация магического ящика содержит великолепного ассистента...или нет?
package com.springinaction.springidol;
public class MagicBoxImpl implements MagicBox {
public MagicBoxImpl() {}
public String getContents() {
return "A beautiful assistant";
}
}
Ключевой момент в классе MagicBoxImpl, что вы должны обратить свое внимание на метод getContents(). Вы должны заметить, что он жестко закодирован всегда возвращать “A beautiful assistant”, но, как вы скоро увидите, все не так, как кажется. Но прежде чем я покажу вам трюк, вот как Гарри и его волшебный ящик связаны с контекстом Spring приложения.
<bean id="harry" class="com.springinaction.springidol.Magician">
<property name="magicBox" ref="magicBox" />
<property name="magicWords" value="Bippity boppity boo" />
</bean>
<bean id="magicBox" class="com.springinaction.springidol.MagicBoxImpl" />
Если вы еще не видели этот трюк прежде, знайте, что маг всегда дразнит аудиторию, закрыв ящик, затем открыв его снова, чтобы показать, что помощник все еще там. Действия Гарри ничем не отличаются. Давайте начнем фокус с помощью следующегофрагмента кода:
ApplicationContext ctx = … //загружаем контекст Spring
Performer magician = (Performer) ctx.getBean("harry");
magician.perform();
Когда этот фрагмент кода начнет извлекать компонент harry из контекста приложения, и когда метод perform() будет вызван, вы сможете увидеть все так, как и должно быть:
"Bippity boppity boo"
The magic box contains..
A beautiful assistant
Такой вывод не должен стать сюрпризом для вас. В конце концов, MagicBoxImpl жестко закодирован возвращать “A beautiful assistant” когда вызывается getContents(). Но как я сказал, это только дразнилка. Сейчас Гарри не исполняет настоящий трюк. Но сейчас самое время начать представление, так что давайте настроим XML конфигурацию, чтобы она выглядела следующим образом:
<bean id="magicBox" class="com.springinaction.springidol.MagicBoxImpl">
<replaced-method name="getContents" replacer="tigerReplacer" />
</bean>
<bean id="tigerReplacer" class="com.springinaction.springidol.TigerReplacer" />
Сейчас в компоненте magicBox есть элемент <replaced-method> (см. рисунок 3.6) Как следует из названия, этот элемент используется для замены метода - новой реализацией. В этом случае атрибут name указывает на метод getContents(), который будет заменен. И атрибут replacer указывает на вызов компонента tigerReplacer для замещения реализации.
Вот где происходит реальная ловкость рук. Компонент tigerReplacer это TigerReplacer (класс), который определен в листинге 3.3
Листинг 3.3 TigerReplacer, который заменяет реализацию метода
package com.springinaction.springidol;
import java.lang.reflect.Method;
import org.springframework.beans.factory.support.MethodReplacer;
public class TigerReplacer implements MethodReplacer {
public Object reimplement(Object target, Method method, Object[] args)
throws Throwable {
return "A ferocious tiger";
}
}
TigerReplacer реализует интерфейс Spring-а MethodReplacer. MethodReplacer требует только реализации метода reimplement(). Этот метод принимает три аргумента:
- target - объект , в котором будет заменен метод
- method - метод, который должен быть заменен
- args - массив аргументов, принимаемых методом.
В нашем случае мы не используем аргументы, но это возможно, если вам будет необходимо.
Тело метода reimplement() фактически становится новой реализацией метода getContents() магического ящика. В нашем примере, единственное, что мы хотим сделать, это вернуть "свирепый тигр". В действительности, содержимое ящика заменяется тигром как показано на рисунке 3.6 Сейчас, когда мы вызовем метод perform(), на консоль будет выведено следующее:
Bippity boppity boo
The magic box contains..
A ferocious tiger
Та-да! Великолепный ассистент был заменен свирепым тигром - без изменения существующего кода MagicBoxImpl. Волшебный трюк был успешно выполнен с помощью <replaced-method> Spring-а.
Стоит отметить, что хотя MagicBoxImpl имеет конкретную реализацию метода getContents(), было бы также возможно для getContents(), быть написанным как абстрактный метод.
В самом деле, внедрение метода это трюк, который полезен, когда фактическая реализация метода не известна до времени развертывания.
В это время класс метода замещения может быть представлен в JAR файле, помещенного в classpath приложения. Замена методов общего характера это конечно изящный трюк. Но есть более специфичная форма внедрения методов которая позволяет среде выполнения связать компоненты геттер методом. Давайте посмотрим как выполняется getter injection в компонентах Spring.
Использование getter injection
Getter injection это особый случай внедрения метода, когда метод (который обычно абстрактный) объявлен чтобы возвращать компонент определенного типа, но действительные возвращаемые компоненты настраиваются в контексте Spring.
Для примера, рассмотрим новую форму класса Instrumentalist с внедренными методами в листинге 3.4
Листинг 3.4:
package com.springinaction.springidol;
public abstract class Instrumentalist implements Performer {
public Instrumentalist() {}
public void perform() throws PerformanceException {
System.out.print("Playing " + song + " : ");
getInstrument().play();
}
private String song;
public void setSong(String song) {
this.song = song;
}
public abstract Instrument getInstrument();
}
В отличие от оригинального Instrumentalist, этот класс не получает инструмент путем setter injection. Вместо этого, есть абстактный метод getInstrument(), который будет возвращать инструмент исполнителя. Но если getInstrument() абстрактный, тогда большой вопрос как метод получит реализацию.
Один из возможных подходов это использовать замену метода общего назначения, как описано в последней части. Но это потребует написать класс, который реализует MethodReplacer, когда все, что мы должны сделать это переопределить метод getInstrument() чтобы возвращать определенный компонент. Для внедрений в стиле getter injection, Spring предлагает элемент конфигурации <lookup-method>. Так же как <replaced-method>, <lookup-method> заменяет метод новой реализацией во время выполнения.
Но <lookup-method> это сокращенный вариант getter-injection для <replaced-method>, где вы должны определять компонент в контексте Spring, который возвращает замененный метод. С <lookup-method> можно расслабиться. Теперь не надо писать класс MethodReplacer. Следующий XML демонстрирует как используется <lookup-method> для замены метода getInstrument() другим, который возвратит ссылку на бин guitar.
<bean id="stevie" class="com.springinaction.springidol.Instrumentalist">
<lookup-method name="getInstrument" bean="guitar" />
<property name="song" value="Greensleeves" />
</bean>
Как и в <replaced-method>, атрибут name в <lookup-method> указывает на метод, который будет замещен. Здесь мы заменяем метод getInstrument(). Атрибут bean указывает на на компонент, который будет возвращен при вызове метода getInstrument(). В нашем случае, это компонент с id=guitar.
В результате метод getInstrument() фактически будет переопределен таким образом:
public Instrument getInstrument() {
ApplicationContext ctx = …;
return (Instrument) ctx.getBean("guitar");
}
Сам по себе, getter injection это только видоизменение setter injection. Однако, это имеет значение, когда областью действия компонента является prototype :
<bean id="guitar" class="com.springinaction.springidol.Guitar" scope="prototype" />
Даже если область видимости prototype, метод guitar будет только однажды внедрен в свойство, если мы использовали setter injection. Однако, вводя его в метод getInstrument() через getter injection, мы гарантируем, что каждый вызов getInstrument() вернет различные гитары. Это может пригодиться, если гитарист порвет струну в середине исполнения и потребует свежий
струнный инструмент.
Вы должны знать, что несмотря ни на что, мы используем <lookup-method> чтобы выполнить getter injection в методе getInstrument(), нет никаких требований к <lookup-method> чтобы фактический замененный метод был методом установки свойств (getter, т. е. начинающийся с get). Любой не-void метод это кандидат на замещение в <lookup-method>.
Важно отметить, что даже если внедрение метода позволяет вам заменять реализацию метода, нет никакой возможности заменить сигнатуру метода. Параметры и возвращаемый тип должны оставаться такие, какие есть. Для <lookup-method> это означает, что атрибут bean должен указывать на компонент, тип которого может быть представлен типом, возвращаемым методом (Instrument в предыдущем случае).
Внедрение не-Spring компонентов.
Как вы узнали в главе 2, одна из главных задач Spring это настраивать бины. Но до сих пор всегда подразумевалось одно предостережение: контейнер Spring может настраивать только те компоненты, экземпляры которых он сам создает. Может показаться слишком странным на первый взгляд, но это представляет некоторые проблемы. Не все объекты в приложении созданы контейнером Spring. Рассмотрим следующие возможные сценарии:
- Обычно JSP теги реализуются веб контейнером, в рамках которого выполняется приложение. Если JSP тег нуждается во взаимодействии с какими-то другими объектами, они должны создаваться явно.
- Доменные объекты, которые типично создаются во время выполнения инструментами ORM (таким как Hibernate или iBATIS). В насыщенной доменной модели, доменные объекты имеют как состояние, так и поведение. Но если Вы не можете внедрить сервисные объекты в доменные объекты, то ваши доменные объекты должны получить свою собственную реализацию сервисных объектов, или логика поведения должна полностью содержаться в доменных объектах.
И тут может быть другая веская причина почему мы можем нуждаться в Spring чтобы настраивать объекты, которые не создаем. Например, предположим, что мы явно создаем экземпляр из примера Spring Idol :
Instrumentalist pianist = new Instrumentalist();
pianist.perform();
Так как Instrumentalist это - POJO, нет никаких причин по которым мы не можем прямо и явно создать его экземпляр. Но когда вызывается метод perform(), может быть брошено исключение NullPointerException. Это потому, что не смотря на то, что мы позволяем классу Instrumentalist создаваться, его свойство instrument может иметь значение - null. В самом деле, мы можем вручную настраивать свойства класса Instrumentalist. Например:
Piano piano = new Piano();
pianist.setInstrument(piano);
pianist.setSong("Chopsticks");
Даже если бы вручную введенные свойства работали, это не давало бы преимущества возможности Spring разделять конфигурацию и код.
Кроме того, если бы ORM создавали экземпляры Instrumentalist, мы бы никогда не смогли изменять конфигурацию их свойств.
К счастью, в Spring 2.0 включена возможность декларативно настраивать компоненты, экземпляры которых созданы вне Spring. Идея в том, что Spring настраивает компоненты, но не создает их экземпляры.
Рассмотрим компонент Instrumentalist который явно создан ранее. В идеале, мы хотели бы сконфигурировать pianist вне нашего кода и позволить Spring внедрить его со свойствами instrument и song. Следующий XML покажет, как мы можем сделать это.
<bean id="pianist" class="com.springinaction.springidol.Instrumentalist" abstract="true">
<property name="song" value="Chopsticks" />
<property name="instrument">
<bean class="com.springinaction.springidol.Piano" />
</property>
</bean>
По сути нет ничего необычного в этом объявлении бина . Компонент pianist объявляется как Instrumentalist.
И его свойства song и instrument связываются со своими значениями. Это просто ваш заурядный Spring бин, за исключением одной маленькой детали: его атрибут abstract установлен в true.
Но как Вы видели ранее, установка abstract в true сообщает Spring, что вы не хотите реализовывать экземпляр класса этого компонента. Это часто используется, чтобы объявить родительский компонент, который будет расширен дочерним компонентом. Но в этом случае мы просто указываем Spring, что компонент pianist не будет инстантиирован, это будет сделано вне Spring.
На самом деле, компонент pianist только служит шаблоном действий для Spring, когда он формирует Instrumentalist, который создан вне Spring. С этим определенным шаблоном нам нужен некий метод связывания его с классом Instrumentalist. Чтобы сделать это, мы будем аннотировать класс Instrumentalist аннотацией @Configurable:
package com.springinaction.springidol;
import org.springframework.beans.factory.annotation.Configurable;
@Configurable("pianist")
public class Instrumentalist implements Performer {
...
}
Аннотация @Configurable делает две вещи:
- Во первых, это показывает, что экземпляр класса Instrumentalist может быть сконфигурирован Spring, даже если создан вне его.
- Это также ассоциирует класс Instrumentalist с компонентом, id которого = pianist. Когда Spring пытается сконфигурировать экземпляр класса Instrumentalist,он будет рассматривать компонент pianist как шаблон.
Итак, как же Spring узнает, как настраивать компоненты с аннотацией @Configurable ?
За это отвечает, одна из последних вещей, добавленная в конфигурацию Spring:
<aop:spring-configured />
Конфигурационный элемент <aop:spring-configured> это один из многих новых конфигурационных элементов, введенных в Spring 2.0. Они представляют указатели для Spring, что это некие элементы, которые он будет конфигурировать, даже если они созданы
где-то еще.
<aop:spring-configured> устанавливает аспект AspectJ с точкой действия которая срабатывает, когда создается любой компонент аннотированный @Configurable. Когда компонент создается, вмешивается аспект и внедряет свойства в новый экземпляр, основанный на шаблоне <bean> в конфигурации Spring Т.к. сконфигурированный Spring аспект это аспект AspectJ, то ваше приложение нуждается, в запуске на JVM с включенной поддержкой AspectJ. Лучший способ для Java 5 JVM включить поддержку AspectJ, это запустить ее со следующими аргументами JVM:
-javaagent:/path/to/aspectjweaver.jar
В действительности, это сообщает JVM выполнять слияние во время загрузки с любыми аспектами AspectJ. Чтобы сделать это, ей необходимо знать, где расположены связанные AspectJ классы.
Вы должны будете заменить
/path/to/aspectjweaver.jar
действительным путем к фалу aspectjweaver.jar в вашей системе . В этой части мы явно создадим экземпляр Instrumentalist как простую демонстрацию возможности Spring конфигурировать компоненты, которые он не создает. Тем не менее, как я упоминал ранее, сконфигурированный компонент более вероятно был бы инстанцирован ORM или некоторой сторонней библиотекой, если бы это было реальным
приложением.
Сейчас смотрите, как редактор свойств Spring выполняет простую работу по внедрению сложных значений, основанных на String представлении.
Пользовательские редакторы свойств
По мере изучения этой книги, вы можете увидеть несколько примеров, где сложные свойства устанавливаются посредством простого строкового значения. Например, в главе 9, вы можете увидеть как в Spring связывается веб сервисы, используя JaxRpcPortProxyFactoryBean. Одно
из свойств JaxRpcPortProxyFactoryBean, которое вам нужно будет установить, это wsdlDocumentUrl. Это свойство типа java.net.URL. Но вместо того, чтобы создавать компонент java.net.URL и связывать его с этим свойством, вы можете сконфигурировать его используя String таким образом:
<property name="wsdlDocumentUrl" value="https://www.xmethods.net/sd/BabelFishService.wsdl" />
Spring автоматически, в скрытом режиме, преобразует значение String в URL объект. В действительности, магия этого трюка не в каких-то особых возможностях Spring, а скорее в малоизвестных возможности оригинального JavaBeans API. Интерфейс java.beans.PropertyEditor обеспечивает возможность модифицировать значения типа String, связывая их с не-String типами. Удобная реализация этого интерфейса - java.beans.PropertyEditorSupport - имеет два метода, интересующие нас.
- getAsText ( ) возвращает String представление значений свойств.
- setAsText (String value) устанавливает компоненту значение свойства, переданного в виде строки.
При попытке установить не String свойство в String значение, вызывается метод setAsText() для выполнения преобразования. Точно также, метод getAsText() вызывается, чтобы вернуть текстовое представление значения свойства.
Spring сопровождается несколькими общими редакторами, основанными на PropertyEditorSupport, включая org.springframework.beans.propertyeditors.URLEditor, который использует пользовательский редактор, чтобы конвертировать в/из String объекты java.net.URL. в таблице 3.1 представлен выбор для Spring пользовательских редакторов
Таблица 3.1 Spring работает со свойствами нескольких пользовательских редакторов, которые автоматически в свою очередь вводят строковые значения в более сложные типы.
Редактор свойств. | Что делает. |
---|---|
ClassEditor | Устанавливает свойство java.lang.Class для строки, значение которого содержит полное имя класса. |
CustomDateEditor | Устанавливает свойство java.util.Date для строки используя пользовательский объект java.text.DateFormat. |
FileEditor | Устанавливает свойство java.io.File для строкового значения, которое содержит полный путь к файлу. |
LocaleEditor | Устанавливает свойство java.util.Locale для строкового значения которое содержит текстовое представление локали (например, en_US). |
StringArrayPropertyEditor. | Преобразует разделенную запятыми строку в строковый массив свойств. |
StringTrimmerEditor | Автоматически отрезает пробелы у строк свойств с возможность конвертировать пустые строки в null. |
URLEditor | Устанавливает свойство java.net.URL для строкового значения содержащее спецификацию для URL. |
В дополнение к обычным редакторам в таблице 3.1, вы можите написать свой собственный редактор, расширяя класс PropertyEditorSupport. Например, предположим, что в приложении есть объект Contact, который удобно хранит информацию о сотрудниках в вашей организации. Кроме того, в объекте Contact есть свойство phoneNumber, которое хранит контактный номер телефона:
public Contact {
private PhoneNumber phoneNumber;
public void setPhoneNumber(PhoneNumber phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
Свойство phoneNumber является PhoneNumber типом и определяется следующим образом:
public PhoneNumber {
private String areaCode;
private String prefix;
private String number;
public PhoneNumber() {}
public PhoneNumber(String areaCode, String prefix, String number) {
this.areaCode = areaCode;
this.prefix = prefix;
this.number = number;
}
...
}
Используя базовую технологию изученную в главе 2, вы можите связать объект PhoneNumber со свойством phoneNumber в бине Contact следующим образом:
<beans>
<bean id="infoPhone" class="com.springinaction.chapter03.propeditor.PhoneNumber">
<constructor-arg value="888" />
<constructor-arg value="555" />
<constructor-arg value="1212" />
</bean>
<bean id="contact" class="com.springinaction.chapter03.propeditor.Contact">
<property name="phoneNumber" ref="infoPhone" />
</bean>
</beans>
Обратите внимание, что надо определить отдельный бин infoPhone, настроить объект PhoneNumber и затем связать свойство phoneNumber с описанием бина. Вместо этого, предположим что вы написали свой редактор PhoneEditor, например так:
public class PhoneEditor extends java.beans.PropertyEditorSupport {
public void setAsText(String textValue) {
String stripped = stripNonNumeric(textValue);
String areaCode = stripped.substring(0, 3);
String prefix = stripped.substring(3, 6);
String number = stripped.substring(6);
PhoneNumber phone = new PhoneNumber(areaCode, prefix, number);
setValue(phone);
}
private String stripNonNumeric(String original) {
StringBuffer allNumeric = new StringBuffer();
for(int i=0; i<original.length(); i++) {
char c = original.charAt(i);
if(Character.isDigit(c)) {
allNumeric.append(c);
}
}
return allNumeric.toString();
}
}
Теперь единственная оставшаяся вещь состоит в том, чтобы заставить Spring распознавать Ваш пользовательский редактор свойств
при связывании свойств бинов. Для этого Вы должны использовать Spring’s CustomEditor - Configurer.
CustomEditorConfigurer - BeanFactoryPostProcessor, который загружает пользовательский редактор в BeanFactory, вызывая метод registerCustomEditor(). (Произвольно, Вы можете вызвать метод registerCustomEditor() в Вашем собственном коде после того, как у Вас есть экземпляр класса фабрики бина). Добавляя следующий фрагмент XML к файлу конфигурации бина, Вы укажете Spring регистрировать PhoneEditor как пользовательский редактор:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="com.springinaction.chapter03.propeditor.PhoneNumber">
<bean id="phoneEditor"
class="com.springinaction.chapter03.propeditor.PhoneEditor">
</bean>
</entry>
</map>
</property>
</bean>
Теперь Вы будете в состоянии конфигурировать свойство phoneNumber объекта Contact, используя простое значение String и не создавая отдельный бин infoPhone:
<bean id="contact" class="com.springinaction.chapter03.propeditor.Contact">
<property name="phoneNumber" value="888-555-1212" />
</bean>
Отметьте, что многие из пользовательских редакторов, которые идут со Spring (такие как URLEditor и LocaleEditor) уже зарегистрированы в фабрике бинов после старта контейнера. Вы не должны регистрировать их явно, используя CustomEditorConfigurer. Редакторы свойств - только один из способов настроить, то как Spring создает и внедряет бины. Существуют и другие типы бинов, выполняющие в контейнере Spring особые функции. Давайте увидим, как создать несколько специальных бинов, которые позволяют Вам настраивать, то как контейнер Spring связывает бины.
Работа с особыми видами Spring бинов
Most beans configured in a Spring container are treated equally. Spring configures them, wires them together, and makes them available for use within an application. Nothing special.
But some beans have a higher purpose. By implementing certain interfaces, you can cause Spring to treat beans as special—as part of the Spring Framework itself. By taking advantage of these special beans, you can configure beans that
- Become involved in the bean’s creation and the bean factory’s lifecycles by postprocessing bean configuration
- Load configuration information from external property files
- Load textual messages from property files, including internationalized messages
- Listen for and respond to application events that are published by other beans and by the Spring container itself
- Are aware of their identity within the Spring container
In some cases, these special beans already have useful implementations that come packaged with Spring. In other cases, you’ll probably want to implement the interfaces yourself.
Let’s start the exploration of Spring’s special beans by looking at Spring’s special beans that perform postprocessing of other beans after the beans have been wired together.
Пост-обработка бинов
In chapter 2, you learned how to define beans within the Spring container and how to wire them together. For the most part, you have no reason to expect beans to be wired in any way different than how you define them in the bean definition XML file. The XML file is perceived as the source of truth regarding how your application’s objects are configured.
But as you saw in figures 2.2 and 2.3, Spring offers two opportunities for you to cut into a bean’s lifecycle and review or alter its configuration. This is called postprocessing. From the name, you probably deduced that this processing is done after some event has occurred. The event this postprocessing follows is the instantiation and configuration of a bean. The BeanPostProcessor interface gives you two
opportunities to alter a bean after it has been created and wired:
public interface BeanPostProcessor {
Object postProcessBeforeInitialization(Object bean, String name)
throws BeansException;
Object postProcessAfterInitialization(Object bean, String name)
throws BeansException;
}
The postProcessBeforeInitialization() method is called immediately prior to bean initialization (the call to afterPropertiesSet() and the bean’s custom init-method). Likewise, the postProcessAfterInitialization() method is called immediately after initialization.
Writing a bean postprocessor
For example, suppose that you wanted to alter all String properties of your application beans to translate them into Elmer Fudd-speak. The Fuddifier class in listing 3.5 is a BeanPostProcessor that does just that. Listing 3.5 Listing 3.5 Fuddifying String properties using a BeanPostProcessor
public class Fuddifier implements BeanPostProcessor {
public Object postProcessAfterInitialization(Object bean, String name)
throws BeansException {
Field[] fields = bean.getClass().getDeclaredFields();
try {
for(int i=0; i < fields.length; i++) {
if(fields[i].getType().equals(java.lang.String.class)) {
fields[i].setAccessible(true);
String original = (String) fields[i].get(bean);
fields[i].set(bean, fuddify(original));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return bean;
}
private String fuddify(String orig) {
if(orig == null) return orig;
return orig.replaceAll("(r|l)", "w").replaceAll("(R|L)", "W");
}
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
return bean;
}
}
The postProcessAfterInitialization() method cycles through all of the bean’s properties, looking for those that are of type java.lang.String. For each String property, it passes it off to the fuddify() method, which translates the String into Fudd-speak. Finally, the property is changed to the “Fuddified” text. (You’ll also notice a call to each property’s setAccessible() method to get around the private visibility of a property. I realize that this breaks encapsulation, but how else could I pull this off?) The postProcessBeforeInitialization() method is left purposefully unexciting;
it simply returns the bean unaltered. Actually, the “Fuddification” process could have occurred just as well in this method. Now that we have a Fuddifying BeanPostProcessor, let’s look at how to tell the container to apply it to all beans.
Registering bean postprocessors
If your application is running within a bean factory, you’ll need to programmatically register each BeanPostProcessor using the factory’s addBeanPostProcessor() method:
BeanPostProcessor fuddifier = new Fuddifier();
factory.addBeanPostProcessor(fuddifier);
More likely, however, you’ll be using an application context. For an application
context, you’ll only need to register the postprocessor as a bean within the context:
<bean class="com.springinaction.chapter03.postprocessor.Fuddifier" />
The container will recognize the fuddifier bean as a BeanPostProcessor and call its postprocessing methods before and after each bean is initialized.
As a result of the fuddifier bean, all String properties of all beans will be Fuddified. For example, suppose you had the following bean defined in XML:
<bean id="bugs" class="com.springinaction.chapter03.postprocessor.Rabbit">
<property name="description" value="That rascally rabbit!" />
</bean>
When the fuddifier postprocessor is finished, the description property will
hold “That wascawwy wabbit!”
Spring’s own bean postprocessors
The Spring Framework itself uses several implementations of BeanPostProcessor under the covers. For example, ApplicationContextAwareProcessor is a BeanPostProcessor that sets the application context on beans that implement the ApplicationContextAware interface (see section 3.5.6). You do not need to register ApplicationContextAwareProcessor yourself. It is preregistered by the applicationcontext itself.
In the next chapter, you’ll learn of another implementation of BeanPostProcessor. You’ll also learn how to automatically apply aspects to application beans using DefaultAdvisorAutoProxyCreator, which is a BeanPostProcessor that creates AOP proxies based on all candidate advisors in the container.
Postprocessing the bean factory
Whereas a BeanPostProcessor performs postprocessing on a bean after it has been loaded, a BeanFactoryPostProcessor performs postprocessing on the entire Spring container. The BeanFactoryPostProcessor interface is defined as follows:
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException;
}
The postProcessBeanFactory() method is called by the Spring container after all bean definitions have been loaded but before any beans are instantiated (including BeanPostProcessor beans). For example, the following BeanFactoryPostProcessor implementation gives
a completely new meaning to the term “bean counter”:
public class BeanCounter implements BeanFactoryPostProcessor {
private Logger LOGGER = Logger.getLogger(BeanCounter.class);
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory)
throws BeansException {
LOGGER.debug("BEAN COUNT: " + factory.getBeanDefinitionCount());
}
}
BeanCounter is a BeanFactoryPostProcessor that simply logs the number of bean definitions that have been loaded into the bean factory. If you’re using an application context container, registering a BeanFactoryPostProcessor is as simple as declaring it as a regular bean:
<bean id="beanCounter"
class="com.springinaction.chapter03.postprocessor.BeanCounter" />
When the container sees that beanCounter is a BeanFactoryPostProcessor, it will automatically register it as a bean factory postprocessor. You cannot use BeanFactoryPostProcessors with basic bean factory containers—this feature is only available with application context containers.
BeanCounter is a naive use of BeanFactoryPostProcessor. To find more meaningful examples of BeanFactoryPostProcessor, we have to look no further than the Spring Framework itself. You’ve already seen CustomerEditorConfigurer (see section 3.4), which is an implementation of BeanFactoryPostProcessor used to register custom PropertyEditors in Spring.
Another very useful BeanFactoryPostProcessor implementation is PropertyPlaceholderConfigurer. PropertyPlaceholderConfigurer loads properties
from one or more external property files and uses those properties to fill in placeholder variables in the bean wiring XML file. Speaking of PropertyPlaceholderConfigurer, that’s what we’ll look at next.
Вынесение конфигурации в properties-файлы
В общем, можно настроить все приложения в одном файле с связанными бинами. Но иногда полезно выносить определенную часть конфигурации в отдельный файл. К примеру, конфигурация источника данных, которая является общей для многих приложений. В Spring вы можете настроить описание доступа к источнику данных, например в виде такого XML кода в файле настройки бинов:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:hsqldb:Training" />
<property name="driverClassName" value="org.hsqldb.jdbcDriver" />
<property name="username" value="appUser" />
<property name="password" value="password" />
</bean>
Однако такая настройка источника данных непосредственно в файле с остальными бинами может быть нецелесообразна или даже вредна. Спецификой баз данных являются детали развертывания приложения. Наоборот же, цель файла с бинами в основном ориентирована на определение того, какие компоненты есть в вашем приложении,чтобы их объединить.
Но это не значит, что нельзя сконфигурировать все компоненты вашего приложения в файле с бинами. В самом деле, когда определяется именно сама конфигурация приложением (исключая подробности его конкретного развертывания) как раз имеет смысл, для настройки конфигурации использовать один файл с бинами. Но детали развертывания должны быть - отделены.
К счастью, в Spring можно легко экстернализовать (от англ."externalize" т.е. "вынести - вовне" ) любые properties-пары свойств, если Вы используете ApplicationContext в качестве Вашего Spring контейнера. Вы можете использовать PropertyPlaceholderConfigurer Spring-а, чтобы сообщить Spring о необходимости загрузки определенной конфигурации из внешнего properties-файла. Чтобы включить эту функцию необходимо объявить следующую связь в файле с бинами:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="jdbc.properties" />
</bean>
Свойство location указывает Spring где искать файл со свойствами. В данном случае, это файл jdbc.properties содержащий следующую информацию для JDBC:
database.url=jdbc:hsqldb:Training
database.driver=org.hsqldb.jdbcDriver
database.user=appUser
database.password=password
Свойство location позволяет работать с одним файлом свойств. Если вы захотите разделить вашу конфигурацию на несколько файлов со свойствами, используйте свойство locations в PropertyPlaceholderConfigurer для установки списка файлов со свойствами:
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>jdbc.properties</value>
<value>security.properties</value>
<value>application.properties</value>
</list>
</property>
</bean>
Теперь вы можите заменить жестко прописанную конфигурацию в файле с бинами - соответствующими переменными. Синтаксически переменные описываются в форме ${variable}, аналогично тому как описываются свойства Ant и языке выражений в JSP. После замены переменными новый бин с источником данных будет выглядеть так:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${database.url}" />
<property name="driverClassName" value="${database.driver}" />
<property name="username" value="${database.user}" />
<property name="password" value="${database.password}" />
</bean>
Когда Spring создаст бин источника данных, PropertyPlaceholderConfigurer вмешается и заменит переменные нужными значениями из файла со свойствами, как это показано на рисунке 3.7.
Использование PropertyPlaceholderConfigurer полезно для выделения части настроек Spring в отдельный файл свойств. Но java использует propertiesфайлы не только для конфигурации; они (файлы свойств) используются для хранения текстовых сообщений и для интернационализации. Посмотрим как интерфейс источника сообщений Spring может использовать для вынесения текстовых сообщений в properties-файлы.
Вынесение текстовых сообщений.
Часто вы не можете жестко закодировать определенный текст, который будет отображаться для пользователя вашего приложения. Это может быть потому, что текст может быть изменен, или возможно ваше приложение будет интернационализированно и вы должны будите отображать текст
на родном языке пользователя.
Поддержка в Java параметризации и интернационализации сообщений позволяет определить один или несколько properties-файлов которые содержат текст, для отображения в вашем приложении. Там всегда должен быть файл, который используется по-умолчанию, вместе с конкретными properties-файлами, содержащими сообщения на других языках. Например, если пакет ваших сообщений для приложения называется “trainingtext”, то вы можете иметь следующий набор файлов свойств с сообщениями:
- trainingtext.properties - сообщения по умолчанию, когда locale не определена или когда для конкретной locale отсутствует файл со свойствами
- trainingtext_en_US.properties - сообщения для пользователей, использующих английский язык в США
- trainingtext_es_MX.properties - сообщения для пользователей, использующих испанский язык в Мексике
- trainingtext_de_DE.properties - сообщения для пользователей, использующих немецкий язык в Германии
Например, для сообщений по умолчанию и на английском языке, properties-файл может состоять из записей
таких, как
course=class
student=student
computer=computer
Тем временем, эти же сообщения но на испанском будут выглядеть так:
course=clase
student=estudiante
computer=computadora
ApplicationContext Spring-а поддерживает параметризацию сообщений, делая их доступными для контейнера через интерфейс MessageSource:
public interface MessageSource {
String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException;
String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);
}
Spring работает на чтение-использование реализации MessageSource. ResourceBundleMessageSource просто использует имеющийся в Java java.util.ResourceBundle для извлечения сообщений. Для того, чтобы использовать ResourceBundleMessageSource надо добавить следующий XML в файл определения бинов:
<bean id="messageSource"
class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>trainingtext</value>
</property>
</bean>
Это очень важно, чтобы данный бин назывался именно - messageSource, потому что ApplicationContext будет искать его по имени, когда будет производить установки внутреннего источника сообщений. Зато, Вам никогда не надо внедрять бин messageSource в ваши бины приложения, явно. Вместо этого вы получите доступ к сообщениям ApplicationContext-а через метод getMessage( ). Например, чтобы получить сообщение с именем "computer" используйте этот код:
Locale locale = … ; //determine locale
String text = context.getMessage("computer", new Object[0], locale);
Вы возможно будете использовать пареметризованные сообщения из контекста в web-приложении, чтобы отобразить их на web-страничке. В этом случае вы захотите использовать специальный JSP тэг Spring-а (<spring:message>) для извлечения сообщений и тогда будет не нужен непосредственный доступ к ApplicationContext:
<spring:message code="computer" />
Но если мне нужны бины, а не JSP, для извлечения сообщений, то как я могу получить доступ к ApplicationContext? Для этого надо немного подождать. Или вы можете сразу перейти к разделу, где я расскажу как сделать бины "осведомленными" о своих контейнерах.
Однако, давайте перейдем к анализу событий, происходящих в течение жизненного цикла контекста приложения и как обращаться с этими событиями для выполнения специальной обработки. Вы также увидите, как публиковать наши собственные события, чтобы вызвать поведение
между разными бинами.
Снижение связности с использованием событий
Внедрение зависимостей является основным способом Spring ослабить связи между объектами приложения - но это не единственный путь (решение). Еще одиним способом для объектов взаимодействовать друг с другом является публикация и прослушивание событий приложения. Используя события, объект события (издатель) может взаимодействовать с другими объектами без знания о том, какие объекты прослушивают. Кроме того, тот объект, который принимает события может реагировать на события без знания о том, кто публикует эти события.
Это взаимодействие событиями является аналогом радиостанции и ее аудитории слушателей, как показано на рисунке 3.8. Радиостанция не подключается напрямую к радиостанции, и радиостанция не знает о настройках других радиостанций. Однако, разделенные станции по-прежнему могли общаться со своими слушателями. В Spring любой бин в контейнере может быть или слушателем или издателем или и тем и другим. Давайте посмотрим как создать бины, которые участвуют в событиях, начнем с бинов, которые публикуют события приложения.
Публикация событий.
Представьте себе, что в онлайн-системе регистрации колледжа, вы хотите, предупредить один или несколько объектов приложения в любое время о том, что студент записывается на курс, и, как следствие, курс набирается полностью. Может быть вы захотите автоматчески планировать другой курс после заполнения.
Вначале определим собсвенное событие, например следующее CourseFullEvent:
public class CourseFullEvent extends ApplicationEvent {
private Course course;
public CourseFullEvent(Object source, Course course) {
super(source);
this.course = course;
}
public Course getCourse() {
return course;
}
}
Далее необходимо опубликовать сообщение. У интерфейса ApplicationContext есть метод publishEvent(), который позволяет публиковать ApplicationEvents. Любой ApplicationListener, который зарегистрирован в контексте приложения будет извлекать событие при вызове метода onApplicationEvent():
ApplicationContext context = …;
Course course = …;
context.publishEvent(new CourseFullEvent(this, course));
К сожалению, для того, чтобы публиковать события, ваши бины должны иметь доступ к ApplicationContext. Это означает, что ваши бины должны знать в каком контейнере они запущены. Вы увидети как сделать так, чтобы бины знали о том, в каком контейнере они работают в разделе 3.5.6.
Но в начале, если publisher публикует событие и никто его не прослушивает, событие действительно произошло? Я оставлю этот философский вопрос для вас, чтобы обдумать позже. Пока, давайте удостоверимся, что события не обращают внимания на создаваемые бины, которые прослушивают события.
Прослушивание событий
В дополнение к событиям, которые публикуются бинами, Spring сам публикует несколько событий в течение времени жизни приложения.
Все эти события - подклассы абстрактного класса org.springframework.context.ApplicationEvent.
Вот три таких события, приложения:
- ContextClosedEvent - публикуется, когда контекст приложения закрыт.
- ContextRefreshedEvent - публикуется, когда контекст приложения инициализирован или обновлен.
- RequestHandledEvent - публикуется в контексте web-приложения, когда обрабатывается запрос.
Большинство бинов не знает зачем они были опубликованы. Но, что, если вы хотите получать уведомления о событиях приложения?
Если вы хотите бин реагировал на события приложения, будь они опубликованы другим бином или контейнером, все, что вам нужно это реализовать
интерфейс org.springframework.context.ApplicationListener. Для этого необходимо реализовать метод onApplicationEvent(), который отвечает за реагирование на события приложения:
public class RefreshListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
…
}
}
Только одну вещь вы должны сделать - сообщить Spring-у о прослушивателе сообщений приложения, для этого надо просто зарегистрировать его в контексте бина:
<bean id="refreshListener" class="com.springinaction.foo.RefreshListener"/>
Когда контейнер загружает бины в контекст приложения, он увидит, что реализован ApplicationListener и будет помнить, что надо вызвать его метод onApplicationEvent() при публикации события. Одну вещь надо иметь в виду, события приложения обрабатываются синхронно. Итак, вы должны позаботится о том, чтобы любые сообщения обрабатывались быстро. В противном случае, это окажет негативное влияние на производительность приложения. Как упоминалось ранее, бин должен знать о контейнере приложения, чтобы иметь возможность публиковать события. Даже если объект не заинтересован в публикации событий, вы можите разработать бин, который знает о контексте приложения, пока он (контекст) жив.
Далее, давайте посмотрим как создать бины, которые внедряются с ссылками на их контейнер.
Создание "осведомленных" бинов
Вы видели фильм "Матрица"? В этом фильме , люди были невольно порабощены машинами, жили своими каждодневными жизнями в виртуальном мире, в то время как в сущность их жизнь состояла в то, чтобы быть разводимыми, для питания машин энергией. Главному персонажу, Томасу Андерсону
был дан выбор между приемом "красной пилюли" открывающей правду его существования или приемом "синей пилюли" с продолжением прежней жизни и игнорированием правды... Он выбрал красную пилюлю и сделался оседомленным о своей реальной личности и правде о витруальном мире.
Большая часть бинов запущенных в контейнере Spring, подобны людям в фильме "Матрица" Для бинов незнание, есть - счастье. Они (бины) не знают (им даже не положено знать), свои имена, или о том, что они работают внутри контейнера Spring.
Как правило это хорошо, потому если бин знает о контейнере, то он становится связанным со Spring и не может существовать вне контейнера.
Но иногда бинам надо знать больше. Иногда им надо знать кто они есть, и где они запущены. Иногда они должны взять "красную пилюлю". (как в фильме "Матрица")
"Красная пилюля" в случае Spring бинов это интерфейсы NameAware, BeanFactoryAware и ApplicationContextAware. Реализуя эти три интерфейса бины будут знать свое имя, и свой BeanFactory или ApplicationContext соответственно.
Но имейте ввиду, что реализуя эти интерфейсы, бины становятся связанными со Spring. И в зависимости от того, как бин использует это знание, вы не можете использовать его вне Spring.
Знание, кто ты есть.
Spring контейнер сообщает бину о его имени через интерфейс BeanNameAware. Этот интерсейс имеет один метод setBeanName(), который указывает контейнеру Spring имя бина, которое устанавливается через его идентификатор или имя атрибута <bean> файле связывания-бинов:
public interface BeanNameAware {
void setBeanName(String name);
}
Для бина может быть полезно знать свое имя в "бухгалтерских" целях. Например, если бин может существовать более чем в одном экземпляре в контексте приложения, для данного бина может быть полезной возможность , самоидентифицировать себя самого по имени и типу, в случае
логгирования собственных действий
Внутри самого Spring Framework, интерфейс BeanNameAware используется несколько раз. Одно известное использование: с бинами выполняющими функции планирования. Например, CronTriggerBean, реализующий интерфейс BeanNameAware для установки имени его Quartz CronTrigger задания.
Следующий отрывок кода из CronTriggerBean иллюстрирует это:
package org.springframework.scheduling.quartz;
public class CronTriggerBean extends CronTrigger implements …, BeanNameAware, … {
…
private String beanName;
…
public void setBeanName(String beanName) {
this.beanName = beanName;
}
…
public void afterPropertiesSet() … {
if (getName() == null) {
setBeanName(this.beanName);
}
…
}
…
}
Вы не должны сделать ничего специального для того чтобы контейнер Spring вызывал метод setBeanName() в классе реализующем BeanNameAware. Когда бин загружается, контейнер увидит что бин реализует BeanNameAware и автоматически вызовет метод setBeanName(), передавая имя бина объявленное как id (либо как name)атрибут элемента <bean> в XML-файле бин-соединений.
Здесь CronTriggerBean расширяет CronTrigger. После установки контекстом Spring всех свойства бина, имя бина передается в метод setBeanName() (объявленный в CronTriggerBean), который используется для установки имени планируемого действия.
Этот пример иллюстрирует, использование BeanNameAware для демонстрации встроенной поддержки планировщика в Spring. Подробнее о планировании будет рассказано в главе 12. Пока, давайте посмотрим, как наделить бин, знанием о Spring контейнере внутри которого он живет.
Знание, где ты живешь
Как Вы видели в этой секции, иногда полезно для бина быть в состоянии получить доступ к контексту приложения. Возможно, Ваш бин нуждается в доступе к параметризовавшему тексту сообщения в источнике сообщения. Или возможно это потребность быть в состоянии опубликовать
события для слушателя событий, отвечающего на них. Безотносительно причины, Ваш бин должен знать о контейнере, в котором он живет.
Spring интерфейсы ApplicationContextAware и BeanFactoryAware позволяют бину быть осведомленным о его контейнере. Эти интерфейсы объявляют методы setApplicationContext() и setBeanFactory(), соответственно.
Контейнер Spring обнаружит, реализует ли какой-либо из Ваших бинов любой из этих интерфейсов и обеспечит BeanFactory или ApplicationContext.
Имея доступ к ApplicationContext бин может активно взаимодействовать с контейнером. Это может быть полезно для програмного нахождения зависимостей от контейнера (когда они не были вставлены самим контейнером) или для публикации событий в приложении.
Возвращаясь к приведенному ранее примеру публикации событий, мы можем завершить его следующим образом:
public class StudentServiceImpl implements StudentService, ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
public void enrollStudentInCourse(Course course, Student student)
throws CourseException;
…
context.publishEvent(new CourseFullEvent(this, course));
…
}
…
}
Знание о контейнере приложение - одновременно благословение и проклятие для бина. С одной стороны доступ к контексту приложения предоставляет бину большую мощь С другой стороны знание о контейнере связывает бин со Spring, а это то чего в принципе желательно бы избегать по возможности.
До сих пор мы предполагали что все бины в контейнере Spring реализованы как Java классы. Это резонное предположение, но это не обязательный вариант. Давайте посмотрим как добавить динамическое поведение в приложение связыванием бинов реализованных с использованием скриптовых языков.
Скриптовые бины.
Когда Вы пишете Java код он превращается в бины в Вашего Spring приложениия и Вы в конечном итоге компилируете его в байт-код.
исполняемый JVM. Более того, Вы вероятно упакуете скомпилированный код в JAR, WAR, или EAR файл для дальнешего развертывания. Но что если после развертывания приложения Вы захотите изменить поведение Вашего кода.
Вы видите, что проблема со статически скомпилированным кодом состоит в том, что он... таки-да... действительно - статичен.
Будучи однажды скомпилированным в файл класса, и упакованным в архив развертывания, он трудно изменяем без перекомпиляции, повторной упаковки и развертывания всего приложения. Во многих обстоятельствах, это приемлемо (и возможно даже желаемо). Но даже в этом случае, статически скомпилированный код создает трудности, в реализации быстрых ответов на динамичные потребности бизнеса.
Например, предположим Вы разрабатываете приложение для Е-коммерции. В пределах приложения код рассчитывает промежуточные итоги и общую сумму покупки основанной на наборе выбранных позиций в заказе, подходящих тарифов доставки, и налогов с продажи. Но что, если Вы нуждаетесь в способности отказаться от налога с продаж в течение одного дня? Если налоговая часть вычисления Вашего приложения статически собрана с
остальным кодом, едиственно доступный дл Вас вариант - это развертывнаие "безналоговой" версии Вашего приложения в полночь, с последующи развертыванием базовой версии в следующую полночь.
You had better start brewing the coffee, because you’re going to be up late two nights in a row. With Spring, it’s possible to write scripted code in either Ruby, Groovy, or BeanShell and wire it into a Spring application context as if it were any other Javabased bean, as illustrated in figure 3.9. In the next few subsections, I’ll demonstrate how to wire Ruby, Groovy, and BeanShell scripts as Spring-managed beans.
If you’re fan of Calypso music then you’re in for a treat—I’m going to demonstrate scripted beans to the tune of one of the genre’s most famous songs. Please follow along as I demonstrate dynamically scripted beans by putting a scripted lime in a Java coconut.
Добавляем Лайм в Кокос
To illustrate how to script beans in Spring, let’s inject a scripted implementation of a Lime interface into a Java Coconut object. To start, let’s look at the Coconut class as defined in listing 3.6.
Listing 3.6 A Java in a coconut shell
package com.springinaction.scripting;
public class Coconut {
public Coconut() {}
public void drinkThemBothUp() {
System.out.println("You put the lime in the coconut...");
System.out.println("and drink 'em both up...");
System.out.println("You put the lime in the coconut...");
lime.drink();
}
// injected
private Lime lime;
public void setLime(Lime lime) {
this.lime = lime;
}
}
The Coconut class has one simple method, called drinkThemBothUp(). When this method is invoked, certain lyrics from Mr. Nilsson’s song are printed to the System.out. The last line of the method invokes a drink() method on an injected Lime instance to print the final lyric. The injected Lime is any object that implements the following interface:
package com.springinaction.scripting;
public interface Lime {
void drink();
}
When wired up in Spring, the Coconut class is injected with a reference to a Lime object using the following XML:
<bean id="coconut" class="com.springinaction.scripting.Coconut">
<property name="lime" ref="lime" />
</bean>
At this point, I’ve shown you nothing special about the Coconut class or the Lime interface that hints to scripted beans. For the most part, the code presented up to this point resembles the basic JavaBean and DI examples from chapter 2. The only thing missing is the exact implementation of the Lime interface and its declaration in the Spring context. The fact is that any Java-based implementation
of the Lime interface will do. But I promised you a scripted Lime and so a scripted Lime is what I’ll deliver next.
Пишем скриптовый бин
When scripting the Lime interface, we can choose to implement it as either a Ruby, Groovy, or BeanShell script. But regardless of which scripting language is chosen, we first need to do some setup in the Spring context definition file. Consider the following <beans> declaration to see what needs to be done:
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="https://www.springframework.org/schema/lang"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans-2.0.xsd
https://www.springframework.org/schema/lang
https://www.springframework.org/schema/lang/spring-lang-2.0.xsd">
...
</beans>
Spring 2.0 comes with several new configuration elements, each defined in an XML namespace and schema. We'll see a lot more of Spring’s custom configuration namespaces throughout this book. But for now, suffice it to say that the highlighted portions of this <beans> declaration tell Spring that we’re going to use certain configuration elements from the lang namespace. Now that the namespace has been declared in the Spring context file, we’re ready to begin scripting our Lime. Let’s start with a Ruby-colored Lime.
Пишем Лайм на Ruby
In recent years, Ruby has caught the attention of many Java developers, so it’d be no surprise if you’d like to write your scripted beans using this very popular scripting language. The following Ruby script implements the Lime interface and its
drink() method:
class Lime
def drink
puts "Called the doctor woke him up!"
end
end
Lime.new
A very important thing to note here is that the last line of the script instantiates a new Lime object. This line is crucial—without it, there will be no instance of the Lime created that can be wired into other Spring objects. Wiring the Ruby Lime in Spring is a simple matter of using the <lang:jruby> configuration element as follows:
<lang:jruby id="lime"
script-source="classpath:com/springinaction/scripting/Lime.rb"
script-interfaces="com.springinaction.scripting.Lime" />
<lang:jruby>
requires two attributes to be set. The first, script-source, tells Spring where it can locate the script file. Here, the script file is Lime.rb and can be found in the classpath in the same package as the rest of the example code. Meanwhile, the script-interfaces attribute tells Spring what Java interface that the script will be implementing. Here it has been set to our Lime interface.
Пишем Groovy Лайм
Despite Ruby’s growing popularity, many developers will be targeting the Java platform for some time to come. Groovy is a language that mixes some of the best features of Ruby and other scripting languages into a familiar Java syntax—effectively giving a best-of-both-worlds option for Java developers.
For those of you who favor Groovy scripting, here’s an implementation of the Lime interface as a Groovy class:
class Lime implements com.springinaction.scripting.Lime {
void drink() {
print "Called the doctor woke him up!"
}
}
Wiring a Groovy script as a Spring bean is as simple as using the <lang:groovy> configuration element. The following <lang:groovy> configuration loads the Groovy implementation of the Lime interface:
<lang:groovy id="lime"
script-source="classpath:com/springinaction/scripting/Lime.groovy" />
As with the <lang:jruby> element, the script-source attribute specifies the location of the Groovy script file. Again, we’re locating the script file in the classpath in the same package as the example code. Unlike the <lang:jruby> element, however, <lang:groovy> doesn’t require
(or even support) a script-interfaces attribute. That’s because there’s enough information in the Groovy script itself to indicate what interfaces the script implements. Notice that the Groovy Lime class explicitly implements com.springinaction.scripting.Lime.
Пишем Лайм в BeanShell
Another scripting language supported in Spring is BeanShell. Unlike Ruby and Groovy, which both provide their own syntax, BeanShell is a scripting language that mimics Java’s own syntax. This makes it an appealing option if you want to script portions of your application but do not want to have to learn another language. Completing our tour of scripting languages that can be wired in Spring, here’s a BeanShell implementation of the Lime interface:
void drink() {
System.out.println("Called the doctor woke him up!");
}
Probably the first thing you noticed about this BeanShell implementation of Lime is that there isn’t a class definition—only a drink() method is defined. In BeanShell scripts, you only define the methods required by the interface, but no class. Wiring the BeanShell lime is quite similar to wiring the Ruby lime, except that you use the <lang:bsh> element as follows:
<lang:bsh id="lime" script-source="classpath:com/springinaction/scripting/Lime.bsh"
script-interfaces="com.springinaction.scripting.Lime" />
the interface being defined in the script. Now you’ve seen how to configure a scripted bean in Spring and how to wire it into a property of a POJO. But what if you want the injection to work the other way? Let’s see how to inject a POJO into a scripted bean.
Внедрение свойств скриптовых бинов
To illustrate how to inject properties of a scripted bean, let’s flip our lime-coconut example on its head. This time, the coconut will be a scripted bean and the lime will be a Java-based POJO. First up, here’s the Lime class in Java:
package com.springinaction.scripting;
public class LimeImpl implements Lime {
public LimeImpl() {}
public void drink() {
System.out.println("Called the doctor woke him up!");
}
}
LimeImpl is just a simple Java class that implements the Lime interface. And here it is configured as a bean in Spring:
<bean id="lime" class="com.springinaction.scripting.LimeImpl" />
Nothing special so far. Now let’s write the Coconut class as a script in Groovy:
class Coconut implements com.springinaction.scripting.ICoconut {
public void drinkThemBothUp() {
println "You put the lime in the coconut..."
println "and drink 'em both up..."
println "You put the lime in the coconut..."
lime.drink()
}
com.springinaction.scripting.Lime lime;
}
As with the Java version of Coconut, a few lyrics are printed and then the drink() method is called on the lime property to finish the verse. Here the lime property is defined as being some implementation of the Lime interface. Now all that’s left is to configure the scripted Coconut bean and inject it with the lime bean:
<lang:groovy id="coconut"
script-source="classpath:com/springinaction/scripting/Coconut.groovy">
<lang:property name="lime" ref="lime" />
</lang:groovy>
Here the scripted Coconut has been declared similar to how we declared the scripted Lime in previous sections. But along with the <lang:groovy> element is a <lang:property> element to help us with dependency injection.
The <lang:property> element is available for use with all of the scripted bean elements. It is virtually identical in use to the <property> element that you learned about in chapter 2, except that its purpose is to inject values into the properties of scripted beans instead of into properties of POJO beans. In this case, the <lang:property> element is the lime property of the coconut bean with a reference to the lime bean—which in this case is a JavaBean. You may find it interesting, however, that you can also wire scripted beans into the properties
of other scripted beans. In fact, it’s quite possible to wire a BeanShell-scripted bean into a Groovy-scripted bean, which is then wired into a Ruby-scripted bean.
Following that thought to an extreme, it’s theoretically possible to develop an entire Spring application using scripted languages!
Обновление скриптовых бинов
One of the key benefits of scripting certain code instead of writing it in statically compiled Java is that it can be changed on the fly without a recompile or redeployment of the application. If the Lime implementation were to be written in Java and you decided to change the lyric that it prints, you’d have to recompile the implementation class and then redeploy the application. But with a scripting
language, you can change the implementation at any time and have the change applied almost immediately.
When I say “almost immediately,” that really depends on how often you’d like Spring to check for changes to the script. All of the scripting configuration elements have a refresh-check-delay attribute that allows you to define how often (in milliseconds) a script is refreshed by Spring.
By default, refresh-check-delay is set to –1, meaning that refreshing is disabled. But suppose that you’d like the Lime script refreshed every 5 seconds. The following <lang:jruby> configuration will do just that:
<lang:jruby id="lime" script-source="classpath:com/springinaction/scripting/Lime.rb"
script-interfaces="com.springinaction.scripting.Lime" refresh-check-delay="5000" />
It should be pointed out that although this example is for <lang:jruby>, the refresh-check-delay attribute works equally well with <lang:groovy> and <lang:bsh>.
Скриптовые inline бины
Typically you’ll define your scripted beans in external scripting files and refer to them using the script-source attribute of the scripting configuration elements. However, in some cases, it may be more convenient to write the scripting code directly in the Spring configuration file.
To accommodate this, all of the scripting configuration elements support a <lang:inline-script> element as a child element. For example, the following XML defines a BeanShell-scripted Lime directly in the Spring configuration:
<lang:bsh id="lime" script-interfaces="com.springinaction.scripting.Lime">
<lang:inline-script>
<lt;![CDATA[
void drink() {
System.out.println("Called the doctor woke him up!");
}
]]>
</lang:inline-script>
</lang:bsh>
Instead of using script-source, this lime bean has the BeanShell code written as the content of a <lang:inline-script> element.
Take note of the use of <![CDATA[…]]> when writing the inline script. The script code may contain characters or text that may be misinterpreted as XML.
The <![CDATA[…]]> construct prevents scripted code from being parsed by the XML parser. In this example the script contains nothing that would confuse the XML parser. Nevertheless it’s a good idea to use <![CDATA[…]]> anyway, just in case the scripted code changes.
In this section, you were exposed to several Spring configuration elements that step beyond the <bean> and <property> elements that you were introduced to in chapter 2. These are just a few of the new configuration elements that were introduced in Spring 2.0. As you progress through this book, you’ll be introduced to even more configuration elements.
Резюме
As with all of the scripting elements, script-source indicates the location of the script file. And, as with the <lang:jruby> element, script-interfaces specifies Spring’s primary function is to wire application objects together. While chapter 2 showed how to do the basic day-to-day wiring, this chapter showed the more oddball wiring techniques.
To reduce the amount of repeated XML that defines similar beans, Spring offers the ability to declare abstract beans that describe common properties and then “sub-bean” the abstract bean to create the actual bean definitions. Perhaps one of the oddest features of Spring is the ability to alter a bean’s functionality through method injection. Using method injection, you can swap out a bean’s existing functionality for a replaced method definition. Alternatively,используя сеттер-инъекции, Вы можете заменять геттер-методы на другие объявленные с помощью Spring геттеры-методы возвращающие ссылки на бины. Не все объекты в приложении создаются или управляются Spring. Чтобы разрешить
DI для объектов, которые Spring не создает, Spring обеспечивает средства объявления объектов как "Spring-конфигурируемые". Spring-конфигурированные бины перехватываются контейнером Spring, после их создания, и конфигурируются на основе Spring бин шаблона.
Spring использует преимущества редакторов полей так что даже комплексные объекты такие как URL и массивы могут быть сконфигурированы с использованием значений типа String. В этой главе, Вы увидите как создавать собственные редакторы полей для упрощения конфигурирования комплексных полей объекта
Sometimes it is necessary for a bean to interact with the Spring container. For those circumstances, Spring provides several interfaces that enable a bean to be postprocessed, receive properties from an external configuration file, handle application events, and even know their own name. Finally, for the fans of dynamically scripted languages, Spring lets you write beans in scripted languages such as Ruby, Groovy, and BeanShell. This feature supports dynamic behavior in an application by making it possible to hot-swap bean definitions written in one of three different scripting languages.
Now you know how to wire together objects in the Spring container and have seen how DI helps to loosen the coupling between application objects. But DI is only one of the ways that Spring supports loose coupling. In the next chapter, I’ll look at how Spring’s AOP features help break out common functionality from the application objects that it affects.
------------------------
ТРИО теплый пол отзыв
Заработок на сокращении ссылок
Earnings on reducing links
Код PHP на HTML сайты
Категория: Книги по Java
Комментарии |