среда, 28 октября 2009 г.

Динамическая модификация контекста в Spring

Существует механизм изменения контекста Spring, который запускается после сбора всей информации о нем (из xml и аннотаций) и срабатывает до создания самих bean-ов. Я говорю об интерфейсе org.springframework.beans.factory.config.BeanFactoryPostProcessor, реализуя который вы получаете именно такую возможность. Используя данный механизм очень удобно менять параметры уже объявленных где-то в контексте bean-ов. Объект реализующий этот интерфейс можно зарегистрировать как обычный bean в контексте, либо добавить его на этапе создания контекста, например так:

package com.blogspot.nkoksharov;

import org.springframework.beans.*;
import org.springframework.beans.factory.config.*;

public class SpringTest {

public void testPostProcessor() {
ConfigurableApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"/spring/context.xml"}, false);
context.addBeanFactoryPostProcessor(new BeanFactoryPostProcessor() {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
BeanDefinition definition = beanFactory.getBeanDefinition("sampleBean");
MutablePropertyValues values = definition.getPropertyValues();
MockObject object = new MockObject();
values.addPropertyValue("someProperty", object);
}
});
context.refresh();

...

}

}

Только не забудьте указать в конструкторе контекста аргументу refresh значение false, иначе наш механизм будет вызываться лишь для новых bean-ов добавляемых уж после создания контекста (как это делать я писал тут). При вызове метода refresh произойдет инициализация контекста и наш обработчик будет вызван.

Описанный выше прием оказывается очень полезен в использовании при тестировании, когда нужно изменить какие-то из параметров bean-ов или просто выставить свои значения.

понедельник, 17 августа 2009 г.

Управление версиями в Maven

После очередного релиза проекта приходится менять его версию, проходясь при этом по всем pom-файлам. Процедура эта довольно утомительная и избавить от нее поможет versions-maven-plugin.

Достаточно выставить в головном pom-файле новую версию проекта и затем выполнить команду:

mvn versions:update-child-modules

Как вы наверное догадались, команда выполнит обновление всех версий проекта в pom-файлах дочерних модулей. Если же, по какой-то причине, вы остались недовольны проделанной работой плагина, то содержимое pom-файлов можно легко вернуть в исходное состояние командой:

mvn versions:revert

Чтобы удалить backup-ы pom-файлов выполните:

mvn versions:commit

Помимо выполнения обновления версии проекта, есть еще несколько полезных возможностей. Вывод списка новых версий зависимостей проекта, доступных из репозитария:

mvn versions:display-dependency-updates

Список новых версий плагинов используемых в проекте:

mvn versions:display-plugin-updates

суббота, 8 августа 2009 г.

Параметры производительности nginx и tapestry 5

Думаю, что вам известно о HTTP-сервере nginx, а также возможности его использования в качестве front-end сервера для передачи разных ресурсов клиенту. Использование nginx позволяет разгрузить ваш сервер Java-приложений от лишней работы со стататическими ресурсами.

Отмечу лишь несколько основных, на мой взгляд параметров, влияющих на производительность системы использующей Nginx и веб-фреймворка Tapestry 5.

Параметры для nginx, выставляемые в nginx.conf:

  • worker_processes - кол-во процессов, обеспечивающих загрузку ресурсов с диска. Значение зависит от объема ресурсов и скорости носителя с которого производится чтение. Также играет роль число ядер/процессоров в вашем сервере, думаю, что значение должно быть не меньше чем их кол-во;

  • worker_connections - максимальное кол-во одновременных соединений с сервером. Следует выбрать оптимальное число, которое сможет держать сам сервер-приложений. Именно верный порог соединений предотвратит DDOS-атаки на ваш сервер и даст уверенность в том, что ваш сервер будет устойчиво работать.

