The Spring framework: Основы

Автор: content Вторник, Апрель 10th, 2012 Нет комментариев

Рубрика: Язык Java

Spring это легковесный opensource J2EE Framework, разработка которого началась в феврале 2003 года. Основой послужил программный код, опубликованный в книге Expert One-on-One Design and Development (2002 год). Причем основные идеи возникли еще в 2000 году и являлись отражением опыта успешной разработки нескольких коммерческих проектов.

Сейчас Spring это достаточно популярный opensource проект, охватывающий многие аспекты как J2EE, так и Java разработок.

Целью данной статьи является описание архитектуры и основных возможностей Spring.

Архитектура

Архитектура Spring представлена следующей схемой


Рис. 1 Схема SpringДалее в статье будут рассмотрены основные составляющие Spring Framework.

Ядро

 

1. IoC контейнер

В основе Spring лежит паттерн Inversion of control. Применительно к легковесным контейнерам, основная идея этого паттерна заключается в устранении зависимости компонентов или классов приложения от конкретных реализаций вспомогательных интерфейсов и делегировании полномочий по управлению созданием нужных реализаций IoC контейнеру. Рассмотрим UML диаграмму.


Рис. 2IoC контейнер отвечает за создание нужной реализации Product для Consumer. При использовании класса Consumer в других проектах мы сможем заменить реализацию интерфейса Product на более подходящую, не внося изменений в код.

Основные преимущества IoC контейнеров:

  1. управление зависимостями
  2. упрощение повторного использования классов или компонентов
  3. упрощение unit-тестирования
  4. более «чистый» код (Классы больше не занимаются инициализацией вспомогательных объектов. Не стоит, конечно «перегибать палку», управляя созданием абсолютно всех объектов через IoC. В IoC контейнер лучше всего выносить те интерфейсы, реализация которых может быть изменена в текущем проекте или в будущих проектах.)

Паттерн IoC не единственный, позволяющий устранить зависимость от реализации. Альтернативой IoC являются, хорошо известные шаблоны ServiceLocator/Factory.


Рис. 3 Шаблон ServiceLocatorДанный подход вносит зависимость кода от ServiceLocator, что приводит к меньшей гибкости. Основные недостатки шаблона ServiceLocator:

  1. Если в одном проекте в разных случаях потребуются разные реализации Product, придется либо менять код вызова ServiceLocator во всех используемых местах, указывая дополнительные параметры либо менять сам ServiceLocator. Оба варианта приводят к «замусориванию» кода, внесению дополнительных условий и параметров, которые усложняют код и его повторное использование.
  2. При использовании кода в других проектах
  • (ServiceLocator распространяется вместе с кодом) В случае, когда для получения нужной реализации Product потребуются дополнительные параметры придется вносить изменения в поставляемый ServiceLocator. Это еще приводит к существованию в системе нескольких ServiceLocator, каждый из которых необходимо по-своему настраивать, что усложняет сопровождение.
  • (ServiceLocator не поставляется с кодом) В этом случае придется менять вызов ServiceLocator на используемый в данной системе. Введение общего интерфейса так же не решает проблемы.

Самый распространенный способ указания зависимостей это XML файлы конфигурации (applicationContext.xml). Описание формата этого файла выходит за рамки данной статьи.

Spring гораздо больше, чем просто IoC контейнер. Framework упрощает разработку J2EE проектов, реализуя низкоуровневые и наиболее часто иcпользуемые части корпоративного приложения.

AOP

Одним из ключевых компонентов Spring является AOP framework. Даже не используя AOP напрямую вы, скорее всего, столкнетесь с ним косвенно.

Основные задачи, которые решаются с помощью AOP в Spring:

  1. Декларативное управление транзакциями.
  2. Организация пулов объектов.
  3. Написание собственных аспектов.

Наиболее удобный и гибкий способ управления транзакциями это декларативное управление транзакциями. Если вы раньше работали с EJB CMT, то прекрасно представляете что это такое. Декларативное управление транзакциями избавляет код от зависимости от фреймворка или конкретного механизма управления транзакциями.

Конфигурационный файл (applicationContext.xml) выглядит следующим образом:

<bean id=»myServiceTarget»/>

<bean id=»myService»
>
<property name=»transactionManager»>
<ref local=»transactionManager»/>
</property>
<property name=»target»><ref local=»myServiceTarget»/></property>
<property name=»transactionAttributes»>
<props>
<prop key=»get*»>PROPAGATION_REQUIRED,readOnly</prop>
<prop key=»create*»>PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>

