В одной из прошлых статей мы реализовали тест для тестирования @RequestMapping аннотаций контроллера. Тест получился легковесный и всем нас устраивал. В процессе тестирования нашли небольшой баг. Он был починен буквально сразу, что меня порадовало. Кроме того я получил очень интересный совет от Rossen Stoyanchev - уделить внимание проекту spring-test-mvc.
Далее предлагаю мой модифицированный тест:
Контекст для теста:
Интересный инструмент в копилку разработчика.
Переписка с автором о pro & cons тут.
Дальше еще про SpringMVC (PropertyEditors vs ConversionServices), наконец таки будет SpringSecurity. Кроме того пишите о чем было бы интересно почитать вам!
Всего всего - ваши me & eugene.
2012-07-30
2012-07-26
Использование SpringData JPA или прощай GenericDao!
Доброго утра всем! Это для кого оно доброе конечно, а всем остальным просто привет.
На нашем воркшопе в Одессе было много общения и обмена опытом. Дмитрий задал интересный вопрос: "А почему вы до сих пор используете GenericDao в примере, это же Spring". Звучало примерно как: "Вы все еще кипятите? Тогда мы идем в вам!".
Ну а теперь за дело. Все мы привыкли начинать написание Persistence слоя с Dao, а более конкретно с объектов GenericDao. Например таких:
Набор методов может быть различным все зависит от нужд проекта и стиля/вкуса разработчиков. Затем следует имплементация для Hibernate/JPA/JDBC/и.т.п.:
Далее интерфейс для сущности и его имплементация:
Фух. Много букаф. Я думаю что все копировали эти артефакты из проекта в проект с небольшими изменениями. Но это день вчерашний, теперь же давайте поговорим о дне сегодняшнем. Для того, что использовать SpringData для построение JPA репозиториев мы берем для начала документацию, а затем читаем Oliver Gierke.
Ну что ж к делу. Все просто как раз (pom файл):
Два (интерфейс):
Три (контекст):
Кстати XML использовать совсем не обязательно, для любителей @Configuration в SpringData есть полная поддержка plain-Java подхода к конфигурации.
Дело за малым, а именно написать собственно сервис:
Далее пишем тест (код уже приводить не буду) и видим что мы полетели. Какие на мой взгляд за и против:
Код, как обычно вы можете найти на нашем GitHub
Дальше Spring Security, SpringData + NoSQL, @Cachable, Gradle.
Всего всего - ваши me & eugene
На нашем воркшопе в Одессе было много общения и обмена опытом. Дмитрий задал интересный вопрос: "А почему вы до сих пор используете GenericDao в примере, это же Spring". Звучало примерно как: "Вы все еще кипятите? Тогда мы идем в вам!".
Ну а теперь за дело. Все мы привыкли начинать написание Persistence слоя с Dao, а более конкретно с объектов GenericDao. Например таких:
Набор методов может быть различным все зависит от нужд проекта и стиля/вкуса разработчиков. Затем следует имплементация для Hibernate/JPA/JDBC/и.т.п.:
Далее интерфейс для сущности и его имплементация:
Фух. Много букаф. Я думаю что все копировали эти артефакты из проекта в проект с небольшими изменениями. Но это день вчерашний, теперь же давайте поговорим о дне сегодняшнем. Для того, что использовать SpringData для построение JPA репозиториев мы берем для начала документацию, а затем читаем Oliver Gierke.
Ну что ж к делу. Все просто как раз (pom файл):
Два (интерфейс):
Три (контекст):
Кстати XML использовать совсем не обязательно, для любителей @Configuration в SpringData есть полная поддержка plain-Java подхода к конфигурации.
Дело за малым, а именно написать собственно сервис:
Далее пишем тест (код уже приводить не буду) и видим что мы полетели. Какие на мой взгляд за и против:
- меньше писанины это хорошо
- возможность генерации методов по названию класса, а именно не надо упражнений с HQL или Criteria - это тоже хорошо
- дополнительные зависимости это не очень хорошо
- не очень привычный по сравнению с EMF/SF интерфейс это тоже не очень
- ну и магия, она всегда опасна
Код, как обычно вы можете найти на нашем GitHub
Дальше Spring Security, SpringData + NoSQL, @Cachable, Gradle.
Всего всего - ваши me & eugene
2012-07-25
Тестирование мапингов в SpringMVC
После нашего воркшопа в Одессе мы получили ряд интересных вопросов. Об одном из которых, а точнее о его решение я бы хотел рассказать.
Задача: протестировать @RequestMapping для контроллера
Документация говорит нам (а она как я люблю говорить у Спринга отличная):
"Prior to Spring 3.1, type and method-level request mappings were examined in two separate stages -- a controller was selected first by the DefaultAnnotationHandlerMapping and the actual method to invoke was narrowed down second by the AnnotationMethodHandlerAdapter.
With the new support classes in Spring 3.1, the RequestMappingHandlerMapping is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level @RequestMapping information. "
Правда нет конкретной рекомендации. Зато Oliver Girke на StackOverflow рекомендовал.
Итого имеем:
Поднятие контекста в тесте не требуется. Mockito в свою очередь порадовал вкусняшками типа @Mock. Я думаю что все заметили хак "request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, true);". Промучившись минут 15, а также немного подебажив (как говорит Женя дебажить Спринг одно удовольствие) я засабмитил багу и с чистой совестью поставил костыль.
В следующей серии о Spring Data и GenericDao.
Всего всего - ваши me & eugene
Задача: протестировать @RequestMapping для контроллера
Документация говорит нам (а она как я люблю говорить у Спринга отличная):
"Prior to Spring 3.1, type and method-level request mappings were examined in two separate stages -- a controller was selected first by the DefaultAnnotationHandlerMapping and the actual method to invoke was narrowed down second by the AnnotationMethodHandlerAdapter.
With the new support classes in Spring 3.1, the RequestMappingHandlerMapping is the only place where a decision is made about which method should process the request. Think of controller methods as a collection of unique endpoints with mappings for each method derived from type and method-level @RequestMapping information. "
Правда нет конкретной рекомендации. Зато Oliver Girke на StackOverflow рекомендовал.
Итого имеем:
Поднятие контекста в тесте не требуется. Mockito в свою очередь порадовал вкусняшками типа @Mock. Я думаю что все заметили хак "request.setAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING, true);". Промучившись минут 15, а также немного подебажив (как говорит Женя дебажить Спринг одно удовольствие) я засабмитил багу и с чистой совестью поставил костыль.
В следующей серии о Spring Data и GenericDao.
Всего всего - ваши me & eugene
2012-07-23
Еще немного о валидации
В предыдущей статье мы показали, каким образом можно организовать валидацию данных, поступающих на наши контроллеры. Сегодня я бы хотел рассказать про дополнительные возможности, которые предоставляет Spring. В часности, в этой стетье пойдет речь о JSR-303 (Валидация Бинов) и Hibernate Validation. Также я расскажу о новшествах, которые были введены в Spring MVC 3.1 для более удобной валидации данных.
Итак, построим наши примеры на предыдущей бизнес задаче: создание скидочных купонов. На вход наш контроллер получает данные о купонах, которые нужно сгенерить (DiscountCouponGenerationCommand), в качестве результата возвращает список сгенерированных купонов (CouponJson) либо список ошибок валидации.
Поля нашей команды помечены специальными аннотациями, которые называются Constraint. В примере я использовал несколько стандартных аннотаций @Min, @Digits. Также я использовал свои собвстенные Constraint аннотации: @CouponCode для валидации поля couponCode, @DiscountCoupon для валидации всего бина DiscountCouponGenerationCommand.
Как же работают Constraint аннотации? Давайте заглянем внутрь одной их них:
Прежде всего, обратите внимание на аннотацию @Constraint, которая указывает на то, что @CouponCode является Constraint аннотацией, также она содержит в себе имя валидатора, который будет использоваться для валидации поля, помеченного как @CouponCode. В нашем случае валидирующий класс - это CouponCodeValidator. Из других частей аннотации также следует обратить внимание на параметр message, который содержит сообщение-ошибку, которое будет передано на клиент, в случае если валидация поля прошла не успешно. Также аннотация @Target содержит список элементов Java класса, перед которыми мы сможем использовать @CouponCode.
Следует сказать, что текст ошибок валидации можно указывать явно в классе, либо использовать property файл, что особенно удобно, если необходима локализация сообщений. В этом случае вместо сообщения в параметре message нужно указать ключ окруженный фигурными скобками, а само сообщение поместить в файл ValidationMessages.properties, который обязательно должен находиться в корне classpath:
Рассмотрим теперь сам валидатор CouponCodeValidator.
Как видно из примера, валидатор имплементирует generic интерфейс. Первый generic параметр - это constraint который обрабатывает валидатор, второй - тип поля, на котором предположительно будет использоваться constraint. Итак, наш валидатор проверяет, что поле анотированное @CouponCode не пустое, имеет длину 10 символов и начинается с символов 'CP'.
Теперь перейдем собственно к конроллеру, который будет заниматься обработкой команды, валидацией и генерацией купонов.
Мы помечаем параметр DiscountCouponGenerationCommand аннотацией @Valid, которая и включит механизм валидации перед тем, как будет запущен метод generateDiscountCoupons. Всю работу за нас сделает Spring MVC:
В последних версиях Spring MVC ввели еще одну возможность обработки ошибок валидации - с помощью обработчиков исключений.
Метод аннотированный @ExceptionHandler аннотацией будет автоматически использоваться для обработки ошибок валидации, если таковые возникнут. Параметр MethodArgumentNotValidException содержит всю необходимую нам информацию об ошибках и месте где они возникли.
Конфигурация Spring контекста для JSR-303 не требует ничего особенного, она содержит 2 строки, которые инициализируют поиск компонент и Spring MVC:
В качестве имплементации JSR-303 я использовал Hibernate Validator, его необходимо поместить в classpath. Так как для сборки я использую Maven, я включил в pom.xml следующие зависимости:
Также хочу сказать, что весь код, использованный в примерах, находится на GitHub: https://github.com/hoaz/spring-validation-example. Вы можете склонировать себе этот репозиторий, собрать и поэксперементировать с примерами.
Итак, построим наши примеры на предыдущей бизнес задаче: создание скидочных купонов. На вход наш контроллер получает данные о купонах, которые нужно сгенерить (DiscountCouponGenerationCommand), в качестве результата возвращает список сгенерированных купонов (CouponJson) либо список ошибок валидации.
Поля нашей команды помечены специальными аннотациями, которые называются Constraint. В примере я использовал несколько стандартных аннотаций @Min, @Digits. Также я использовал свои собвстенные Constraint аннотации: @CouponCode для валидации поля couponCode, @DiscountCoupon для валидации всего бина DiscountCouponGenerationCommand.
Как же работают Constraint аннотации? Давайте заглянем внутрь одной их них:
Прежде всего, обратите внимание на аннотацию @Constraint, которая указывает на то, что @CouponCode является Constraint аннотацией, также она содержит в себе имя валидатора, который будет использоваться для валидации поля, помеченного как @CouponCode. В нашем случае валидирующий класс - это CouponCodeValidator. Из других частей аннотации также следует обратить внимание на параметр message, который содержит сообщение-ошибку, которое будет передано на клиент, в случае если валидация поля прошла не успешно. Также аннотация @Target содержит список элементов Java класса, перед которыми мы сможем использовать @CouponCode.
Следует сказать, что текст ошибок валидации можно указывать явно в классе, либо использовать property файл, что особенно удобно, если необходима локализация сообщений. В этом случае вместо сообщения в параметре message нужно указать ключ окруженный фигурными скобками, а само сообщение поместить в файл ValidationMessages.properties, который обязательно должен находиться в корне classpath:
Рассмотрим теперь сам валидатор CouponCodeValidator.
Как видно из примера, валидатор имплементирует generic интерфейс. Первый generic параметр - это constraint который обрабатывает валидатор, второй - тип поля, на котором предположительно будет использоваться constraint. Итак, наш валидатор проверяет, что поле анотированное @CouponCode не пустое, имеет длину 10 символов и начинается с символов 'CP'.
Теперь перейдем собственно к конроллеру, который будет заниматься обработкой команды, валидацией и генерацией купонов.
Мы помечаем параметр DiscountCouponGenerationCommand аннотацией @Valid, которая и включит механизм валидации перед тем, как будет запущен метод generateDiscountCoupons. Всю работу за нас сделает Spring MVC:
- вначале будет создана команда DiscountCouponGenerationCommand, которая заполнится значениями из POST запроса;
- далее команду просканируют на наличие constraint аннотаций, и для каждой из них запустится соответствующий валидатор;
- если ошибок не будет, то вызовется метод контроллера generateDiscountCoupons.
В последних версиях Spring MVC ввели еще одну возможность обработки ошибок валидации - с помощью обработчиков исключений.
Метод аннотированный @ExceptionHandler аннотацией будет автоматически использоваться для обработки ошибок валидации, если таковые возникнут. Параметр MethodArgumentNotValidException содержит всю необходимую нам информацию об ошибках и месте где они возникли.
Конфигурация Spring контекста для JSR-303 не требует ничего особенного, она содержит 2 строки, которые инициализируют поиск компонент и Spring MVC:
В качестве имплементации JSR-303 я использовал Hibernate Validator, его необходимо поместить в classpath. Так как для сборки я использую Maven, я включил в pom.xml следующие зависимости:
Также хочу сказать, что весь код, использованный в примерах, находится на GitHub: https://github.com/hoaz/spring-validation-example. Вы можете склонировать себе этот репозиторий, собрать и поэксперементировать с примерами.
2012-07-11
Валидация данных в Spring MVC
Сегодня мы публикуем статью Олега Артемова. Нашего старого боевого товарища и знатока Spring.
Добрый день всем !!
Данная статья является лишь первой моей статьей об использовании Spring, поэтому не стоит судить строго. :). На её написание меня натолкнуло постоянное количество одного и того же кода в нашем WEB - приложении, поэтому в один прекрасный момент я решил сделать что-нить более элегантное( благо у Spring возможностей хоть отбавляй).
Итак, начнем с преамбулы.
Как мы видим, это код в обычном Spring контроллере, который возвращает JSON. Обычно наш код выглядит именно так. Т.е мы видим, что приходит команда (набор параметров с UI), которые ввел пользователь и объект BindingResult (Spring сам его формирует и передает как параметр в метод) . Дальше проводится валидация команды, если она не прошла – возвращаем перечень ошибок, в противном случае выполняем какую-то логику и возвращаем статус ОК. Таких методов в разных контроллерах у нас довольно много и как любому программисту мне не хочется дублировать код. Поэтому я поставил перед собой 2 задачи:
- Как сделать, чтобы когда я ставлю @Valid на команде (а это стандартные возможности Spring), то вызывался нужный мне валидатор (который я зарегистрирую в контексте, а не стандартный JSR валидатор – у меня в валидаторах часто кастомная логика).
- Как сделать, чтобы в контроллер приходила команда только в том случае, если валидация полностью прошла. Этим я избавлюсь от ненужных инжектов валидаторов в контроллер и их постоянного вызова.
- Есть стандартная Spring – аннотация для метода InitBinder, которая позволяет регистрировать валидаторы для разных команд. Но тогда бы мне в каждом контроллере пришлось бы писать метод с этой аннотацией для каждой команды, которую обрабатывает контроллер (не очень хочется).
- Зарегистрировать глобальный валидатор, который централизованно будет управлять валидацией.
Как мы видим из кода, композитный валидатор ищет нужный валидатор для данной команды. Если не нашел, то используется стандартный JSR валидатор (для этого я использую hibernate-validators). Таким образом, теперь мой код сокращается и порядок действий выглядит так:
1. Ставлю аннотацию @Valid на команду.
2. Спринг вызовет композитный валидатор, а тот в свою очередь найдет нужный валидатор для данной команды и провалидирует её.
Итак, с первой задачей разобрались. Вторая поинтереснее. Для её решения был выбран Spring AOP. Напомню, что с помощью аспекта можно изменить поведение метода (более детально читайте главу по АОП в Спринг референс). Итак, решение такое:
Постараюсь объяснить. Аспект перехватывает все методы контроллеров (а есть разные виды аспектов – этот вид, лишь один из множества), которые помечены аннотацией ValidOnlyForJSON. Если команда была провалидирована успешно, то вызовется оригинальный метод контроллера, в противном случае – вернется перечень ошибок для клиента (то что мы делали в каждом методе).
Для имплементации был подключен AspectJ. Итак посмотрим, как теперь выглядит наш код:
По-моему, выглядит очень прилично. Таким образом, мы получили элегантное и готовое для переиспользования (а это самое главное) решение.
Оригинал статьи тут.
Обновляем материалы
2012-07-07
Гостевые статьи
Если Вы хотите:
- что-то рассказать о Spring,
- например поделиться тем, как Вы решали проблемы проекта с помощью Spring,
- либо рассказать о некоторых неочевидных,
- или просто удобных веща.
Мы с удовольствием примем гостевый статьи и напишем их у себя с указанием авторства.
Кроме того если у Вас есть интересные ссылки по тематике Spring - отправляйте их на наш твиттер @ua_spring.
Заранее спасибо, ваши me & eugene
- что-то рассказать о Spring,
- например поделиться тем, как Вы решали проблемы проекта с помощью Spring,
- либо рассказать о некоторых неочевидных,
- или просто удобных веща.
Мы с удовольствием примем гостевый статьи и напишем их у себя с указанием авторства.
Кроме того если у Вас есть интересные ссылки по тематике Spring - отправляйте их на наш твиттер @ua_spring.
Заранее спасибо, ваши me & eugene
2012-07-06
Будущие выступления
Мы хотели бы расширить географию проведения наших воркшопов. С удовльствием выступим и в Киеве и везде где интересуются Спрингом :) Пишите, будем рады помочь.
Отчет о JavaTechTalks №2, 29-30 июня Одесса
Первым нашим выступлением в рамках проекта, для нас киевлян, Одесса стала не случайно. Куда же ещё поехать летом как не к морю? Что собственно мы и сделали воспользовавшись возможностью, которую нам дала Логика в которой мы с Женей длительное время работали. Мы планировали выступить на встрече TechTalks в пятницу вечером с докладом о Spring MVC (слайды тут, виде ожидаем), а на следующий день провести воркшоп. Доклад на наш взгляд получился неплохой (судя по количеству вопросов).
На воркшоп в праздничную субботу дошли не все зарегистрировавшиеся, но как говорится остались только те, кто действительно хотел узнать больше про Spring. Рассказ первого модуля IoC занял у нас примерно столько сколько мы планировали, а вот Persistence и Spring MVC пошёл намного быстрее. В целом, на наш взгляд получилось очень интересно и продуктивно. Ждём отзывов от участников :)
Спасибо всем кто пришёл! Приходите ещё!
Спасибо организаторам - компании Логика, а также лично Юле Черняк.
Subscribe to:
Posts (Atom)