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.Markerprivate static final String VALIDATION_FAILED_NOEMAIL_FOR_LOGIN = "validation.failed.noemail.for.login";
@ValidationBundle.Markerprivate 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--><scanannofailOnErrors="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--><scanannofailOnErrors="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 » |