Объект myService это уже транзакционный MyBusinessObject, доступ к нему осуществляется через Proxy (TransactionProxyFactoryBean), который и управляет транзакциями. Для пользователя не будет никакой разницы, что работа с Proxy, что работа с MyBusinessObject напрямую. Есть и другой способ описания транзакционной конфигурации, через BeanNameAutoProxyCreator. Данный способ позволяет сократить размер конфигурационного файла. Для более подробной информации обратитесь к документации.

Организация пулов объектов применяется и в EJB. Контейнер поддерживает пул stateless session EJB, при вызове метода объект освобождается из пула. При использовании Spring, пул объектов может поддерживаться для любых POJO (Plain Old Java Object). Пример конфигурационного файла:

<bean id=»myServiceTarget»
singleton=»false»/>

<bean id=»myServiceTargetSource»
>
<property name=»targetBeanName» value=»myServiceTarget»/>
<property name=»maxSize»>25</property>
</bean>

<bean id=»myService»
>
<property name=»targetSource» value=»myServiceTargetSource»/>
</bean>

AOP помогает устранить дублирование кода. Java это объектно-ориентированный язык, который позволяет создавать иерархии взаимосвязанных объектов. Но как быть, если один и тот же код используется в разных иерархиях объектов?

Любое приложение можно рассматривать с двух позиций. С точки зрения функциональности отдельных классов (core concerns) и функционала охватывающего все приложение (crosscutting concerns). Модули, управляющие crosscutting concerns, называются аспектами. В Spring аспекты реализуются через Advisors и Interceptors.

Аспекты могут решать следующие задачи: логирование, кэширование, управление безопасностью, транзакции и т.д. Введение дополнительного слоя интерцепторов разграничивает ответственность между основным кодом и вспомогательными задачами. В результате упрощается сопровождение кода и его повторное использование. Интерцепторы конфигурируются независимо, они могут использоваться в других проектах, в свою очередь объекты могут конфигурироваться с разными интерцепторами.

В качестве примера возьмем задачу — определение времени выполнения методов. Это может быть полезно для выявления узких мест, как во время тестирования, так и на production при реальном количестве пользователей на реальных данных.

public class PerformanceInterceptor implements MethodInterceptor {
private static Logger log = Logger.getLogger(PerformanceInterceptor.class);

public Object invoke(MethodInvocation methodInvocation) throws Throwable {
Method method = methodInvocation.getMethod();

log.info(«========= Start Performance log =============»);
log.info(«1. executing method: » + method.getName());

long startTime = System.currentTimeMillis();
Object result = methodInvocation.proceed();
long endTime = System.currentTimeMillis();

log.info(«2. time: » + (endTime — startTime) + » ms»);
log.info(«========= End Performance log =============»);

return result;
}
}

Обратите внимание на интерфейс org.aopalliance.intercept.MethodInterceptor. Это интерфейс AOP Alliance. Соответственно такие интерцепторы переносимы в другие AOP frameworks. В Spring есть свои собственные интерфейсы интерцепторов, предназначенные для разных случаев. В данной статье эти интерфейсы не рассматриваются.

MethodInterceptor предоставляет наиболее полный контроль над процессом выполнения. Для нашей задачи подходит именно этот интерфейс.

В строчке 12 происходит вызов следующего интерцептора в цепочке либо метода основного объекта. Заметьте, для того, что бы PerformanceInterceptor предоставлял наиболее точную информацию о времени выполнения метода он должен быть последним в цепочке.

Теперь осталось только изменить конфигурационный файл.

<bean id=»classForInterceptorTarget»
/>

<bean id=»performanceInterceptor»
/>

<bean id=»classForInterceptor»
>
<property name=»target» ref=»classForInterceptorTarget»/>
<property name=»interceptorNames»>
<list>
<value>performanceInterceptor</value>
</list>
</property>
</bean>

Дополнительные возможности
Как уже было сказано ранее, Spring помимо IoC контейнера включает в себя множество классов, реализующих наиболее востребованный функционал J2EE приложений. Spring существенно упрощает работу с JDBC, ORM frameworks, EJB, Web services. Framework реализует более высокий уровень абстракции управления рассылкой почты, кэшированием, выполнению задач по расписанию. Помимо этого Spring включает в себя еще и Web framework.

