Книга 1 - Начальные сведения
Книга 2 - Более профессиональный подход |
Студенческий отдел кадров Полный пример бизнес-уровня на Spring
Итак, мы увидели возможности Spring на примере одной функциональности -
Profession. Теперь мы сделаем следующее: для каждой из наших
таблиц создадим подобную функциональность, которая будет включать в себя полный набор классов
и интерфейсов:
При чтении кода я вам рекомендую обратить внимание на то, что класса Main, который мы использовали для "тестирования" работоспособности нашего приложения, уже нет. Вместо него я написал специальный класс для тестирования SpringStudentFacadeTest. Этот класс использует еще одну функциональность Spring - тестирование. Пакет предоставляет несколько очень удобных инструментов, которые мы еще рассмотрим. А пока давайте посмотрим на код этого класса. package students.test; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import junit.framework.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.annotation.Rollback; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; import students.facade.ApplicantFacade; import students.facade.ProfessionFacade; import students.facade.SubjectFacade; import students.view.ApplicantResultView; import students.view.ApplicantView; import students.view.ProfessionView; import students.view.SubjectView; @ContextConfiguration(locations = {"/StudentExample.xml", "/StudentDatabase.xml"}) @TransactionConfiguration(transactionManager = "txManager") public class StudentFacadeTest extends AbstractTransactionalJUnit4SpringContextTests { @Autowired private SubjectFacade subjectFacade; @Autowired private ProfessionFacade professionFacade; @Autowired private ApplicantFacade applicantFacade; @Test @Rollback(false) public void subjectTest() { SubjectView sv = new SubjectView(); // Установим данные для предмета sv.setSubjectName("Mathematic"); // Добавим Long idSubj = subjectFacade.addSubject(sv); // Перечитаем sv = subjectFacade.getSubject(idSubj); // Убедимся, что считывание совпадает с тем, что записывали Assert.assertTrue(sv.getSubjectName().equals("Mathematic")); // Изменим название предмета, запишем и снова убедимся, что все в порядке sv.setSubjectName("Mathematics"); subjectFacade.updateSubject(sv); sv = subjectFacade.getSubject(idSubj); Assert.assertTrue(sv.getSubjectName().equals("Mathematics")); // Убедимся, что всего предметов пока один Assert.assertTrue(subjectFacade.findSubject().size() == 1); sv.setSubjectName("Physics"); idSubj = subjectFacade.addSubject(sv); sv = subjectFacade.getSubject(idSubj); Assert.assertTrue(sv.getSubjectName().equals("Physics")); Assert.assertTrue(subjectFacade.findSubject().size() == 2); sv.setSubjectName("Chemist"); idSubj = subjectFacade.addSubject(sv); sv = subjectFacade.getSubject(idSubj); Assert.assertTrue(sv.getSubjectName().equals("Chemist")); Assert.assertTrue(subjectFacade.findSubject().size() == 3); sv.setSubjectName("Chemist2"); idSubj = subjectFacade.addSubject(sv); sv = subjectFacade.getSubject(idSubj); Assert.assertTrue(sv.getSubjectName().equals("Chemist2")); Assert.assertTrue(subjectFacade.findSubject().size() == 4); // Удалим предмет и убедимся, что общее количество уменьшилось subjectFacade.deleteSubject(sv); Assert.assertTrue(subjectFacade.findSubject().size() == 3); sv.setSubjectName("Literature"); idSubj = subjectFacade.addSubject(sv); sv = subjectFacade.getSubject(idSubj); Assert.assertTrue(sv.getSubjectName().equals("Literature")); Assert.assertTrue(subjectFacade.findSubject().size() == 4); // Проверим, что работает поиск по списку ID List<SubjectView> list = subjectFacade.findSubject(); List<Long> check = new LinkedList<Long>(); for (SubjectView s : list) { check.add(s.getSubjectId()); } Assert.assertTrue(subjectFacade.findSubjectById(check).size() == 4); } @Test @Rollback(false) public void professionTest() { ProfessionView pv = new ProfessionView(); // Добавим новую специальность pv.setProfessionName("Chemists"); Long idProf = professionFacade.addProfession(pv); pv = professionFacade.getProfession(idProf); Assert.assertTrue(pv.getProfessionName().equals("Chemists")); // Исправим значение и убедимся. что так и сделано pv.setProfessionName("Chemist"); professionFacade.updateProfession(pv); pv = professionFacade.getProfession(idProf); Assert.assertTrue(pv.getProfessionName().equals("Chemist")); // Всего специальностей одна штука Assert.assertTrue(professionFacade.findProfession().size() == 1); // Создадим список предметов для специальности List<SubjectView> svList = subjectFacade.findSubject(); List<Long> check = new LinkedList<Long>(); for (SubjectView sv : svList) { if (sv.getSubjectName().equals("Chemist") || sv.getSubjectName().equals("Physics")) { check.add(sv.getSubjectId()); } } professionFacade.updateSubjectList(idProf, check); Assert.assertTrue(subjectFacade.findSubjectByProfession(idProf).size() == 2); pv.setProfessionName("Mathematician"); idProf = professionFacade.addProfession(pv); pv = professionFacade.getProfession(idProf); Assert.assertTrue(pv.getProfessionName().equals("Mathematician")); Assert.assertTrue(professionFacade.findProfession().size() == 2); svList = subjectFacade.findSubject(); check = new LinkedList<Long>(); for (SubjectView sv : svList) { if (sv.getSubjectName().equals("Mathematics") || sv.getSubjectName().equals("Physics") || sv.getSubjectName().equals("Literature")) { check.add(sv.getSubjectId()); } } professionFacade.updateSubjectList(idProf, check); Assert.assertTrue(subjectFacade.findSubjectByProfession(idProf).size() == 3); pv.setProfessionName("Removed"); pv.setSubjectList(new HashSet(svList)); Long idProf2 = professionFacade.addProfession(pv); pv = professionFacade.getProfession(idProf2); Assert.assertTrue(pv.getProfessionName().equals("Removed")); Assert.assertTrue(professionFacade.findProfession().size() == 3); professionFacade.deleteProfession(pv); Assert.assertTrue(professionFacade.findProfession().size() == 2); } @Test @Rollback(false) public void applicantTest() { // Получаем список специальностей List<ProfessionView> pList = professionFacade.findProfession(); Assert.assertTrue(professionFacade.findProfession().size() == 2); ProfessionView pr1 = professionFacade.getProfession(pList.get(0).getProfessionId()); ProfessionView pr2 = professionFacade.getProfession(pList.get(1).getProfessionId()); Long applicantId = 0L; // Заполняем данные для абитуриента ApplicantView av = new ApplicantView(); av.setLastName("Стрельцов1"); av.setFirstName("Павел"); av.setMiddleName("Сергеевич"); av.setEntranceYear(2009); av.setProfessionId(pr1.getProfessionId()); // Записываем applicantId = applicantFacade.addApplicant(av); // Считываем av = applicantFacade.getApplicant(applicantId); // Проверяем, что оценок у только что введенного абитуриента нет Assert.assertTrue(av.getApplicantResultList().size() == 0); // Добавляем оценки абитуриенту av.setApplicantResultList(createMark(pr1, applicantId, 1)); applicantFacade.updateApplicantResult(av); // Перечитываем и убеждаемся, что оценки теперь есть av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == pr1.getSubjectList().size()); // Поробуем поменять фамилию у абитуриента av.setLastName("Стрельцов"); applicantFacade.updateApplicant(av); av = applicantFacade.getApplicant(applicantId); // Убеждаемся, что изменения произошли Assert.assertTrue(av.getLastName().equals("Стрельцов")); // Перечитываем и убеждаемся, что оценки остались Assert.assertTrue(av.getApplicantResultList().size() == pr1.getSubjectList().size()); av.setLastName("Иванов"); av.setFirstName("Андрей"); av.setMiddleName("Васильевич"); av.setEntranceYear(2009); av.setProfessionId(pr1.getProfessionId()); applicantId = applicantFacade.addApplicant(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == 0); av.setApplicantResultList(createMark(pr1, applicantId, 2)); applicantFacade.updateApplicantResult(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == pr1.getSubjectList().size()); av.setLastName("Смирнов"); av.setFirstName("Сергей"); av.setMiddleName("Петрович"); av.setEntranceYear(2009); av.setProfessionId(pr2.getProfessionId()); applicantId = applicantFacade.addApplicant(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == 0); av.setApplicantResultList(createMark(pr2, applicantId, 3)); applicantFacade.updateApplicantResult(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == pr2.getSubjectList().size()); av.setLastName("Затейников"); av.setFirstName("Виктор"); av.setMiddleName("Капитонович"); av.setEntranceYear(2009); av.setProfessionId(pr2.getProfessionId()); applicantId = applicantFacade.addApplicant(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == 0); av.setApplicantResultList(createMark(pr2, applicantId, 4)); applicantFacade.updateApplicantResult(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == pr2.getSubjectList().size()); av.setLastName("Федоров"); av.setFirstName("Алексей"); av.setMiddleName("Дмитриевич"); av.setEntranceYear(2009); av.setProfessionId(pr2.getProfessionId()); applicantId = applicantFacade.addApplicant(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == 0); av.setApplicantResultList(createMark(pr2, applicantId, 5)); applicantFacade.updateApplicantResult(av); av = applicantFacade.getApplicant(applicantId); Assert.assertTrue(av.getApplicantResultList().size() == pr2.getSubjectList().size()); Assert.assertTrue(applicantFacade.findApplicant().size() == 5); } @Test @Rollback(false) public void applicantDeleteTest() { List<ApplicantView> avList = applicantFacade.findApplicant(); Assert.assertTrue(avList.size() == 5); applicantFacade.deleteApplicant(avList.get(0)); Assert.assertTrue(applicantFacade.findApplicant().size() == 4); } // Вспомогательная процедура для установки оценок private List<ApplicantResultView> createMark(ProfessionView pv, Long applicantId, Integer mark) { List<ApplicantResultView> arvList = new LinkedList<ApplicantResultView>(); for (SubjectView sv : pv.getSubjectList()) { ApplicantResultView ar = new ApplicantResultView(); ar.setApplicantId(applicantId); ar.setSubjectId(sv.getSubjectId()); ar.setMark(mark); arvList.add(ar); } return arvList; } }
Как видите мы не делаем что-то особенное - просто пытаемся вызывать методы, которые мы написали
и убеждаемся в том, что они работают. Думаю. что профессионалы могут упрекнуть меня в таком
не очень разумном варианте кодирования - создание данных можно было бы сделать и покомпактнее,
да и делать проверки на количество записей в базе данных тоже не самое лучшее решение.
Мне просто хотелось показать, что можно проверять и как. Надеюсь на вашу сниходительность.
А теперь коротко рассмотрим функциональность фасадов. Только предварительно я хочу сделать небольшое отступление. При написании приложения возникает некоторая сложность при работе со списком и единичным экземпляром. Суть ее в том, что информация, которая требуется для списка, может быть более экономичной, чем для одного экземпляра. Когда же мы смотрим например одну специальность, то хорошо сразу иметь и список предметов для этой специальности. Для списка специальностей эта информация будет скорее всего излишней. Можно сделать отдельный класс View для списка и для одного экземпляра. Здесь я предлагаю иной вариант - класс один, но его заполнение может проиходить двумя способами - полное и частичное. Дополнительный логический аргумент в конструкторе View позволяет выбрать режим заполнения. ProfessionFacade.java
SubjectFacade.java
ApplicantFacade.java
ApplicantResultFacade.java - разберите его сами. Названия достаточно очевидны. Я сознательно не занимался проверкой этого класса в нашем тесте - попробуйте придумать что-то сами. Кроме этого можно придумать еще несколько функций для анализа данных. Например:
Остальной код вы можете рассмотреть самостоятельно. Для запуска теста откройте его в редакторе и нажмите Shift+F6 или выберите пункт меню Run->Run File. Библиотеки
Для текущего проекта нам понадобятся следующие библиотеки: Переходим на уровень WebКак только мы начинаем заниматься Web-программированием, у нас возникает потребность удобно делать несколько вещей, главными из которых на мой взгляд являются:
Для решения данной задачи был разработан шаблон MVC - Model-View-Controller
(Модель, Представление, Контроллер). Их функции можно описать так: Таким образом, схема работы этих трех компонентов может быть описана следующим образом: Controller получает запрос и по определенным настройкам (правилам) с учетом полученных параметров определяет, что именно надо делать для получения данных (Model). После работы с данными Model может быть передана View и с помощью этого View данные будут отображаться на экране.
Очень часто контроллером может быть какой-то сервлет, который
по определенной конфигурации делает что-либо. Model - здесь не могу однозначно что-то
сказать. Это может быть самый обычный класс с нужными полями. А в качестве View выступает
чаще всего JSP-страница. Spring MVCКак я уже упоминал, в качестве контроллера часто выступает сервлет. Spring следует этому правилу и для начала мы рассмотрим файл web.xml - вместилище сервлетов. Кстати сам web.xml можно также рассматривать в качестве несложного контроллера. Он ведь занимается вызовами разных сервлетов по определенным маскам URL. Итак, вот наш web.xml <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/classes/StudentDatabase.xml, /WEB-INF/classes/StudentExample.xml, /WEB-INF/classes/StudentController.xml </param-value> </context-param> <servlet> <servlet-name>context</servlet-name> <servlet-class> org.springframework.web.context.ContextLoaderServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet> <servlet-name>applicantServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value/> </init-param> <load-on-startup>2</load-on-startup> </servlet> <servlet-mapping> <servlet-name>applicantServlet</servlet-name> <url-pattern>*.std</url-pattern> </servlet-mapping> <session-config> <session-timeout>30</session-timeout> </session-config> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <resource-ref> <description>DB Connection</description> <res-ref-name>studentDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> </web-app> Давайте внимательно и подробно рассмотрим все детали - здесь вважно будет практически все.
ВАЖНО: Не забудьте скопировать файл mysql-connector-java-3.1.13-bin.jar в каталог /lib корневого каталога Tomcat (я говорю о Tomcat 6). Для Tomcat 5 каталог /common/lib Если в двух словах: Tomcat предоставляет возможность воспользоваться реализацией интерфейса javax.sql.DataSource, которая является пулом коннектов к базе данных. параметры для коннекта находятся в файле META_INF/context.xml <?xml version="1.0" encoding="UTF-8"?> <Context path="/Spring_05"> <Resource name="studentDS" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" maxIdle="2" maxWait="5000" validationQuery="SELECT 1" url="jdbc:mysql://127.0.0.1:3306/db_applicant?characterEncoding=UTF-8" maxActive="4"/> </Context>
Надеюсь, что больших вопросов содержание данного файла у вас не вызовет. Если что -
читайте 9-ю часть. Там все описано более подробно. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean name="studentDS" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/studentDS"/> <property name="resourceRef" value="true"/> </bean> <bean name="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="studentDS" /> <property name="annotatedClasses"> <list> <value>students.entity.Profession</value> <value>students.entity.Subject</value> <value>students.entity.Applicant</value> <value>students.entity.ApplicantResult</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect hibernate.show_sql=true </value> </property> </bean> <bean name="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean name="abstractTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager" ref="txManager" /> <property name="transactionAttributes"> <props> <prop key="find*">PROPAGATION_REQUIRED, readOnly </prop> <prop key="get*">PROPAGATION_REQUIRED, readOnly </prop> <prop key="add*">PROPAGATION_REQUIRED,-Exception </prop> <prop key="update*">PROPAGATION_REQUIRED,-Exception </prop> <prop key="delete*">PROPAGATION_REQUIRED,-Exception </prop> </props> </property> </bean> <bean name="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans> Как видите, теперь мы обращаемся к ресурсу по имени и этот ресурс предоставляет нам Web-контейнер Tomcat. Т.е. теперь параметры коннекта зарегистрированы у Tomcat и каждый, кто захочет, может им пользоваться. Опять разделение труда и облегчение работы. Что приятно. Разобравшись с DataSource и вопросом загрузки контента Spring давайте рассмотрим что и как делает Spring после этого для реализации шаблона MVC. Но прежде чем рассматривать xml-файлы и конкретные классы, давайте более подробно остановимся на принципах организации всго механищма MVC в Spring. Я воспользовался картинкой из документации Spring.
Как видим, все начинается с прихода запроса в DispatcherServlet (Front Controller). В нем
определяется какой именно класс (который реализует интерфейс
org.springframework.web.servlet.mvc.Controller) будет
использоваться для обработки конкретного запроса. Именно в этом классе мы будем организовывать
логику получения данных для отображения. Если быть более точным, то
DispatcherServlet использует объект/класс, который реализует
интерфейс HandlerMapping. Вобщем-то никто не мешает
использовать уже готовые классы от Spring - SimpleUrlHandlerMapping
или BeanNameUrlHandlerMapping. После того, как сделаны нужные
изменения и данные готовы, контроллер решает, какое именно представление (View) будет использовано
для отображения.
Имя этого View передается так называемому ViewResolver'у (точный перевод сделать сложно, но наверно
лучшим будет что-то вроде "определитель/выбиратель" View). Если быть более точным - ViewResolver'ов
может быть несколько. Они организуются в последовательность (причем порядком вы можете управлять)
и каждый пытается определить, какой View скрывается под указанным именем. Когда View определен
(в большинстве случаев это какая-то JSP-страница) ему передаются данные и уже сформированная
HTML-страница (а может WML) отправляется в броузер. А теперь давайте рассмотрим готовый пример, в котором мы сделаем три несложные страницы для показа списка предметов, списка специальностей и списка абитуриентов. Сначала посмотрим на файл конйигурации StudentController.xml <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass"> <value>org.springframework.web.servlet.view.JstlView</value> </property> <property name="order" value="2"/> <property name="prefix"> <value>/</value> </property> <property name="suffix"> <value>.jsp</value> </property> </bean> <bean id="professionController" class="students.web.controller.ProfessionController"> <property name="professionFacade" ref="professionFacade" /> </bean> <bean id="subjectController" class="students.web.controller.SubjectController"> <property name="subjectFacade" ref="subjectFacade" /> </bean> <bean id="applicantController" class="students.web.controller.ApplicantController"> <property name="applicantFacade" ref="applicantFacade" /> </bean> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/profession.std">professionController</prop> <prop key="/subject.std">subjectController</prop> <prop key="/applicant.std">applicantController</prop> </props> </property> </bean> </beans> Начнем рассматривать этот файл снизу вверх. Как видите, для определения имени контроллера, который будет обрабатывать апросы, мы выбрали SimpleUrlHandlerMapping. Думаю, что вы уже догадались, как происходит выбор контроллера - это обычное совпадение по маске. В маске можно использовать * для обобщения нескольких страниц. Далее идут три наших контроллера - для показа наших трех тсраниц. И самое интересное - это "выбиратель" страниц - в данном случае мы воспользовались классом InternalResourceViewResolver. Принцип его работы следующий: Сначала подставляется часть из prefix, потом к ней подставляется имя View, которое нам передаст контроллер (мы чуть ниже это увидим) и в конце подставляется часть из suffix. Т.е. если в качестве имени контроллер передаст строку subject/subject, то итогом будет страница JSP /subject/subject.jsp, которой и будет передано управление. А теперь самое время посмотреть на код одного из контроллеров - они у нас достаточно похожи и поэтому мы рассмотрим только один - ProfessionController. package students.web.controller; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.AbstractController; import students.facade.ProfessionFacade; import students.view.ProfessionView; public class ProfessionController extends AbstractController { private ProfessionFacade professionFacade; public void setProfessionFacade(ProfessionFacade professionFacade) { this.professionFacade = professionFacade; } @Override protected ModelAndView handleRequestInternal(HttpServletRequest arg0, HttpServletResponse arg1) throws Exception { List<ProfessionView> l = professionFacade.findProfession(); Map<String,List<ProfessionView>> data = new HashMap<String,List<ProfessionView>>(); data.put("professionList", l); return new ModelAndView("students/profession", data); } } Как видите, в нем нет ничего сложного. Мы унаследовали наш контроллер от класса AbstractController и переопределили метод handleRequestInternal, который в качестве параметров имеет то же, что и обычный сервлет. Обратите внимание на два момента:
Еще один момент - это страница JSP которая будет отображать данные. <%@page contentType="text/html" pageEncoding="UTF-8"%> <%@taglib uri="/WEB-INF/tld/c.tld" prefix="c" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Profession List</title> </head> <body> <table border="1"> <c:forEach var="profession" items="${professionList}"> <tr> <td>${profession.professionId}</td> <td>${profession.professionName}</td> </tr> </c:forEach> </table> </body> </html>
Обратите внимание на часть
<c:forEach var="profession" items="${professionList}">
Если вы посмотрите снова на код нашего контроллера, то увидите, что наши данные мы поместили под именем professionList. И именно по этому имени обращаемся к данным.
Теперь вы можете проверить наше приложение подставляя разные URL: Исходный код для всех классов вы можете найти в проекте Spring_05. Тестирование без TomcatВ конце мне бы хотелось обратить ваше внимание на еще два класса, которые у нас появились в разделе Test Packages - а именно StudentControllerTest и StudentSuit package students.test; import javax.naming.NamingException; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.mock.jndi.SimpleNamingContextBuilder; /** * * @author ASaburov */ @RunWith(Suite.class) @Suite.SuiteClasses({ students.test.StudentFacadeTest.class, students.test.StudentControllerTest.class }) public class StudentSuit { @BeforeClass public static void setUpClass() throws Exception { try { SimpleNamingContextBuilder builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder(); DriverManagerDataSource ds = new DriverManagerDataSource("jdbc:mysql://localhost:3306/db_applicant", "root", "root"); ds.setDriverClassName("com.mysql.jdbc.Driver"); builder.bind("java:comp/env/studentDS", ds); } catch (IllegalStateException ex) { ex.printStackTrace(); Assert.fail(); } catch (NamingException ex) { ex.printStackTrace(); Assert.fail(); } } } Spring предоставляет немало интересных вохможностей по тестированию. Одна из них - возможность создания объектов, к котороым можно обратиться через JNDI - мы ведь используем данный способ. Чтобы не переделывать конфигурацию можно использовать нужные классы. Мы создаем эмулятор JNDI и помещаем туда DriverManagerDataSource, который связан с тем же именем, что и при использовании Tomcat. Также следует обратить внимание, каким образом создается целый набор классов, котоый мы запускаем для тестирование - я имею в виду аннотацию @Suite.SuiteClasses. И давайте посмотрим на класс StudentControllerTest. В нем самое главное - это использование так называемых mock-объектов (я бы перевел это как подставных/тренировочных). Как вы уже видели в метод контроллера мы должны передать объекты, которые реализуют интерфейс HttpServletRequest и HttpServletResponse. Но это интерфейсы, а нам нухны реальные объекты. И Spring предоставляет нам такой набор - их возможности достаточно большие - я настоятельно советую вам посмотреть документацию на них. ВНИМАНИЕ !!! Перед запуском тестов база данных должна быть пустой. Вы можете это сделать запустить скрипт создания базы - Часть 15 - Новая структура данных. Такой вариант не является удачным, но в данном случае мне хотелось сразу наполнить базу данными. Ну и заодно увидеть, что не так именно в таком тесте. Вы можете сделать тесты более удобными и правильными. Архив с исходными кодами: Исходный код |