Spring 3.1: новые возможности

В феврале 2011 года компания SpringSource объявила о выходе новой версии своего основного продукта — Spring Framework 3.1. Пока  пользователям доступны  промежуточные версии (ситуация на июнь 2011 года), выход окончательного релиза (GA) продукта запланирован на конец 2 квартала 2011 года, однако новые особенности, которые появились уже реализованы и могут опробованы уже сейчас. В своем интервью информационному порталу InfoQ, о котором уже сообщалось на нашем сайте,  лидер проекта Spring Framework Юрген Хёллер (Jurgen Hoeller) представил новведения, которые появятся в новых версиях 3.1 и 3.2, целью этой статьи рассказать о них более подробно и представить пример их использования.

Профайлы управляемых компонент (bean definition profiles).

О том как использовать XML файлы или аннотации для конфигурации приложений c использованием SpringFramework существует много различных материалов, в том числе есть и статья на нашем сайте. В новой версии появились дополнительные возможности, которые могут оказаться полезными.

Приложения во время своего создания проходит через несколько различных фаз, такие как например собственно разработка, тестирование, промышленное использовании и во время каждой фазы как правило используются различные ресурсы. Так например во время разработки программист часто работает с СУБД типа HSQLDB или MySQL, установленные на  его личном компьютере, и  как правило запускает Tomcat в качестве сервера приложений (если конечно речь не идет о разработке EJB компонент). В этом случае для доступа к базе данных создается локальный экземпляр класса,  реализующего интерфейс DataSource, и DataSourceTransactionManager управляет транзакциями, то есть конфигурационный файл имеет приблизительно следующий вид:

 

<bean id="dataSource"

      class="org.springframework.jdbc.datasource.DriverManagerDataSource">

      <property name="username" value="${jdbc.username}"/>

      <property name="password" value="${jdbc.password}" />

      <property name="driverClassName" value="${jdbc.driverClassName}" />

      <property name="url" value="${jdbc.url}" />

</bean>

 

<bean id="transactionManager"

      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

      <property name="dataSource" ref="dataSource"/>

</bean>

 

В промышленной эксплуатации приложение как правило исполняется в полновесном сервере приложений (как например Oracle Weblogic), и там для получения DataSource и менеджера транзакций используется JNDI поиск и соответствующая часть конфигурационного файла принимает следующий вид:

 

<tx:jta-transaction-manager />

<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/dataSource"/>

 

Разумеется эти две конфигурации не могут одновременно быть активными, поскольку уже только одинаковые идентификаторы управляемых компонент вызовут проблемы.

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

Для обеспечения этой возможности в Spring 3.1 была реализована новая функциональная возможность, названная профили определения управляемых компонент (bean definition profiles), позволяющая групировать управляемые компоненты с одинаковыми идентификаторами в различные группы и загружать в процессе выполнения необходимую комбинацию.

Предусмотрено несколько возможностей определения профайлов. Первым способом является применение в конфигурационном файле нового атрибута profile XML тэга beans. Для того, чтобы разделить компоненты для разработки и промышленной эксплуатации можно создать соотвественно три различных кофигурационых файла, один определенный обычным образом и содержащий компоненты, которые неизменны во всех окружениях, второй содержит компоненты, применяемые во время разработки:

 

<beans profile="dev">

      <bean id="dataSource"

            class="org.springframework.jdbc.datasource.DriverManagerDataSource">

            <property name="username" value="${jdbc.username}"/>

            <property name="password" value="${jdbc.password}" />

            <property name="driverClassName" value="${jdbc.driverClassName}" />

            <property name="url" value="${jdbc.url}" />

      </bean>

 

      <bean id="transactionManager" 

      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

            <property name="dataSource" ref="dataSource"/>

      </bean>

</beans>

 

В третьем конфигурацом файле определены компоненты, использыемые в промышленной эксплуатации:

 

<beans profile="prod">

      <tx:jta-transaction-manager />

      <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/dataSource"/>

