-
Notifications
You must be signed in to change notification settings - Fork 56
Сборка проекта с разделением по профилям
При сборке Maven может использовать профили для настройки процесса сборки. Фактически профиль - поименнованное множество настроек.
Профили объявляются в pom.xml в разделе <profiles />
.
Объявим 2 профиля: devel
и prod
. Профиль devel
будет включён по умолчанию.
pom.xml
<profiles>
<profile>
<id>devel</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<some.property>some 13 connection</some.property>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<some.property>some production connection</some.property>
</properties>
</profile>
</profiles>
Рассмотрим элементы подробнее:
-
<profile />
- опредеяет настройки профиля-
<id />
- идентификатор профиля -
<activation />
- раздел активации, то есть при каком сочетании признаков данный профиль будет выбран (например, можно делать билды для различных OS или при некоторой комбинации переменных окружения и т.д.).-
<activeByDefault />
- указывает, что профиль выбран по умолчанию
-
-
<properties />
- раздел свойств (будет использованы для подстановки)
-
Так же в <profile />
могут входить описание заисимостей, раздел билд, раздел плагинов, описание репозиториев и т.д.
Активация профиля осуществляется с помощью ключа -P
при вызове maven.
Например:
-
mvn clean install
- сборка с профилем по умолчанию (devel
в нашем случае) -
mvn -Pdevel clean install
- сборка с профилемdevel
-
mvn -Pprod clean install
- сборка с профилемprod
-
mvn -Ptest,jdbc clean install
- сборка с профилямиtest
иjdbc
Кроме сборки по профилям хотелось бы влиять на ресурсы в приложении. Например, в зависимоти от провиля maven выбирать подключение к БД или, например, выбирать профиль Spring.
Для этих целей можно использовать resource filtering. То есть в процессе копирования ресурсов для сборки будет проведена фильтрация файлов с подстановкой переменных из профиля pom.
В предыдущем примере была объявлена переменная some.property
на уровне pom.
Теперь создадим файл контекста spring и установим в качестве некоторого параметра строку из pom.
app-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="some_bean" class="java.lang.String">
<constructor-arg index="0" value="${some.property}" />
</bean>
</beans>
После обработки maven значение ${some.property}
будет заменено на значение свойства из pom.xml.
Теперь необходимо включить resource filtering.
pom.xml
<build>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
Данная настройка указывает директорию с ресурсами и что к ресурсам будет применена фильтрация.
Добавим плагины для сборки. Результрующий раздел build будет выглядеть так:
<build>
<resources>
<resource>
<directory>src/main/resources/</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<addMavenDescriptor>false</addMavenDescriptor>
<compress>true</compress>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>libs/</classpathPrefix>
<mainClass>a1s.learn.App</mainClass>
</manifest>
</archive>
</configuration>
<version>2.4</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
</configuration>
</execution>
</executions>
<version>2.5.1</version>
</plugin>
</plugins>
</build>
Проверим сборку с разными профилями:
$ mvn clean install
...
$ java -jar target/pres_0_6-1.0-SNAPSHOT.jar
Variable is some 13 connection
$ mvn -Pprod clean install
...
$ java -jar target/pres_0_6-1.0-SNAPSHOT.jar
Variable is some production connection
Как видно из вывода значение строки было подставлено из pom.
Сделаем 2 билда и посмотрим на файл ресурсов:
$ mvn -Pprod clean install
...
$ unzip -p target/pres_0_6-1.0-SNAPSHOT.jar app-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="some_bean" class="java.lang.String">
<constructor-arg index="0" value="some production connection" />
</bean>
</beans>
$ mvn clean install
...
$ unzip -p target/pres_0_6-1.0-SNAPSHOT.jar app-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="some_bean" class="java.lang.String">
<constructor-arg index="0" value="some 13 connection" />
</bean>
</beans>
В Spring есть собственный механизм профилей. Выбор активного пофиля влияет на DI-контейнер во время выполнения. Активными могут быть несколько профилей одновременно.
Use case'ы использования профилей в Spring:
- разделение на production, devel и testing профили
- профили разделённые по поставщикам данных, хранение базы данных пользователей в LDAP или базе данных("jdbc,ldap" или "hibernate,ldap" или "jdbc,mysql")
Для задания текущего активного профиля для Spring есть несколько способов:
- используя метод
setActiveProfiles()
уApplication
applicationContext.getEnvironment().setActiveProfiles("jdbc,ldap");
- используя переменную окружения
spring.profiles.active
$ spring.profiles.active="jdbc,ldap" java -jar somejar.jar
- используя переменную JVM
spring.profiles.active
$ java -Dspring.profiles.active="jdbc,ldap" -jar some.jar
- по умолчанию используется профиль с именем
default
В качестве тестового примера создадим приложение, в котором сочетаются 2 источника данных и различные профили для разработки, тестирование и производственного использования.
Будем создавать сервис, который получает данные(эмулирует получение) по HTTP RESP:
- для production с использованием шифрования
- для разработки без шифрования
- для тестирования fake-объект
В качестве источников данных будут:
- MySQL
- PostgreSQL (по умолчанию)
Branch с приложением https://github.com/wizardjedi/my-spring-learning/tree/pres_0_7
Создадим Java-приложение на основе Maven.
Добавим в pom.xml
зависимости от сторонних библиотек: JUnit и spring framework.
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.2.0.RELEASE</version>
</dependency>
Для начала создадим основной класс приложения learn.sprofile.App
, который создаёт контекст приложения
App.java
package learn.sprofile;
import org.springframework.context.support.GenericXmlApplicationContext;
public class App
{
public static void main( String[] args )
{
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("app-context.xml");
Service service = (Service)ctx.getBean("service");
System.out.println("Requester " + service.serve());
System.out.println("Data provider" + service.getData());
}
}
Данное приложение загружает контекст Spring, создаёт bean с названием service
и вызывает 2 метода для печати.
Создадим интерфейс для сервиса:
Service.java
package learn.sprofile;
public interface Service {
public String serve();
public String getData();
}
Создадим реализацию для сервиса:
ServiceImpl.java
package learn.sprofile;
public class ServiceImpl implements Service{
public HttpRequester request;
public DAO dao;
public HttpRequester getRequest() {
return request;
}
public void setRequest(HttpRequester request) {
this.request = request;
}
public DAO getDao() {
return dao;
}
public void setDao(DAO dao) {
this.dao = dao;
}
public String serve() {
return request.request();
}
public String getData() {
return dao.getData();
}
}
Создадим интерфейсы для источника данных и объекта запросов.
DAO.java
package learn.sprofile;
interface DAO {
public String getData();
}
HttpRequester.java
package learn.sprofile;
interface HttpRequester {
public String request();
}
Далее опишем различия для каждого из профилей и специфические настройки.
В профиле для разработчика необходимо реализовать класс для обычных запросов по Http.
HttpPlainRequester.java
package learn.sprofile;
public class HttpPlainRequester implements HttpRequester {
public String request() {
return HttpPlainRequester.class.getName();
}
}
В профиле для тестирования реализует fake-requester.
HttpFakeRequester.java
package learn.sprofile;
public class HttpFakeRequester implements HttpRequester {
public String request() {
return HttpFakeRequester.class.getName();
}
}
Кроме того напишем Unit-test для нашего приложения.
AppTest.java
package learn.sprofile;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:app-context.xml")
@ActiveProfiles(profiles = {"test","mysql"})
public class AppTest extends TestCase {
@Autowired
public Service service;
@Test
public void testData() {
assertEquals(MySQLDAO.class.getName(), service.getData());
}
@Test
public void testRequest() {
assertEquals(HttpFakeRequester.class.getName(), service.serve());
}
}
Тест очень простой и проверяет правильную установку классов для сервиса.
-
@RunWith(SpringJUnit4ClassRunner.class)
указывает, что тест будет запуска в инфраструктуре Spring -
@ContextConfiguration(locations = "classpath:app-context.xml")
- указывает на расположение XML-файла конфигурации контекста -
@ActiveProfiles(profiles = {"test","mysql"})
- указывает активные профили для теста
Кроме того, можно опустить аннотацию @ActiveProfiles
, а воспользоваться заданием активного профиля через переменнуб окружения. Для этого установим соответствующую переменную окружения в конфиге тестового плагина.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.7.1</version>
<configuration>
<systemPropertyVariables>
<spring.profiles.default>dev</spring.profiles.default>
</systemPropertyVariables>
</configuration>
</plugin>
В производственном профиле будет класс для запросов с использованием шифрования.
HttpCryptoRequester.java
package learn.sprofile;
public class HttpCryptoRequester implements HttpRequester {
public String request() {
return HttpCryptoRequester.class.getName();
}
}
package learn.sprofile;
public class MySQLDAO implements DAO{
public String getData() {
return MySQLDAO.class.getName();
}
}
package learn.sprofile;
public class PostgresqlDAO implements DAO{
public String getData() {
return PostgresqlDAO.class.getName();
}
}
Создадим app-context.xml
в /src/main/resources/app-context.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
">
<bean id="service" class="learn.sprofile.ServiceImpl">
<property name="dao" ref="daoBean" />
<property name="request" ref="httpRequester" />
</bean>
<beans profile="production">
<bean id="httpRequester" class="learn.sprofile.HttpCryptoRequester" />
</beans>
<beans profile="test">
<bean id="httpRequester" class="learn.sprofile.HttpFakeRequester" />
</beans>
<beans profile="devel,default">
<bean id="httpRequester" class="learn.sprofile.HttpPlainRequester" />
</beans>
<beans profile="postgresql,default">
<bean id="daoBean" class="learn.sprofile.PostgresqlDAO" />
</beans>
<beans profile="mysql">
<bean id="daoBean" class="learn.sprofile.MySQLDAO" />
</beans>
</beans>
Для элемента корневого элемента <beans />
есть возможность указать дочерний элемент <beans />
, который и указывает разделение по профилям. Профиль, для которого описывается конфигурация, указывается в атрибуте profile
. Можно указать несколько профилей разделённых запятыми или пробелами.
###Сборка проекта#
$ mvn clean install
###Запуск приложения#
Для запуска по умолчанию: devel профиль и postgresql
$ mvn exec:java -Dexec.mainClass="learn.sprofile.App"
Например, тестовый профиль и mysql.
$ mvn exec:java -Dexec.mainClass="learn.sprofile.App" -Dspring.profiles.active="test,mysql"
Профили Maven и Spring относятся к разным фазам: сборки для Maven и выполнения для Spring, поэтому объединить профили автоматически не получится. Более менее модули стыкуются для фазы тестирования, потому как Maven устанаваливает системные переменные.
Возможным решением является "пробрасывание" имени профиля в Shell-скриипт запуска приложения.