validation.failed.noemail.for.login=Ошибка, вы не указали ваш email. Вход в систему не возможен. validation.failed.nopass.for.login=Ошибка, вы не указали ваш пароль. Вход в систему не возможен.
« Jsp теги, новые и улучшенные | Системы управления версиями для программистов и не только. Часть 3 » |
Как в spring написать валидатор, использующий коды сообщений, и не забыть кого-то из них
Введение в суть вопроса
Я продолжаю выкладывать на всеобщее обозрение ряд своих утилиток помогающих и упрощающих разработку. Сегодня я расскажу об инструменте, который наверняка пригодится всем тем кто пользуется spring. Предположим что вы создаете некоторый контроллер, например, такой:
<bean name="loginController" class="blz.server.controllers.LoginController">
<property name="requireSession" value="true"/>
<property name="supportedMethods" value="GET,POST"/>
<property name="formView" value="loginStarted"/>
<property name="commandClass" value="blz.model.dto.LoginDTO"/>
<property name="commandName" value="login"/>
<property name="validator">
<bean class="blz.model.dto.validators.ValidateLoginDTO"/>
</property>
</bean>
Сам код класса валидатора может выглядеть примерно так:
public class ValidateLoginDTO implements Validator {
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(LoginDTO.class);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, LoginDTO.F_USEREMAIL, "validation.failed.noemail.for.login");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, LoginDTO.F_USERPASSWORD, "validation.failed.nopass.for.login");
}
}
Эти коды не берутся из ниоткуда: я должен создать файл properties, например, "/WEB-INF/localization/register_and_login.properties" и поместить в него следующий текст:
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basenames">
<list>
<value>/WEB-INF/localization/register_and_login</value>
</list>
</property>
<property name="defaultEncoding" value="windows-1251"/>
<property name="fallbackToSystemLocale" value="true"/>
</bean>
Надо сказать что подобный многошаговый процесс меня сильно раздражает т.к. способствует появлению ошибок в основном из-за неаккуратности или спешки (ну-ну, разве может быть иначе?). Несмотря на это изменить шаги, убрать, поменять местами нельзя, т.к. ничего лучшего не придумывается, а если даже и придумается то врядли этот самокат будет плавно входить в общую систему spring. Однако это не значит, что нельзя упростить для себя любимого процесс проверки наличия в файла свойств нужных для работы валидаторов кодов сообщений. Для этого я решил добавить поддержку специальной аннотации. Вот ее код:
/**
* Аннотация для маркировки тех классов, которые хотят использовать
* услуги проверки их на наличие корректных кодов сообщений
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ValidationBundle {
/**
* Базовое имя ResourceBundle
* @return
*/
String value();
/**
* Список локалей, для которых нужно выполнить проверку
* @return
*/
String[] locales () default {};
/**
* Кодировка файла с ресурсами, ну так получилось, что делать ...
* @return
*/
String encoding () default "ISO8859-1";
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD} )
/**
* Еще один маркер, но уже не для класса, а для конкретных его полей
*/
public static @interface Marker {}
}
@SuppressWarnings ({"inchecked", "unsafe"})
@ValidationBundle(value="/WEB-INF/localization/register_and_login", locales = {"RU_ru"}, encoding="windows-1251")
public class ValidateLoginDTO implements Validator {
@ValidationBundle.Marker
private static final String VALIDATION_FAILED_NOEMAIL_FOR_LOGIN = "validation.failed.noemail.for.login";
@ValidationBundle.Marker
private static final String VALIDATION_FAILED_NOPASS_FOR_LOGIN = "validation.failed.nopass.for.login";
public boolean supports(Class clazz) {
return clazz.isAssignableFrom(LoginDTO.class);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, LoginDTO.F_USEREMAIL, VALIDATION_FAILED_NOEMAIL_FOR_LOGIN);
ValidationUtils.rejectIfEmptyOrWhitespace(errors, LoginDTO.F_USERPASSWORD, VALIDATION_FAILED_NOPASS_FOR_LOGIN);
}
}
Все поля хранящие в себе значение кода сообщения я промаркировал аннотацией "@ValidationBundle.Marker".
Что дальше?
Теперь есть два варианта как запустить проверку spring-контекста и ваших классов на корректность. Во-первых, и это наиболее удобный для меня способ это создать ant-скрипт, частью которого является создание отчета об найденных ошибках. Для этого я создал ant-скрипт.
<?xml version="1.0"?>
<project default="makeanotest">
<target name="makeanotest">
<path id="library.spring.classpath">
<fileset dir="E:/Program_Files_2/Libraries/spring-framework-2.5/dist">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="library.velocity.classpath">
<fileset dir="E:/Program_Files_2/Libraries/spring-framework-2.5/lib/velocity">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="library.spring-all-libs.classpath">
<fileset dir="E:/Program_Files_2/Libraries/spring-framework-2.5/lib/">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="library.ant.classpath">
<fileset dir="E:/Program_Files_2/Libraries/ant/lib/">
<include name="**/*.jar"/>
</fileset>
</path>
<path id="library.commons.classpath">
<fileset dir="E:/Program_Files_2/Libraries/spring-framework-2.5/lib/jakarta-commons/">
<include name="**/*.jar"/>
</fileset>
</path>
<dirname property="base" file="${ant.file}"/>
<path id="project.sourcepath">
<dirset dir="${base}/src">
<include name="**/*.java"/>
<exclude name="com/**"/>
<exclude name="org/**"/>
<exclude name="javax/**"/>
</dirset>
</path>
<path id="project.classpath" location="${base}/out/production/sitegraph/"/>
<path id="all.classpath">
<path refid="library.spring.classpath"/>
<path refid="library.spring-all-libs.classpath"/>
<path refid="library.ant.classpath"/>
<path refid="project.classpath"/>
</path>
<!--
Создаю новую цель
-->
<taskdef name="scananno"
classname="blz.server.utils.anno.AntTaskAnnoScan"
classpathref="all.classpath"
/>
<property name="browser" location=
"C:/Program Files/Internet Explorer/iexplore.exe"/>
<property name="file-1" location="${base}/out/report-anno-1.html"/>
<property name="file-2" location="${base}/out/report-anno-2.html"/>
<!--
Вот пример использования новой задачи.
В качестве параметров я указываю будет ли завершаться скрипт ant аварийно, если
все нужные ресурсы не были найдены
далее я указываю путь к файлу, куда должен быть помещен результат в виде html-отчета
затем я указываю путь к исходному файлу spring
и наконец признак того как будут запрашиваться нужные ресурсы
в случае если признак useContext равен true то ресурсы будут загружаться из самого spring
-->
<scananno
failOnErrors="false"
targetFileName="${file-1}"
pathToSpringFile="${base}/web/WEB-INF/spring-tester.xml"
useContext="false"
pathToBaseResourcesDir="${base}/web/"
/>
<!--
Теперь, после создания отчета, нужно его показать в браузере
-->
<exec executable="${browser}" spawn="true">
<arg value="${file-1}"/>
</exec>
<!--
В этом случае к параметрам показанным в прошлом примере добавляются еще два:
признак того что загрузка должна вестись не из контекста а напрямую с помощью ResourceBundle
а также путь к корню каталога относительно которого будут искаться файлы properties
-->
<scananno
failOnErrors="true"
targetFileName="${file-2}"
pathToSpringFile="${base}/web/WEB-INF/spring-tester.xml"
useContext="false"
pathToBaseResourcesDir="${base}/web/"
/>
</target>
</project>
После запуска сценария я получу такой html-отчет:
Надо сказать, что внешний вид отчета можно гибко настраивать: в файлах с исходниками проекта вы найдете velocity-шаблон для выходного отчета (если вы не знаете что такое velocity, то скоро я опубликую серию статей посвященную ему).
Теперь про второй вариант использования. В этом случае в контекст spring добавляется новый bean, который зарегистрирован в случае получения события "Контекст обновлен" выполнить его сканирование и сформировать отчет.
<bean id="annoBean" class="blz.server.utils.anno.AnnoBean">
<constructor-arg index="0" value="report-anno.html" />
</bean>
В качестве единственного параметра к конструктору нужно передать путь к файлу, куда будет помещен сформированный html-отчет об найденных ошибках. В случае если путь не абсолютный, а относительный (не начинается с file:), то нужно каким-то образом сообщить об "базе" файла. Класс бина (AnnoBean) реализует интерфейс ServletContextAware. А значит что если все пошло хорошо то ему сообщат о местоположении корня веб-приложения и относительный путь будет отсчитываться от него.
Исходники
https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/java/annoforspringmessages
Хотя код утилиты заточен под работу с валидаторами, однако довольно легко можно ее перестроить и научить выполнять проверки наличия кодов сообщений для произвольный spring-бинов.
« Jsp теги, новые и улучшенные | Системы управления версиями для программистов и не только. Часть 3 » |