Маловероятно, что в разрабатываемом приложении будут использоваться все возможности Spring. Поэтому вы можете включать в приложение только те jar-архивы, классы которых вы используете. Spring группирует классы в jar по пакетам: spring-aop.jar, spring-beans.jar и т.д. Вы так же можете включить один jar со всеми классами — spring.jar.

Далее будут описаны некоторые из возможностей Spring Framework.

JDBC

Начнем сразу с примера.

public class SimpleDAO extends JdbcDaoSupport {
// SqlRowSet это disconnected java.sql.ResultSet
public SqlRowSet getUserRowSet(String id) {
return getJdbcTemplate().queryForRowSet(
«select name, email from user where id = ?»,
new Object[] {id});
}
public int getMaxAmount() {
return getJdbcTemplate().queryForInt(«select max(amount) from payments»);
}
// Результат запроса «оборачивается» в бин User
public List getUsersByName(String username) {
return getJdbcTemplate().query(
«select * from user where username = ?»,
new Object[] { username },
new RowMapper() {
public Object mapRow(ResultSet resultSet, int i)
throws SQLException {
User user = new User();
user.setId(resultSet.getInt(1));
user.setName(resultSet.getString(2));
return user;
}
});
}}

Больше не нужно писать громоздкие конструкции try {..} catch (..) finally {..}, управлять соединениями, транзакциями. Простые запросы вообще умещаются в одну строку.
Интернализация (i18n) и конфигурация

Spring включает классы и интерфейсы, предназначенные для интернализации приложения. Все сообщения храняться в properties файлах. Один properties файл соответствует одному языку. Для того, что бы получить доступ к этим сообщениям, в applicationContext.xml необходимо добавить messageSource бин. Имя messageSource является обязательным. Spring поддерживает иерархические сообщения.

<bean id=»messageSource»
>
<property name=»basenames»>
<list>
<value>springfundamentals.i18n.messages</value>
<value>springfundamentals.i18n.formats</value>
</list>
</property>
</bean>

В интерфейсе ApplicationContext (основной интерфейс, определяющий конфигурацию приложения) есть соответствующие методы getMessage(MessageSourceResolvable resolvable, Locale locale), getMessage(String code, Object[] args, Locale locale), getMessage(String code, Object[] args, String defaultMessage, Locale locale). Пример использования:

messages_en.properties
======================
mesg=test message parameter: {0}

ApplicationContext context = …
String message = context.getMessage(
«mesg»,
new Object[] {«paramName»} ,
«default message»,
Locale.US));

Все конфигурационные параметры приложения удобно хранить в одном или нескольких properties файлах. К ним можно будет обращаться прямо из applicationContext.xml. Для этого в applicationContext.xml нужно добавить бин.

<bean id=»placeholderConfig»
>
<property name=»location» value=»WEB-INF/config.properties»/>
</bean>

Тогда к параметрам можно будет обращаться следующим образом.

<bean id=»dataSource»
>
<property name=»jndiName» value=»${jdbc.jndiName}»/>
</bean>

Где jdbc.jndiName конфигурационный параметр в WEB-INF/config.properties.
Events

Обработка событий в Spring осуществляется через класс ApplicationEvent и интерфейс ApplicationListener. При наступлении события нотифицируются все объекты, реализующие интерфейс ApplicationListener. Рассмотрим пример.

public class EventPublisher implements ApplicationContextAware {
private ApplicationContext context;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.context = applicationContext;
}
public void go() {
context.publishEvent(new ApplicationEvent(«event») {
public long getTimestamp() {
return super.getTimestamp();
}
});
}
}
public class EventListener implements ApplicationListener {
public void onApplicationEvent(ApplicationEvent event) {
System.out.println(«Event: » + event.getSource());
}
}

Вызов context.publishEvent() происходит в синхронном режиме. Все ApplicationListener выполняются в том же транзакционном контексте, что и EventPublisher.
Заключение

В статье были рассмотрены базовые принципы и некоторые из возможностей Spring. Для более подробного изучения рекомендую прочитать книгу Spring in Action и документацию, входящую в дистрибутив.
К основным достоинствам Spring относятся:простота; удобство тестирования; использование Spring положительно сказывается на дизайне приложения и простоте кода.

В Spring реализовано многое без чего не обходится практически любое Java приложение. Прежде чем изобретать, что-то свое посмотрите, возможно, это уже есть в Spring.

Источник: http://www.javaportal.ru/java/articles/spring.html
Автор:Александров Сергей

Оставить комментарий

Чтобы оставлять комментарии Вы должны быть авторизованы.

Похожие посты