суббота, 11 января 2014 г.

Redisson - распределенные и масштабируемые структуры данных Java на основе сервера Redis

Опубликовал первый релиз проекта Redisson. Проект позволяет использвать знакомые всем структуры данных Java (Set, List, Map, Queue, AtomicLong, Lock, CountDownLatch), а также использовть функционал publish/subscribe в распределенном виде с поддержкой масштабирования. Все это достигается благодаря использованию весьма популярного сервера Redis, для которого Redisson является лишь клиентом.

Вам даже не придется знать команды Redis-а, да и возможности вызова команд Redis напрямую нет, т.к. цель проекта предоставить удобный api с использованием уже известных Java-интерфейсов. Для выполнения вызовов на сервере Redis используется пропаченный Redis-клиент - lettuce.

Представленный api по своей стуктуре в чем-то схож с Hazelcast api. Проект Hazelcast может хранить данные только в памяти, что не всегда удобно и влияет на надежность системы. Сервер Redis, в свою очередь, предоставляет три способа хранения данных:
   1. только в памяти
   2. переодическое сохранение на диск (snapshotting) - используется по-умолчанию
   3. лог транзакций, т.е. синхронная запись каждого изменения в лог (append-only file)
Таким образом, благодаря возможностям сервера Redis, мы получаем распределенные java структуры данных с возможностью хранения состояния не только в памяти.

Доступ к хранилищу Redis реализован практически для любого языка программирования, благодаря большому количеству реализованных клиентов. Соотвественно данные созданные в Redisson будут доступны для любого Redis-клиента.

Изначально проект задумывался как простая попытка реализовать Map на Java через Redis-команды. Однако дальнейшее изучение команд Redis, (в Redis есть поддержка транзакций, hashes, lists, sets) навело на мысль о реализации более сложных структур, таких как Lock и CountDownLatch.

В проекте более 70 unit-тестов, для запуска которых (по-умолчанию они отключены) нужно запустить локально Redis-сервер и запустить maven-сборку с параметром -DskipTest=false
Примеры использования:
Распределенный Map:
        Config config = new Config();
        config.setConnectionPoolSize(10);
        config.addAddress("some.redis-server.com:8291");
        Redisson redisson = Redisson.create(config);

        ConcurrentMap<String, SomeObject> map = redisson.getMap("anyMap");
        map.put("123", new SomeObject());
        map.putIfAbsent("323", new SomeObject());
        map.remove("123");

        ...

        redisson.shutdown();

Распределенный Lock:
        Redisson redisson = Redisson.create();

        Lock lock = redisson.getLock("anyLock");
        lock.lock();
        lock.unlock();

        // или

        redisson.getLock("anyLock").lock();

        ...

        redisson.getLock("anyLock").unlock();

        ...

        redisson.shutdown();

пятница, 25 мая 2012 г.

Проект Hibernate Dynamic SQL Cache

Выложил на github проект Hibernate Dynamic SQL Cache.

Описание

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

  • HQL-кэш, а также кэш коллекций entity-объектов будут сбрасываться при каждом обновлении данных в хотя бы в одной из таблиц, участвующих в запросе. При очень частых обновлениях таблиц польза от такого рода кэшей сводится к 0.
  • SQL-кэш держит в памяти лишь результат первого вызова запроса и не сбрасывается.

Поэтому возникла идея создания динамически обновляемого кэша. Результат запроса обновляется при INSERT или DELETE объекта в базе. Для использования данного решения следует немного изменить подход к построению запросов, а также использованию различных коллекций внутри entity-объектов.

  • Запрос должен возвращать только id объекта. По этому id вы можете легко загрузить объект из кэша. (Таким образом получается два обращения к кэшу, что всегда быстрее выполнения запросов к базе)
  • Запрос должен быть только по неизменяемым полям объекта.
  • Вместо использования коллекций в entity-объекте использовать соответствующий SQL-запрос c динамическим обновлением.

Cache provider может использоваться абсолютно любой (Infinispan, Hazelcast, EHCache ...)

Проект опубликован под Apache License 2.0

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

Для наглядности примера используется Spring, хотя сам проект не привязан к нему, можно использовать любой другой контейнер.

Конфигурация hibernate. Здесь важно отметить использование собственного hibernate.cache.query_cache_factory через который и осуществляется вся логика управления обновлениями кэшей:

Регистрируем com.corundumstudio.hibernate.dsc.QueryCacheEntityListener который будет отслеживать основные операции по всем entity.

