Skip to content

Сборка проекта с разделением по профилям

Sayapin Alexander edited this page Jan 23, 2013 · 12 revisions

Профили maven

При сборке 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

Resource filtering

Кроме сборки по профилям хотелось бы влиять на ресурсы в приложении. Например, в зависимоти от провиля 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

В 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();
}

Далее опишем различия для каждого из профилей и специфические настройки.

Devel профиль

В профиле для разработчика необходимо реализовать класс для обычных запросов по Http.

HttpPlainRequester.java

package learn.sprofile;

public class HttpPlainRequester implements HttpRequester {
	public String request() {
		return HttpPlainRequester.class.getName();
	}
}

Testing профиль

В профиле для тестирования реализует 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>

Production профиль

В производственном профиле будет класс для запросов с использованием шифрования.

HttpCryptoRequester.java

package learn.sprofile;

public class HttpCryptoRequester implements HttpRequester {
	public String request() {
		return HttpCryptoRequester.class.getName();
	}
}

Классы для Mysql и postgresql профилей

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();
	}
}

Задание профилей в XML-файле контекста Spring

Создадим 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 и выполнения для Spring, поэтому объединить профили автоматически не получится. Более менее модули стыкуются для фазы тестирования, потому как Maven устанаваливает системные переменные.

Возможным решением является "пробрасывание" имени профиля в Shell-скриипт запуска приложения.