Настройки производительности у Tapestry 5 сводятся к верной настройке пула страниц. Напомню, что при каждом обращении клиента фреймворк вытаскивает из пула экземпляр определенной страницы. Параметры:

  • tapestry.page-pool.hard-limit - максимальное значение страниц в пуле. Следует учесть, количество локалей поддерживаемых вашим приложением, т.к. для каждой локали создается отдельный экземпляр страницы (по-умолчанию значение 20);

  • tapestry.page-pool.soft-limit - кол-во страниц в пуле, после которого tapestry перейдет в ожидание освободившихся страниц. Пока число страниц в пуле не достигнет этого значения и свободных экземпляров не будет, они будут создаваться (по-умолчанию значение 5).

понедельник, 3 августа 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 2

Теперь о том, что касается изменений которые вносит hibernate в структуру БД, при добавлении/изменении сущностей, если в настройках указан параметр hibernate.hbm2ddl.auto=update. Как продолжить пользоваться этой возможностью, если теперь для всех изменений структуры БД мы должны использовать только liquibase? Было бы не плохо получать sql генерируемый hibernate, который можно легко поместить в changeset liquibase. Для этого необходимо подключить hibernate3-maven-plugin к модулю, в котором используется hibernate:

<properties>
<hibernate.update>false</hibernate.update>
<hibernate.create>false</hibernate.create>
</properties>
<build>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>hibernate3-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<componentProperties>
<outputfilename>schema.sql</outputfilename>
<configurationfile>/resources/hibernate.cfg.xml</configurationfile>
<export>false</export>
<update>${hibernate.update}</update>
<create>${hibernate.create}</create>
<drop>true</drop>
</componentProperties>
</configuration>
<dependencies>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>8.3-603.jdbc4</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

Необоходимым условием для работы плагина является наличие файла hibernate.hbm.xml, в нем должны содержаться следующие настройки:

<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">...</property>
<property name="hibernate.connection.url">...</property>
<property name="hibernate.connection.username">...</property>
<property name="hibernate.connection.password">...</property>
<property name="hibernate.dialect">...</property>

<mapping class="ru.somesite.SomeObject"/>
...
</session-factory>
</hibernate-configuration>

Не забудьте также и о том, что теперь все маппинги классов тоже нужно указывать в этом файле, а не в каком-нибудь SessionFactoryBean, если используете Spring. Настройка SessionFactoryBean будет выглядеть примерно так:

<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="classpath:/hibernate.cfg.xml" />
<property name="hibernateProperties">
<props>
...
<prop key="hibernate.hbm2ddl.auto">validate</prop>
</props>
</property>
</bean>

Стоит отметить, что параметру hibernate.hbm2ddl.auto, в вашем проекте, нужно присвоить значение validate. Теперь при каждом выполнении команды:

mvn hibernate3:hbm2ddl -Dhibernate.create=true

вам будет выводится sql генерируемый hibernate, который можно вставить в changeset. Параметр -Dhibernate.create=true следует указывать, в том случае, когда вам требуется SQL всей схемы БД. В случае, когда требуется получить SQL-команды, выполняемые hibernate для обновления схемы БД, то вместо первого параметра используйте -Dhibernate.update=true.

И помните, что в случае обновления БД вам нужно будет следить SQL, генерируемым hibernate, т.к. не все изменения параметров объектов входят в SQL (к примеру, hibernate не будет создавать некоторые constraint-ы).

четверг, 9 июля 2009 г.

Использование LiquiBase с Hibernate и Maven. Часть 1

Когда-то я представлял вашему вниманию инструмент для управления изменениями в БД - liquibase. Теперь я решил описать как его можно использовать в проектах с maven-сборкой, и hibernate.