Пример DAO-сервиса. com.corundumstudio.hibernate.dsc.CacheCallback - callback через который будет обслуживать операции "insert" или "delete" на объекте (в данном примере SimpleEntity), чтобы результаты запроса были всегда "свежими".

Теперь при вызове SimpleEntityDao.getEntityByPhone, первый раз будет произведено обращение к БД. Последующие вызовы метода будут возвращать значение из кэша, причем если объект SimpleEntity с искомым значением phone будет добавлен в базу он также появится и в результате данного запроса. И наоборот, если объект с таким значением phone будет удален, то результат запроса будет возвращать null

Если результат запроса возвращает список объектов, то в этом случае, при удалении/создании объекта, в списке будет удаляться/добавляться его id

пятница, 13 апреля 2012 г.

BoneCP - производительный пул соединений для БД

BoneCP - библиотека управления соединениями с БД. Использую ее уже более 2-х лет в разных проектах, в том числе и в системах с нагрузкой. Автор позиционирует ее как более производительную замену библиотекам c3p0 и dbcp. Представленные им бенчмарки это только подтверждают.

Среди полезных возможностей можно отметить:

  • логирование всех sql-операций

  • доступ к статистике через JMX

  • кэширование PreparedStatement-ов

четверг, 12 января 2012 г.

Поддержка socket.io протокола на Java

Опубликовал свой первый проект на github - netty-socketio, реализующий серверную поддержку socket.io протокола на java. В основе использован легендарный фреймворк Netty.

Socket.io - библиотека предназначенная для создания постоянной связи браузера с сервером. Таким образом возможно организовывать доставку данных в реальном времени в обе стороны. Socket.io поддерживает несколько транспортных механизмов для организации такого взаимодействия - web-sockets, xhr-polling и т.д.

В моей реализации поддерживается socket.io-client версии 0.8.7+. Из поддерживаемых транспортов пока только xhr-polling и websocket.

среда, 2 марта 2011 г.

Smooks: парсим xml с помощью ... xml

Smooks - отличный инструмент, который решает задачу парсинга xml используя xml-маппинг тэгов и аттрибутов на объекты и их свойства. Вообще продукт позиционируется как инструмент для трансформации, биндинга, валидации и обработки данных в различных форматах (CSV, XML, EDI). Я рассматриваю этот продукт как достойную альтернативу других механизмов парсинга xml - JAXB, JAXP, Digester и пр.

Предположим нам надо распарсить такой вот xml:

Xml необходимо распарсить в List из объектов типа PlayerRole:

Для начала создадим xml-маппинг необходимый для преобразования данных в объекты:

В Smooks все метаданные, необходимые для парсинга участков xml, представляются в виде бинов с сылками друг на друга. Адресация в xml осуществляется с помощью xpath-выражений. Если при парсинге необходима какая-то пост-обработка данных можно воспользоваться возможностью выполнения groovy-скриптов (тэг g:groovy), при этом код работы с xml будет более лаконичн чем на java. Из groovy также возможны обращение к бинам smooks.

Ну и наконец пример запуска механизма парсинга данных с приведенным конфигом:

Чтобы получить результат парсинга надо выбрать бин, обрабатывающий root-овый тэг, в нашем случае это бин с id="roles-bean".

Я выбраю этот движок для разбора xml т.к. он почти не требует кода и интегрирован с groovy. Использую его когда в проекте требуется распарсить различные конфигурационные ресурсы на этапе запуска сервера.

пятница, 14 января 2011 г.

Использование Javassist для генерации прокси в Spring Framework

Известно, что проект cglib давно находится в заброшенном состоянии, также при работе с ним возникают некоторые проблемы, описанные здесь. Наиболее популярная альтернатива этому проекту - javassist. Он используется в таких проектах как JBoss AS, Hibernate, Weld ...

Только вот в Spring он еще не появился. Замена библиотеки cglib, используемой для генерации proxy-объектов, на javassist, судя по плану проекта, состоится в версии 3.1 проекта. Вот jira-таск. В этом же таске я прикрепил аттачи в виде трех классов и пары патчей, которые позволяют окончательно перейти на Javassist.

Для включения поддержки Javassist вам понадобится пристроить к себе в проект вот эти три класса - JavassistAopProxy, JavassistAopProxyFactory и JavassistApplicationContext. Затем вместо spring-овой реализации ApplicationContext-а задействовать org.springframework.aop.framework.JavassistApplicationContext. Если вы не создаете его напрямую, а используете org.springframework.web.context.ContextLoaderListener сконфигурированный в web.xml, то вам необоходимо будет добавить в этот же web.xml такие строки:

суббота, 6 ноября 2010 г.

"Хорошая" новость от Oracle...

