Количество сред, в которых ваше приложение должно работать, прежде чем оно заработает, обычно зависит от нескольких вещей, включая бизнес-процессы вашей организации, масштаб вашего приложения и его «важность» (например, если вы пишете сбор налогов) система для службы доходов вашей страны, тогда процесс тестирования может быть более строгим, чем если бы вы писали приложение электронной коммерции для местного магазина). Чтобы получить картину, ниже приведен быстрый (и, вероятно, неполный) список всех различных окружающих сред, которые приходили на ум:
- Локальный компьютер разработчика
- Тестовая Машина Развития
- Функциональная испытательная машина испытательных команд
- Интеграционная испытательная машина
- Окружающая среда клонов (копия live)
- Жить
Это не новая проблема, и обычно она решается путем создания набора Spring XML и файлов свойств для каждой среды. XML-файлы обычно состоят из основного файла, который импортирует другие специфичные для среды файлы. Затем они объединяются во время компиляции для создания различных файлов WAR или EAR. Этот метод работал годами, но у него есть несколько проблем:
- Это нестандартно. У каждой организации обычно есть свой способ решения этой проблемы, при этом нет двух одинаковых методов.
- Это сложно реализовать, оставляя много места для ошибок.
- Для каждой среды должен быть создан и развернут другой файл WAR / EAR, который требует времени и усилий, которые могут быть лучше потрачены на написание кода.
Различия в конфигурациях Spring bean обычно можно разделить на две части. Во-первых, существуют специфические для среды свойства, такие как URL-адреса и имена баз данных. Они обычно внедряются в XML-файлы Spring с использованием класса PropertyPlaceholderConfigurer и связанной нотации $ {} .
1
2
3
4
5
6
7
|
< bean id = 'propertyConfigurer' class = 'org.springframework.beans.factory.config.PropertyPlaceholderConfigurer' > < property name = 'locations' > < list > < value >db.properties</ value > </ list > </ property > </ bean > |
Во-вторых, существуют специфические для среды классы компонентов, такие как источники данных, которые обычно различаются в зависимости от того, как вы подключаетесь к базе данных.
Например, в разработке вы можете иметь:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
< bean id = 'dataSource' class = 'org.springframework.jdbc.datasource.DriverManagerDataSource' > < property name = 'driverClassName' > < value >${database.driver}</ value > </ property > < property name = 'url' > < value >${database.uri}</ value > </ property > < property name = 'username' > < value >${database.user}</ value > </ property > < property name = 'password' > < value >${database.password}</ value > </ property > </ bean > |
… Пока вы в тесте или вживую, вы просто напишите:
1
|
< jee:jndi-lookup id = 'dataSource' jndi-name = 'jdbc/LiveDataSource' /> |
В руководствах Spring говорится, что профили Spring должны использоваться только во втором примере выше: классы, специфичные для bean-компонентов, и что вы должны продолжать использовать PropertyPlaceholderConfigurer для инициализации простых свойств bean-компонента; однако вы можете использовать профили Spring для внедрения среды PropertyPlaceholderConfigurer, специфичной для вашей среды, в контекст Spring.
Сказав это, я собираюсь нарушить это соглашение в моем примере кода, так как хочу, чтобы самый простой код продемонстрировал возможности профиля Spring.
Профили Spring и конфигурация XML
С точки зрения конфигурации XML, Spring 3.1 вводит новый атрибут профиля в элемент bean-компонентов схемы spring-beans:
1
|
< beans profile = 'dev' > |
Именно этот атрибут профиля действует как переключатель при включении и отключении профилей в разных средах.
Чтобы объяснить все это далее, я собираюсь использовать простую идею, что ваше приложение должно загружать класс person, и этот класс person содержит различные свойства в зависимости от среды, в которой работает ваша программа.
Класс Person очень тривиален и выглядит примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
public class Person { private final String firstName; private final String lastName; private final int age; public Person(String firstName, String lastName, int age) { this .firstName = firstName; this .lastName = lastName; this .age = age; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } public int getAge() { return age; } } |
… И определяется в следующих файлах конфигурации XML:
01
02
03
04
05
06
07
08
09
10
11
12
|
<? xml version = '1.0' encoding = 'UTF-8' ?> xsi:schemaLocation = 'http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd' profile = 'test1' > < bean id = 'employee' class = 'profiles.Person' > < constructor-arg value = 'John' /> < constructor-arg value = 'Smith' /> < constructor-arg value = '89' /> </ bean > </ beans > |
…и
01
02
03
04
05
06
07
08
09
10
11
12
|
<? xml version = '1.0' encoding = 'UTF-8' ?> xsi:schemaLocation = 'http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd' profile = 'test2' > < bean id = 'employee' class = 'profiles.Person' > < constructor-arg value = 'Fred' /> < constructor-arg value = 'ButterWorth' /> < constructor-arg value = '23' /> </ bean > </ beans > |
… Называемые test-1-profile.xml и test-2-profile.xml соответственно (запомните эти имена, они важны позже). Как видите, единственные различия в конфигурации — это имя, фамилия и возраст.
К сожалению, недостаточно просто определить свои профили, вы должны указать Spring, какой профиль вы загружаете. Это означает, что следующий старый «стандартный» код теперь не будет работать:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Test (expected = NoSuchBeanDefinitionException. class ) public void testProfileNotActive() { // Ensure that properties from other tests aren't set System.setProperty( 'spring.profiles.active' , '' ); ApplicationContext ctx = new ClassPathXmlApplicationContext( 'test-1-profile.xml' ); Person person = ctx.getBean(Person. class ); String firstName = person.getFirstName(); System.out.println(firstName); } |
К счастью, есть несколько способов выбора вашего профиля, и, на мой взгляд, наиболее полезным является использование системного свойства spring.profiles.active. Например, следующий тест теперь пройдет:
01
02
03
04
05
06
07
08
09
10
11
|
System.setProperty( 'spring.profiles.active' , 'test1' ); ApplicationContext ctx = new ClassPathXmlApplicationContext( 'test-1-profile.xml' ); Person person = ctx.getBean(Person. class ); String firstName = person.getFirstName(); assertEquals( 'John' , firstName); |
Очевидно, что вы не захотите жестко кодировать вещи, как я делал выше, и лучшая практика обычно подразумевает разделение определений системных свойств для вашего приложения. Это дает вам возможность использовать простой аргумент командной строки, такой как:
1
|
-Dspring.profiles.active= 'test1' |
… или добавив
1
2
|
# Setting a property value spring.profiles.active=test1 |
к кошке Tomcat's.properties
Итак, это все, что нужно сделать: вы создаете свои профили Spring XML с помощью атрибута профиля элемента beans и включаете профиль, который хотите использовать, установив системное свойство spring.profiles.active на имя вашего профиля .
Доступ к некоторой дополнительной гибкости
Тем не менее, это еще не конец истории, поскольку Парень из Spring добавил несколько способов программной загрузки и включения профилей — если вы решите это сделать.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test public void testProfileActive() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext( 'test-1-profile.xml' ); ConfigurableEnvironment env = ctx.getEnvironment(); env.setActiveProfiles( 'test1' ); ctx.refresh(); Person person = ctx.getBean( 'employee' , Person. class ); String firstName = person.getFirstName(); assertEquals( 'John' , firstName); } |
В приведенном выше коде я использовал новый класс ConfigurableEnvironment для активации профиля «test1».
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test public void testProfileActiveUsingGenericXmlApplicationContextMultipleFilesSelectTest1() { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ConfigurableEnvironment env = ctx.getEnvironment(); env.setActiveProfiles( 'test1' ); ctx.load( '*-profile.xml' ); ctx.refresh(); Person person = ctx.getBean( 'employee' , Person. class ); String firstName = person.getFirstName(); assertEquals( 'John' , firstName); } |
Тем не менее, The Guys At Spring теперь рекомендуют использовать класс GenericApplicationContext вместо ClassPathXmlApplicationContext и FileSystemXmlApplicationContext, поскольку это обеспечивает дополнительную гибкость. Например, в приведенном выше коде я использовал метод load (...) GenericApplicationContext для загрузки нескольких файлов конфигурации с использованием подстановочного знака :
1
|
ctx.load( '*-profile.xml' ); |
Помните имена файлов ранее? Это загрузит и test-1-profile.xml, и test-2-profile.xml .
Профили также включают дополнительную гибкость, которая позволяет активировать более одного одновременно. Если вы посмотрите на приведенный ниже код, то увидите, что я активирую оба моих профиля test1 и test2 :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Test public void testMultipleProfilesActive() { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ConfigurableEnvironment env = ctx.getEnvironment(); env.setActiveProfiles( 'test1' , 'test2' ); ctx.load( '*-profile.xml' ); ctx.refresh(); Person person = ctx.getBean( 'employee' , Person. class ); String firstName = person.getFirstName(); assertEquals( 'Fred' , firstName); } |
Осторожно, в этом примере у меня есть два бина с одинаковым идентификатором «employee», и нет способа определить, какой из них действителен и должен иметь приоритет. Исходя из моего теста, я предполагаю, что второй, который читается, перезаписывает или маскирует доступ к первому. Это нормально, так как у вас не должно быть нескольких бинов с одним и тем же именем — это просто то, на что нужно обратить внимание при активации нескольких профилей.
Наконец, одно из лучших упрощений, которое вы можете сделать, это использовать вложенные элементы <beans />.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<? xml version = '1.0' encoding = 'UTF-8' ?> xmlns:xsi = 'http://www.w3.org/2001/XMLSchema-instance' xmlns:context = 'http://www.springframework.org/schema/context' 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'> < beans profile = 'test1' > < bean id = 'employee1' class = 'profiles.Person' > < constructor-arg value = 'John' /> < constructor-arg value = 'Smith' /> < constructor-arg value = '89' /> </ bean > </ beans > < beans profile = 'test2' > < bean id = 'employee2' class = 'profiles.Person' > < constructor-arg value = 'Bert' /> < constructor-arg value = 'William' /> < constructor-arg value = '32' /> </ bean > </ beans > </ beans > |
Это устраняет все утомительные манипуляции с подстановочными знаками и загрузкой нескольких файлов, хотя и за счет минимальной гибкости.
Мой следующий блог завершает мой взгляд на профили Spring, рассматривая аннотацию @Configuration, используемую в сочетании с новой аннотацией @Profile … так что об этом позже.
Ссылка: Использование Spring Profiles в XML Config от нашего партнера по JCG Роджера Хьюза в блоге Captain Debug’s Blog .