</beans>

 

В Spring 3.1 появилась возможность использовать вложенные <beans> тэги в одном, а не создавать несколько различных  конфигурационных файлов. Используя эту новую особенность, конфигурационный фаул может выглядеть следующим образом:

 

<beans>

….....

<beans profile="dev">

      <bean id="dataSource"

      class="org.springframework.jdbc.datasource.DriverManagerDataSource">

            <property name="username" value="${jdbc.username}"/>

            <property name="password" value="${jdbc.password}" />

            <property name="driverClassName" value="${jdbc.driverClassName}" />

            <property name="url" value="${jdbc.url}" />

      </bean>

 

      <bean id="transactionManager"

      class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

            <property name="dataSource" ref="dataSource"/>

      </bean>

</beans>

 

<beans profile="prod">

      <tx:jta-transaction-manager />

      <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/dataSource"/>

</beans>

 

</beans>

 

Существует и другая возможность указания профайла, для этого создана новая аннотация @Profile, которая имеет единственный строковый параметр, в котором задается имя профайла:

 

@Profile("cacheable")

public class CountryDaoImpl implements CountryDao {

...

}

 

Для активации профайла в Spring 3.1 создана новая абстракция окружения, начиная с этой версии  в контексте ApplicationContext существует объект, реализующий интерфейс Environment. В этом объекте хранится информация о всех активных профайлах.

Активировать определенный профайл можно например явно программным способом,

 

GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();

ctx.getEnvironment ().setActiveProfiles("dev");

ctx.load("classpath:/com/finecosoft/spring31/*-context.xml");

ctx.refresh();

 

Однако такой способ хорошо подходит для использования в unit tests во время разработки и совершенно не пригоден для промышленной эксплуатации, поскольку требует перекомпиляции исходного кода для изменения профайла.

Поэтому предусмотрена и другая возможность актиации, для этого надо инициализировать именем  нужного профайла переменную окружения (property) spring.profile.active.

В этом варианте используется второе назначение новой абстракции окружения Environment — управление иерархией PropertySource, которыеявляется своего рода источником различных переменных окружения/свойств. Когда в приложении необходимо узнать значение той или иной переменной, например следующим образом:

 

String property=ctx.getEnvironment().getProperty("some.property");

 

то будут запрошены по очереди все зарегестрированные PropertySource,  сохранена ли в них эта переменная. По умолчанию автоматически в PropertySource регистрируются параметры JVM и переменные окружения операционной системы. В веб приложении также создаются дополнительный PropertySource для данных, зарегистрированных в web.xml.

 

Абстракция кэширования (cache abstraction)

 

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

В мире java существует множество различный имплементаций кэширования, например Jboss Cache или Ehcache, однако их использование требует достаточно интенсивного кодирования, причем  API каждого кэш- провайдера явяется уникальным и требует большой работы в случае смены имплементации кэширования на новый. Другой проблемой является то, что использование кэширования часто влечет за собой, что в одном методе используется как код, который реализует бизнес логику, так и функциональность, относящаяся к кэшированию, таким образом нарушуется один важных принципов создания качественного программного обеспечения - принципу разделения ответственности (separation of concerns).

Для решения этой проблемы в Spring  Framework 3.1 появилась новая абстракция, которая реализует кэширование как АОП аспект (подробнее об АОП аспектах в можно в частности узнать из статьи «Аспектно-Ориентированное Программирование в Spring» на нашем сайте), который активируется аннотацией @Cacheable. Этой аннотацией помечаются методы класса, которые возращают значения, которые можно сохранить в кэше:

 

public class CountryDaoImpl implements CountryDao {

…...

 

      @Cacheable("countries")

      public Country getCountry(int countryId) {

      }

…...

}

 

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

Существуют и другие параметры этой аннотации, позволящие как задавать условия, при которых значения будут сохранены в кэше — параметр condition, так параметр, который в случае, если аннотируемая функция принимает как параметр сложный объект, позволяющие определять ключ кэша  — параметр key.