Хотя это событие было предсказуемо, после коммерциализации MySQL и OpenOffice, верить в него не хотелось. Похоже oracle хочет разделить версии JVM на "free" и "premium", подробнее тут.

среда, 3 ноября 2010 г.

Поиск по ключу входящему в числовой диапазон.

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

  Диапазон опыта     Уровень

0 < exp <= 12 1
12 < exp <= 30 2
30 < exp <= 67 3
... ...
231 < exp <= 300 10

Задача: реализовать алгоритм поиска уровня.

Для решения задачи я задействую класс java.util.TreeMap. Почему этот класс? Все просто, он реализует важный интерфейс - java.util.NavigableMap, появившийся с версии Java 1.6. Его метод

Map.Entry<K,V> ceilingEntry(K key)

как раз позволяет найти запись в map-е удовлетворяющую условию K(n) < key <= K(n+1) и при этом будет содержать значение по ключу K(n+1). Таким образом, остается составить map-у из записей: ключ - макс. значение опыта на уровне, значение - уровень. Код решения:

Для наглядности я захардкодил map-у в коде, на деле же она создается из xml.

пятница, 29 октября 2010 г.

Обновление максимального значения без использования блокировок

При реализации грамотных многопоточных механизмов, порой используют только синхронизованные блоки кода. Но, не следует также забывать и о функции CAS (Compare and swap, представленной соответствующей процессорной командой). Данная функция присутствует во всех классах пакета java.util.concurrent.atomic.

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

До недавнего времени примение CAS-у в своей практике я не находил. Однако, когда потребовалось реализовать многопоточный алгоритм обновления максимального значения, он оказался весьма уместен:

воскресенье, 8 августа 2010 г.

Реализация concurrent HashSet-а в Java

К сожалению, в JDK нет полноценной реализации concurrent HashSet-а, как в случае для HashMap - java.util.concurrent.ConcurrentHashMap. Есть CopyOnWriteArraySet, использующий внутри себя CopyOnWriteArrayList, но у него есть некоторые недостатки:

  • поиск элементов будет медленне ибо в List-ах он выполняется по equals, а не по hashcode -> equals.
  • невозможность удаления объектов при итерации (iterator.remove() - не реализован) и понятно почему, ведь при итерации используется копия коллекции.

Но выход есть, подобие ConcurrentHashSet можно сделать на основе ConcurrentHashMap, записывая как ключ элемент, а в значение какой-нибудь dummy-объект. Такая реализация представлена в виде org.jboss.util.collection.ConcurrentSet из бибилиотеки jboss-common-core.

вторник, 6 апреля 2010 г.

Транзакционные синхронизации в Spring Framework

Выполнение определенной логики по окончанию текущей транзакции в Spring Framework можно "подключать" динамически с помощью так называемых транзакционных синхронизаций.

Транзакционная синхронизация - это по сути callback-объект типа org.springframework.transaction.support.TransactionSynchronization, который содержит в себе определенные методы вызываемые из AbstractPlatformTransactionManager на соответствующие действия над транзакцией (afterCompletion, afterCommit, afterRollback, suspend и т.д.). org.springframework.transaction.support.TransactionSynchronizationManager - менеджер со статическими методами, используемый для регистрации и управления синхронизациями. При написании своей синхронизации в качестве базового класса удобно использовать org.springframework.transaction.support.TransactionSynchronizationAdapter. Вот пример:

суббота, 6 марта 2010 г.

Сериализация полей без set-методов и поддержка аннотаций в BlazeDS

Для передачи данных между BlazeDS 3.2 и клиентом рекомендуется использовать value object-ы. Эти объекты содержат только те данные, которые действительно необходимы клиенту.

К сожалению, в BlazeDS есть некоторые неудобства при сериализации объектов:

  • Обязательное наличие set-метода (можно и пустого), если вы хотите сериализовать поле у которого есть только get-метод

  • Сериализация enum-ов как объектов, вместо использования строчного значения, как заявлено в документации

Так же не очень удобно, на мой взгляд, является создание отдельных классов для каждого value object-a. Было бы удобнее помечать аннтоциями getter-ы или поля, которые будут учавствовать/отсутсвовать в value object-е, без создания нового класса.

Чтобы устранить вышеперечисленные недостатки придется разобраться с механизмом сериализации BlazeDS. Кратко опишу классы, которые задействованы при сериализации: flex.messaging.io.PropertyProxy - основной интерфейс, используемый для сериализации/десериализации объектов. Каждому классу объекта, передаваемому на клиента, соответствует определенная реализация PropertyProxy, в основном это flex.messaging.io.BeanProxy. Соответствие (Класс объекта - PropertyProxy-объект) держится в классе flex.messaging.io.PropertyProxyRegistry, при сериализации/десериализации BlazeDS получает PropertyProxy для какого-либо объекта используя методы: getProxy и getProxyAndRegister.

