Итак, я не буду разливаться что такое спринг, для чего он нужен, а сразу перейду к делу. Рассмотренное ниже приложение очень простое и показывает только основные возможности и фичи. Приложение предоставляет набор CRUD операций над некой сущностью.
web.xml
<context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext.xml /WEB-INF/databaseConfig.xml /WEB-INF/dispatcher-servlet.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
Как мы видим, ничего не поменялось. Ничего страшного, стабильность - залог успеха.
Модель данных
package my.example.entity; import javax.persistence.*; import java.util.Date; @Entity @Table(name = "USERS") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; @Column(name = "name", nullable = false) private String name; @Column(name = "email", nullable = false) private String email; @Column(name = "birth", nullable = false) private Date birth; // geteers & setters
Тут я думаю все понятно.
Интерфейс
public interface UserDAO { public void add(User user); public void update(User user); public void delete(User user); public User getById(Long id); public List<User> getAll(); }
Реализация
package my.example.dao.impl; import my.example.dao.UserDAO; import my.example.entity.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Repository public class UserDAOImpl implements UserDAO { private HibernateTemplate template; @Autowired public void setTemplate(HibernateTemplate template) { this.template = template; } @Transactional public void add(User user) { template.save(user); template.flush(); } @Transactional public void update(User user) { template.saveOrUpdate(user); template.flush(); } @Transactional public void delete(User user) { template.delete(user); template.flush(); } @Transactional public User getById(Long id) { return (User) template.get(User.class, id); } @Transactional public List<User> getAll() { return template.find("from User"); } }
Тут появилось уже несколько новых аннтотаций.
1) @Repository - насколько я понял, аннотация необходима для определения типа исключения которое может случиться при работе с данными.
2) @Autowired - через данную аннотацию указывается бин, который необходимо проинжектить в текущий класс.
3) @Transactional - аннтотация необходима менеджеру транзакций, чтобы работать с данным методом. Конфигурация будет ниже.
Класс сервиса, делегирующего дао-методы, будет использоваться в контроллере.
package my.example.service; import my.example.dao.UserDAO; import my.example.entity.User; import org.springframework.beans.factory.annotation.Autowired; import java.util.List; public class UserService { private UserDAO userDAO; @Autowired public void setUserDAO(UserDAO userDAO) { this.userDAO = userDAO; } public void add(User user) { userDAO.add(user); } public void update(User user) { userDAO.update(user); } public void delete(User user) { userDAO.delete(user); } public User getById(Long id) { return userDAO.getById(id); } public List<User> getAll() { return userDAO.getAll(); } }
application-context.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> <bean name="userDAO" class="my.example.dao.impl.UserDAOImpl"> <property name="template" ref="hibernateTemplate"/> </bean> <bean name="userService" class="my.example.service.UserService"> <property name="userDAO" ref="userDAO"/> </bean> </beans>
Конфигурация базы и дао.
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/spring3demo"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <bean name="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" id="sessionFac"> <property name="dataSource" ref="dataSource"/> <property name="annotatedClasses"> <list> <value>my.example.entity.User</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> <tx:annotation-driven transaction-manager="txManager"/> <bean name="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="sessionFactory"/> </bean> </beans>
Ну как? Нравится? По мне так круто, нет монстроурозного abstractTransactionProxy :)
Ну теперь наверно перейдем к самому вкусному - контроллеру.
dispatcher-servlet.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:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <context:component-scan base-package="my.example.controller"/> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <mvc:annotation-driven/> <bean name="userValidator" class="my.example.validator.UserValidator"/> </beans>
Строчка <context:component-scan base-package="my.example.controller"/> указывает какой пэкадж сканить на нличие контроллеров.
Помимо этого в конфиге остался только резолвер и бин валидатора. Никаких бинов контроллеров, урлмаппингов и всего прочего. Это особенно порадовало.
Ну и класс контроллера
package my.example.controller; import my.example.entity.User; import my.example.service.UserService; import my.example.validator.UserValidator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.propertyeditors.CustomDateEditor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.Validator; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.ModelAndView; import java.text.SimpleDateFormat; import java.util.Date; @Controller public class UserController { @Autowired private UserService userService; @Autowired private UserValidator userValidator; @RequestMapping("/") public String foo() { return "redirect:user"; } @RequestMapping(value = "/user", method = RequestMethod.GET) public ModelAndView get() { ModelAndView mav = new ModelAndView(); mav.setViewName("users"); mav.addObject("users", userService.getAll()); mav.addObject("user", new User()); return mav; } @RequestMapping(method = RequestMethod.POST) public String add(@ModelAttribute("user") User user, BindingResult result) { userValidator.validate(user, result); if (result.hasErrors()) return "/users"; userService.update(user); return "redirect:/user"; } @RequestMapping(value = "/user/{id}/{action}", method = RequestMethod.GET) public String edit(@PathVariable String id, @PathVariable String action, Model model) { User user = userService.getById(Long.parseLong(id)); if (action.equals("delete")) { userService.delete(user); return "redirect:/user"; } if (action.equals("edit")) model.addAttribute("user", user); return "/users"; } @InitBinder public void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); } }
Итак, разберемся по пунктам:
1)Аннтотация @Controller необходима, чтобы спринг понял, что это контроллер.
2) Тут мы снова видим аннтотацию @Autowired, которая инфжектит бины в наш контроллер, однако тут я обошелся даже без сеттер-методов.
3)Метод get() откликается на урл /user. Как мы видим тут создается простой объект ModelAndView, в который помещается объект формы, если мы хотим работать с формой, коллекция уже имеющихся объектов (прощай referenceData...) и указывается имя вьюхи, на которую следует перейти.
4)Следующий метод add() предназначен для обработки сабмитов. Тут происходит создание нового объекта и редактирование имеющегося. Обратите внимание что метод возвращает строку. В первом случае это имя view, во втором это редирект на урл "/user"
5)Ну и метод еdit, в котором нам отдается редактируемый объект и происходит удаление объекта. Очень интересная строка
@RequestMapping(value = "/user/{id}/{action}", method = RequestMethod.GET)
Тут грубо говоря описан паттерн, если улр будет матчиться, то запрос будет обработан данным методом, очень круто на мой взягляд. Следующая строка тоже довольно интересна
public String edit(@PathVariable String id, @PathVariable String action, Model model)
Тут прямо в скобках у метода указывается аннотация, которая говорит, что значение из урла надо использовать как переменную. Таким образом я из урла выцепляю id и действие, что надо сделать над объектом. Костыли конечно, но мне нравится работать с сущностью в одном контроллере, а не в нескольких.
6) Ну и метод initBinder, в котором регистрируется кастомный обработчик даты. Кто будет запускать пример, дату вбивать в формате указанном у объекта SimpleDateFormat.
Ну и сама view
<%@ page contentType="text/html; charset=UTF-8" language="java" %> <%@ page session="false" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <table> <c:forEach items="${users}" var="u"> <tr> <td><c:out value="${u.name}"/></td> <td><c:out value="${u.email}"/></td> <td><c:out value="${u.birth}"/></td> <td><a href='<c:url value="user/${u.id}/edit"/>'>Edit</a></td> <td><a href='<c:url value="user/${u.id}/delete"/>'>Delete</a></td> </tr> </c:forEach> </table> <form:form modelAttribute="user"> <form:hidden path="id"/> <table> <tr> <td>Name</td> <td><form:input path="name"/><form:errors path="name" /></td> </tr> <tr> <td>Email</td> <td><form:input path="email"/><form:errors path="email" /></td> </tr> <tr> <td>Birth</td> <td><form:input path="birth"/><form:errors path="birth" /></td> </tr> <tr> <td></td> <td><input type="submit"></td> </tr> </table> </form:form>
Ну вот собственно и все. Возможно не очень красиво пока все, но это пока, дальше будет лучше, я уверен :)
PS. Старался разобраться с jsr 303 - bean validation и ее интеграцией со спрингом, но что-то пока не вышло. Кто выкурит тему и выложит, тому респект :)
P.P.S. Ссылка на проект (IDEA) http://narod.ru/disk/18077159000/Spring3Demo.rar.html