Для начала необходимо создать отдельный maven-артефакт, который будет содержать только ресурсы - xml-файлы с changeSet-ами от liquibase. Структура файлов в папке resources нашего артефакта следующая:

    /changelogs/версия_проекта/*-changelog.xml
/changelogs/версия_проекта/release-changelog.xml
/project-changelogs.xml

Файл project-changesets.xml включает в себя набор файлов release-changelog.xml всех версий проекта, вот пример его содержимого:

<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.9
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.9.xsd">

<include file="changelogs/1.0/release-changelog.xml" relativeToChangelogFile="true"/>
<include file="changelogs/1.1/release-changelog.xml" relativeToChangelogFile="true"/>
<include file="changelogs/2.0/release-changelog.xml" relativeToChangelogFile="true"/>
...
</databaseChangeLog>

Таким же образом файлы release-changelog.xml включают в себя список файлов с измененями, находящихся в той же папке и касающихся лишь данной версии проекта. Сами изменения я предпочитаю описывать не пользуясь при это тэгами liquibase, а использую привычный SQL.

Благодаря наличию в liquibase такого понятия как "контекст", можно разделить все операции над БД на три контекста:

  • schemedata - контекст в котором будут отражены изменения структруры базы;

  • initdata - контекст определяющий данные для инициализации БД, например добавление новых пользовательских ролей;

  • migratedata - контекст используемый при изменении "живых" данных, т.е данных появляющихся в системе при ее эксплуатации.

Такая концепция позволит вам четко разделять виды изменений в БД. А также "накатывать" изменения лишь нужных контекстов. Пример:

<changeSet id="2" author="nkoksharov" context="initdata">
<sql>
insert into roles values (123, 'ROLE_USER');
</sql>
<rollback>
<sql>
delete from roles where id = 123;
</sql>
</rollback>
</changeSet>

Если в проекте предполагается использовать две базы - БД разработчика/продакшн и БД для тестов, то pom-файл, созданного нами артефакта, будет содержать следующие строки:

<project>
<properties>
<liquibase.goal>update</liquibase.goal>
</properties>
<profiles>
<profile>
<id>devdb</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<database.url>${app.database.url}</database.url>
<database.user>${app.database.user}</database.user>
<database.password>${app.database.password}</database.password>
<database.contexts>${app.database.contexts}</database.contexts>
</properties>
</profile>
<profile>
<id>testdb</id>
<properties>
<database.url>${test.database.url}</database.url>
<database.user>${test.database.user}</database.user>
<database.password>${test.database.password}</database.password>
<database.contexts>${test.database.contexts}</database.contexts>
</properties>
</profile>
</profiles>

<build>
<plugins>
<plugin>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-plugin</artifactId>
<version>1.9.3.0</version>
<configuration>
<changeLogFile>src/main/resources/project-changelogs.xml</changeLogFile>
<driver>org.postgresql.Driver</driver>

<url>${database.url}</url>
<username>${database.user}</username>
<password>${database.password}</password>
<contexts>${database.contexts}</contexts>
</configuration>
<executions>
<execution>
<id>appdb</id>
<phase>process-resources</phase>
<configuration>
<contexts>${app.database.contexts}</contexts>
<url>${app.database.url}</url>
<username>${app.database.user}</username>
<password>${app.database.password}</password>
</configuration>
<goals>
<goal>${liquibase.goal}</goal>
</goals>
</execution>
<execution>
<id>testdb</id>
<phase>process-resources</phase>
<configuration>
<contexts>${test.database.contexts}</contexts>
<url>${test.database.url}</url>
<username>${test.database.user}</username>
<password>${test.database.password}</password>
</configuration>
<goals>
<goal>${liquibase.goal}</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>

Параметры app.database.xxx и test.database.xxx можно определить в головном pom-файле проекта. Теперь накатывать изменения на обе БД можно одной командой:

mvn process-resources либо mvn clean install

Чтобы сделать rollback обеих баз на несколько changeset-ов назад к команде следует добавить параметры:

-Dliquibase.goal=rollback -Dliquibase.rollbackCount=1

liquibase.rollbackCount - определят кол-во changeset-ов которые следует откатить.

Порой может возникнуть ситуация, когда требуется внести изменение в существующий changeset и заново накатить все changeset-и. Здесь поможет параметр:

-Dliquibase.dropFirst=true

Если потребуется работать с каждой БД отдельно, то необходимо использовать уже команды самого плагина liquibase:

mvn liquibase:update либо mvn liquibase:rollback

Для тестовой БД добавляем параметр -Ptestdb в командной строке.

Ускорение выполнения команд maven

На днях мне рассказали про maven-cli-plugin и я решил поспешить рассказать вам об этом окрытии. Думаю, у каждого, кто работал с maven, возникало желание хоть как-то ускорить выполнение его команд. Теперь такая возможность появилась в виде реализации выше названного плагина.

Плангин, после запуска, предоставляет разработчику командную строку, в которой вы можете запускать различные фазы жизненного цикла - clean, install, test ... (в режиме execute-phase), либо другие плагины (в режиме execute). Из-за того, что плагин, поднимает в себе инфраструктуру maven-а, то время выполнения команд, на некоторых модулях, может уменьшится в 2 раза! Первое выполнение команды может ни чем не отличаться по времени, но последующие будут быстрыми.

Я пока использую лишь execute-phase. Причем очень удобной оказалась возможность собирать несколько модулей с одной фазой, перечисляя их через пробел:

            module1 module2 clean install

Чтобы подключитьданный плагин, в pom-файле нужно указать сам плагин:

  <plugins>
<plugin>
<groupId>org.twdata.maven</groupId>
<artifactId>maven-cli-plugin</artifactId>
</plugin>
</plugins>

И репозитарий для него:

  <pluginRepositories>
<pluginRepository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.org/maven2</url>
</pluginRepository>
</pluginRepositories>

Запуск выполняется командой, на уровне корневого pom-файла:

     mvn cli:execute-phase

Кстати, если вы меняете pom-файлы, то плагин придется перезапустить.

Последнее, что хотелось бы отметить - это возможность задания алиасов в коммандной строке, они указываются следующим образом:

  <plugins>
<plugin>
<groupId>org.twdata.maven</groupId>
<artifactId>maven-cli-plugin</artifactId>
<configuration>
<userAliases>
<module-build>module clean install -Dpmd.skip=true -Dcheckstyle.skip=true</module-build>
<web-build>web-module package -Pdevelopment</web-build>
<rebuild>module-build web-build</rebuild>
</userAliases>

</configuration>
</plugin>
</plugins>

суббота, 2 мая 2009 г.

Javascript AOP

Наверное каждому известно что такое АОП или по-просту аспектно-ориентированное программирование. В java есть целый ряд фреймворков реализующих определенные возможности данного подхода.

Но если смотреть в сторону javascript-языка, то возможности АОП в нем реализовать довольно просто:

var AOPUtils = {
addBefore: function(object, methodName, invokedObject, invokedMethodName) {
var oldMethod = object[methodName];
object[methodName] = function() {
var args = $A(arguments);
try {
if (Object.isFunction(invokedObject)) {
invokedObject.call(invokedObject, args);
} else {
invokedObject[invokedMethodName].call(invokedObject, args);
}
} catch (e) {
alert("An exception occurred in method '" + methodName + "' Error: " + e.message);
}
try {
var result = oldMethod.apply(object, args);
} catch (e) {
alert("An exception occurred in method '" + methodName + "' Error: " + e.message);
}
return result;
};
}
};

Представленная функция AOPUtils.addBefore позволяет вам выполнить свою функцию перед функцией какого-либо объекта. Для реализации использовались стандартные javascript-функции, смысл которых состоит в следующем: apply - вызов функции с параметрами в виде массива, call - вызов функции с перечислением параметров через запятую. Первым аргументом обеих функций будет объект который в вызываемой функции будет представлять ссылку на this.

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

var SomeClass = Class.create({
simplemethod: function(url) {
alert(url);
}
});

var sobject = new SomeClass();
AOPUtils.addBefore(sobject, "simplemethod", function(args) {
var url = args[0];
url += "?param=test";
args[0] = url;
});

sobject.simplemethod("http://test.com");

Отмечу, что для использования данного механизма необоходма библиотека Prototype