Т.к. PropertyProxyRegistry является singleton-ом, то замену реализации нам придется сделать через смену значения поля registry, которое спрятано внутри класса. Реализация, в которой убраны перечисленные выше недостатки, выглядит так:

вторник, 29 декабря 2009 г.

Запуск "Наемники : Карты судьбы"

Не так давно наступил день, который я так долго ждал. 25 декабря был запущен проект для ВКонтакте - игра "Наемники : Карты судьбы", над которым я трудился последние 2,5 месяца. Серверная часть проекта была реализована полностью мной, что называется "своими руками". Давненько мечтал о таком маленьком проекте, в котором я смогу применить все свои знания и опыт в области разработки.

Использовались следующие java-технологии:

  • Spring
  • Spring security
  • Spring BlazeDS Integration
  • BlazeDS
  • Hibernate
  • JBoss Cache
  • Unitils
  • Jmockit
  • DBUnit
  • TestNG

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

Теперь, когда проект запущен, я могу снова начинать писать статьи, освещая некоторые проблемы с которыми я столкнулся при разработке.

Отмечу также и тот факт, что более 80% кода было покрыто тестами. Хотя этот процесс и занимал у меня до 50% времени, я считаю, что именно благодаря тестам удалось выйти в релиз с минимальным кол-вом багов на серверной стороне. Без тестов был бы невозможен безболезненный рефакторинг и быстрое добавление новой функциональности.

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

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

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

Только не забудьте указать в конструкторе контекста аргументу 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:

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

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

Стоит отметить, что параметру 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 всех версий проекта, вот пример его содержимого:

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

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

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

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

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

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

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

Параметры 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 в командной строке.

суббота, 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

четверг, 2 апреля 2009 г.

Переключение из HTTPS-соединения в HTTP (в Tomcat)

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

К примеру, если сделать на HTTPS-соединении только логин пользователя, а после его входа в систему снова использовать обычный HTTP, то на web-сервере Tomcat придется реализовать поддержку корректного переключения с HTTPS на HTTP.

Конфигурация Spring Security для такого случая:

<security:http auto-config="true" >
<security:intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY"
requires-channel="https"/>
<security:intercept-url pattern="/billing.html" access="ROLE_USER"
requires-channel="http"/>
</security:http>

Возможны два варианта переключения соединений:


1.    2.

В первом случае все будет работать, т.к. Tomcat создаст cookie сперва для HTTP сессии, которая будет действительна и для HTTPS, а следовательно может быть аутентифицирована Spring Security и использована дальше в работе с обычным HTTP.

Во втором случае Tomcat создаст cookie для HTTPS с параметром secure, а это значит что аутентификация такой сессии не позволит использовать в дальнейшем обычную HTTP сессию. Сookie с параметром secure привязана лишь к HTTPS-сессиям. Для пользователя же это обернется редиректом Spring Security на страницу логина, но с уже созданной cookie, и только второй логин уже пустит пользователя в систему. Чтобы решить эту проблему необходимо, находясь в HTTPS сессии, создать cookie для обычной HTTP сессии без параметра secure. С помощью фильтра это реализовать довольно просто:

package com.blogspot.nkoksharov.filter;

import java.io.IOException;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.lang.StringUtils;

public class HttpsCookieFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
final HttpServletRequest httpRequest = (HttpServletRequest) request;
final HttpServletResponse httpResponse = (HttpServletResponse) response;
final HttpSession session = httpRequest.getSession(false);

if (request.isSecure() && session != null) {
final Cookie sessionCookie = new Cookie("JSESSIONID", session.getId());
sessionCookie.setMaxAge(-1);
sessionCookie.setSecure(false);
String contextPath = httpRequest.getContextPath();
if (StringUtils.isNotBlank(contextPath)) {
sessionCookie.setPath(contextPath);
} else {
sessionCookie.setPath("/");
}
httpResponse.addCookie(sessionCookie);
}

chain.doFilter(request, response);
}

...

}

Теперь переход с https-соединения будет выглядеть так:

Фильтр лучше всего поместить в самом начале цепочки фильтров, важно чтобы он был перед springSecurityFilterChain. Следует отметить, что при таком решении http-cookie будет приходить пользователю на каждый https-запрос. Т.к. сложно опеределить при https-соединении создана ли cookie для http-канала.