Разумеется, аннотация @Cacheable не может применяться только к методам, которые в своей сигнатуре описаны как возвращающие тип void.

В Spring  Framework 3.1 существует также аннотация @CacheEvict, позволяющая в случае необходимости удалять значения из кэша

 

public class CountryDaoImpl implements CountryDao {

…...

 

      @CacheEvict("countries")

      public void deleteCountry(int countryId) {

      }

…...

 

}

 

В настоящее абстракция кэширования поддерживает 2 имплементации — одна базируется на JDK классе ConcurrentHashMap, другая использует библиотеку открытого программного обеспечения Ehcache. Вариант с ConcurrentHashMap прост в конфигурации и использовании и имеет высокую производительность в многопоточной среде, однако в отличии от второго варианта не поддерживает таких важных аспектов как транзакционность и поддержка распределенной конфигурации.

Конфигурация кэша осуществляется в конфигурационном файле Spring с применением нового XML пространства имен (XML namespace) http://www.springframework.org/schema/cache. Элемент <cache:annotation-driven/> активирует кэш и позволяет использовать аннотации @Cacheable и @CacheEvict в приложении. Через атрибут mode этого элемента может быть специфицировано, будут ли при реализации аспектов использованы объекты-посредники (proxies) или байт-код аспекта будет внедрен в код помеченного аннотацией метода (weaving).

Дальше в конфигурации необходимо указать, какая реализация кэша (из двух возможных пока — Ehcache или ConcurrentHashMap) будет использована в приложении, для этого надо сконфигурировать управляемый компонент с идентификатором cacheManager , который будет иницализирован либо из класса SimpleCacheManager; либо EhcacheCacheManager.

 

<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">

      <property name="caches">

            <set>

                  <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">

                        <property name="name" value="cacheArea1"/>

                  </bean>

                  <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">

                        <property name="name" value="cacheArea2"/>

                  </bean>

            </set>

      </property>

</bean>

 

Пример использования

Для демонстрации новых возможностей Spring Framework 3.1 разработано приложение, которое выбирает один за одним всех сотрудников из базы данных. Структура базы данных приведена на следующей диаграмме:

db_structure

 

База данных содержит три таблицы: список стран, список работников и список языков. Каждый работник может разговоривать на нескольких языках (служебная таблица employee_speaks_language служит для реализации отношения многие-ко-многим) и живет в определенной стране.

3 DAO интерфейса определяют существующие операции доступа к базе данных:

 

public interface CountryDao {

      int getCountryId(int employeeId);

      Country getCountry(int countryId);

}

 

public interface EmployeeDAO {

      List<Integer> getEmployeesIds();

      Employee getEmployee(int employeeId);

}

 

public interface LanguageDao {

      List<Integer> getLanguagesIds (int employeeId);

      Language getLanguage(int languageId);

}

 

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

В проекте существуют две различных реализации DAO интерфейсов, выполняющих выборку данных из базы данных. По сути обе реализации идентичны, только у одной некоторые методы помечены аннотацией @Cacheable, а у другой нет:

 

package com.finecosoft.spring31.dao.cacheipml;

...

public class CountryDaoImpl implements CountryDao {

...

      @Cacheable("countries")

      public Country getCountry(int countryId) {

      }

...

}

 

package com.finecosoft.spring31.dao.nocacheipml;

...

public class CountryDaoImpl implements CountryDao {

...

      public Country getCountry(int countryId) {

      }

...

}

 

Для конфигурации приложения использовались вложенные <beans> тэги:

 

<beans      xmlns="http://www.springframework.org/schema/beans"

            xmlns:context="http://www.springframework.org/schema/context"

            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

            xmlns:cache="http://www.springframework.org/schema/cache"

            xsi:schemaLocation="

                  http://www.springframework.org/schema/beans

                  http://www.springframework.org/schema/beans/spring- beans-3.1.xsd

                  http://www.springframework.org/schema/context

                  http://www.springframework.org/schema/context/spring-context-3.1.xsd

                  http://www.springframework.org/schema/cache

                  http://www.springframework.org/schema/cache/spring- cache.xsd">

.....   

      <beans profile="noncacheable">

            <bean id="countryDao"

                  class="com.finecosoft.spring31.dao.nocacheimpl.CountryDaoImpl">

                  <property name="jdbcTemplate" ref="jdbcTemplate"/>

            </bean>

            <bean id="employeeDao"

                  class="com.finecosoft.spring31.dao.nocacheimpl.EmployeeDaoImpl">

                  <property name="jdbcTemplate" ref="jdbcTemplate"/>

            </bean>

            <bean id="languageDao"

                  class="com.finecosoft.spring31.dao.nocacheimpl.LanguageDaoImpl">

                  <property name="jdbcTemplate" ref="jdbcTemplate"/>

            </bean>

      </beans>

 

      <beans profile="cacheable">

            <cache:annotation-driven />

 

            <bean id="countryDao"

                  class="com.finecosoft.spring31.dao.cacheipml.CountryDaoImpl">

                  <property name="jdbcTemplate" ref="jdbcTemplate"/>

            </bean>

            <bean id="employeeDao"

                  class="com.finecosoft.spring31.dao.cacheipml.EmployeeDaoImpl">

                  <property name="jdbcTemplate" ref="jdbcTemplate"/>

            </bean>

            <bean id="languageDao"

                  class="com.finecosoft.spring31.dao.cacheipml.LanguageDaoImpl">

                  <property name="jdbcTemplate" ref="jdbcTemplate"/>

            </bean>

            <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">

                  <property name="caches">

                        <set>

                            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">

                                  <property name="name" value="countries"/>

                            </bean>

                            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">

                                  <property name="name" value="employess"/>

                            </bean>

                            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">

                                  <property name="name" value="languages"/>

                            </bean>

                        </set>

                  </property>

            </bean>

      </beans>

</beans>

 

Выбор какую реализацию использовать происходит заданием программным образом имени необходимого профайла:

 

public static void main(String[] args) {

      GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();

      ctx.getEnvironment().setActiveProfiles("noncacheable");

      ctx.load ("classpath:/com/finecosoft/spring31/applicationContext.xml");

      ctx.refresh();

 

      EmployeeInventory inventory = ctx.getBean(EmployeeInventory.class);

 

      long startTime = System.currentTimeMillis();

      inventory.getAllEmployees();

      System.out.println("Get all employess in non cacheable profile : "

                  + (System.currentTimeMillis() - startTime)

                  + " ms.");

 

      ctx = new GenericXmlApplicationContext();

      ctx.getEnvironment().setActiveProfiles ("cacheable");

      ctx.load("classpath:/com/finecosoft/spring31/applicationContext.xml");

      ctx.refresh();

 

      inventory = ctx.getBean(EmployeeInventory.class);

 

      startTime = System.currentTimeMillis();

      inventory.getAllEmployees();

      System.out.println("Get all employess in cacheable profile : "

                  + (System.currentTimeMillis() - startTime)

                  + " ms.");

}

 

Если запустить это приложение, то на системной консоли можно увидеть примерно следующий результат работы:

 

Get all employess in non cacheable profile : 25953 ms.

Get all employess in cacheable profile : 8140 ms.

 

Исходные тексты проекта, приведенного в статье, можно скачать здесь.

Скрипт для создания базы данных в MySQL можно скачать здесь.

 

© 2008-2023 Финэкософт.

 

Oracle Silver Partner
+7 (495) 234 8808
Учебный центр
Центр обучения и сертификации в области информационных технологий (IT).

Широкий выбор курсов и программ обучения. Подробности здесь.

Отправить письмо
Обратная связь

 

Для Ваших вопросов и отзывов