欢迎来到本教程的第四部分。 在这一部分中,我们将使用Spring MVC编写控制器和视图,并考虑我们的REST模型。

我们必须做的第一件事,就是根据目前的情况制作一个Web应用程序。 我们将web / WEB-INF文件夹添加到我们的项目根目录。 在WEB-INF内创建jsp文件夹。 我们将把JSP放在那个地方。 在该文件夹内,我们将放置具有以下内容的部署描述符web.xml文件:

<?xml version='1.0' encoding='UTF-8'?>
<web-app xmlns='http://java.sun.com/xml/ns/javaee'xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'xsi:schemaLocation='http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd'version='3.0'><display-name>timesheet-app</display-name><context-param><param-name>contextConfigLocation</param-name><param-value>classpath:persistence-beans.xml</param-value></context-param><listener><listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener><servlet><servlet-name>timesheet</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><load-on-startup>1</load-on-startup></servlet><servlet-mapping><servlet-name>timesheet</servlet-name><url-pattern>/</url-pattern></servlet-mapping></web-app>

注意,我们正在使用称为时间表的servlet。 这是一个调度程序servlet。 下图说明了Spring的调度程序servlet的工作方式(在下面的图片中称为Front控制器):

  1. 请求由调度程序Servlet处理
  2. 分派器servlet决定将请求传递到哪个控制器(通过请求映射,我们将在后面看到),然后将请求委托
  3. 控制器创建模型并将其传递回调度程序Servlet
  4. 分派器Servlet解析视图的逻辑名称,在此绑定模型并呈现视图

最后一步很神秘。 调度程序Servlet如何解析视图的逻辑名称? 它使用称为ViewResolver的东西。 但是我们不会手工创建自己的,而是只创建另一个配置文件,使用ViewResolver定义一个bean,并由Spring注入它。 在WEB-INF中创建另一个Spring配置文件。 按照约定,它必须命名为timesheet-servlet.xml ,因为我们将DispatcherServlet命名为“ timesheet”,并且这是文件名,Spring将在其中默认情况下查找config。 还创建包org.timesheet.web 。 这是我们将放置控制器的地方(它们也只是带注释的POJO)。

这是时间表-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:context='http://www.springframework.org/schema/context'xsi:schemaLocation='http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd'><context:component-scan base-package='org.timesheet.web' /><beanclass='org.springframework.web.servlet.view.InternalResourceViewResolver'><property name='prefix' value='/WEB-INF/jsp/' /><property name='suffix' value='.jsp' /></bean>
</beans>

我们定义了前缀后缀来解析逻辑名。 真的很简单。 我们这样计算名称:
全名=前缀+逻辑名+后缀
因此,使用我们的InternalResourceViewResolver,逻辑名称“ index”将解析为“ /WEB-INF/jsp/index.jsp”。

对于视图,我们将结合使用JSP技术和JSTL(标记库),因此我们需要向pom.xml文件中添加另一个依赖项:

<dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version></dependency>

现在,我们想在/ timesheet-app / welcome上处理GET。 因此,我们需要编写视图和控制器(对于Model,我们将使用Spring工具中的一个)。 让我们从控制器开始:

package org.timesheet.web;import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;import java.util.Date;@Controller
@RequestMapping('/welcome')
public class WelcomeController {@RequestMapping(method = RequestMethod.GET)public String showMenu(Model model) {model.addAttribute('today', new Date());return 'index';}}

因此,当有人访问url欢迎(在我们的示例中为http:// localhost:8080 / timesheet-app / welcome )时,此控制器将处理请求。 我们还使用Model并在那里绑定名为“ today”的值。 这就是我们如何获得查看页面的价值。

请注意,我的应用程序的根目录是/ timesheet-app。 这称为应用程序上下文 。 您当然可以更改它,但是假设您是应用程序上下文,则所有其余代码都按这样设置。 如果要部署WAR,它将基于WAR的名称。

从showMenu方法中,我们返回“索引”,它将被解析为WEB-INF / jsp / index.jsp,因此让我们创建一个这样的页面并放置一些基本内容:

<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<%@ taglib prefix='fmt' uri='http://java.sun.com/jsp/jstl/fmt' %>
<%@ taglib prefix='spring' uri='http://www.springframework.org/tags' %><html>
<head><title>Welcome to Timesheet app!</title>
</head>
<body><h1>Welcome to the Timesheet App!</h1><ul><li><a href='managers'>List managers</a></li><li><a href='employees'>List employees</a></li><li><a href='tasks'>List tasks</a></li><li><a href='timesheets'>List timesheets</a></li></ul><h2>Also check out <a href='timesheet-service'>extra services!</a></h2>Today is: <fmt:formatDate value='${today}' pattern='dd-MM-yyyy' />
</body>
</html>

回顾一下,我们添加了web.xml文件,timesheet-servlet.xml Spring配置文件,控制器类和jsp页面。 让我们尝试在某些Web容器上运行它。 我将使用Tomcat7,但是如果您对其他Web容器甚至应用程序服务器更满意,请随时进行切换。 现在,有很多方法可以运行Tomcat以及部署应用程序。 您可以:

  • 结合使用嵌入式Tomcat和Maven插件
  • 直接从IntelliJ运行Tomcat
  • 直接从Eclipse / STS运行Tomcat

无论选择哪种方式,请确保您可以访问上述URL,然后再继续。 坦白说,用Java进行部署对我来说并不是一件很有趣的事情,因此,如果您感到沮丧,请不要放弃,它可能不会第一次起作用。 但是使用上面的教程,您可能不会有任何问题。 另外,不要忘记设置正确的应用程序上下文。

在编写更多控制器之前,让我们准备一些数据。 当Spring创建welcomeController bean时,我们想要一些数据。 因此,现在,让我们编写虚拟生成器,它会创建一些实体。 在本教程的后面,我们将看到一些更实际的解决方案。

辅助程序包放在Web程序包下,然后将控制器放置在EntityGenerator类中:

package org.timesheet.web.helpers;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.timesheet.domain.Employee;
import org.timesheet.domain.Manager;
import org.timesheet.domain.Task;
import org.timesheet.domain.Timesheet;
import org.timesheet.service.GenericDao;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.service.dao.ManagerDao;
import org.timesheet.service.dao.TaskDao;
import org.timesheet.service.dao.TimesheetDao;import java.util.List;/*** Small util helper for generating entities to simulate real system.*/
@Service
public final class EntityGenerator {@Autowiredprivate EmployeeDao employeeDao;@Autowiredprivate ManagerDao managerDao;@Autowiredprivate TaskDao taskDao;@Autowiredprivate TimesheetDao timesheetDao;public void generateDomain() {Employee steve = new Employee('Steve', 'Design');Employee bill = new Employee('Bill', 'Marketing');Employee linus = new Employee('Linus', 'Programming');// free employees (no tasks/timesheets)Employee john = new Employee('John', 'Beatles');Employee george = new Employee('George', 'Beatles');Employee ringo = new Employee('Ringo', 'Beatles');Employee paul = new Employee('Paul', 'Beatles');Manager eric = new Manager('Eric');Manager larry = new Manager('Larry');// free managersManager simon = new Manager('Simon');Manager garfunkel = new Manager('Garfunkel');addAll(employeeDao, steve, bill, linus, john, george, ringo, paul);addAll(managerDao, eric, larry, simon, garfunkel);Task springTask = new Task('Migration to Spring 3.1', eric, steve, linus);Task tomcatTask = new Task('Optimizing Tomcat', eric, bill);Task centosTask = new Task('Deploying to CentOS', larry, linus);addAll(taskDao, springTask, tomcatTask, centosTask);Timesheet linusOnSpring = new Timesheet(linus, springTask, 42);Timesheet billOnTomcat = new Timesheet(bill, tomcatTask, 30);addAll(timesheetDao, linusOnSpring, billOnTomcat);}public void deleteDomain() {List<Timesheet> timesheets = timesheetDao.list();for (Timesheet timesheet : timesheets) {timesheetDao.remove(timesheet);}List<Task> tasks = taskDao.list();for (Task task : tasks) {taskDao.remove(task);}List<Manager> managers = managerDao.list();for (Manager manager : managers) {managerDao.remove(manager);}List<Employee> employees = employeeDao.list();for (Employee employee : employees) {employeeDao.remove(employee);}}private <T> void addAll(GenericDao<T, Long> dao, T... entites) {for (T o : entites) {dao.add(o);}}
}

现在,让我们使用WelcomeController的代码。 我们将在此处注入生成器,并放置使用@PostConstruct注释进行注释的特殊方法。 这是用于bean生命周期的JSR-250注释,Spring对此进行了支持。 这意味着,在Spring IoC容器实例化welcomeController bean之后,将立即调用此方法。

package org.timesheet.web;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.timesheet.web.helpers.EntityGenerator;import javax.annotation.PostConstruct;
import java.util.Date;@Controller
@RequestMapping('/welcome')
public class WelcomeController {@Autowiredprivate EntityGenerator entityGenerator;@RequestMapping(method = RequestMethod.GET)public String showMenu(Model model) {model.addAttribute('today', new Date());return 'index';}@PostConstructpublic void prepareFakeDomain() {entityGenerator.deleteDomain();entityGenerator.generateDomain();}}

好吧,现在就为域逻辑编写一些控制器!

我们将从编写Employee的控制器开始。 首先,在org.timesheet.web包下创建EmployeeController类。 将class标记为Web控制器并处理“ /员工”请求:

@Controller
@RequestMapping('/employees')
public class EmployeeController { ...

为了处理持久性数据(在这种情况下为Employees),我们需要DAO并将其通过Spring的IoC容器自动连接,所以我们就可以这样做:

private EmployeeDao employeeDao;@Autowiredpublic void setEmployeeDao(EmployeeDao employeeDao) {this.employeeDao = employeeDao;}

现在我们要处理HTTP GET方法。 当用户使用Web浏览器访问http:// localhost:8080 / timesheet-app / employees时,控制器必须处理GET请求。 只是联系DAO并收集所有员工并将他们纳入模型。

@RequestMapping(method = RequestMethod.GET)public String showEmployees(Model model) {List<Employee> employees = employeeDao.list();model.addAttribute('employees', employees);return 'employees/list';}

在jsp文件夹下,创建employees文件夹,我们将在其中放置所有相应的雇员JSP。 可能您已经注意到,包含员工列表的页面将解析为/WEB-INF/jsp/employees/list.jsp。 因此,创建这样的页面。 稍后,我们将查看内容,如果您愿意,可以暂时在其中放置随机文本以查看其是否有效。

在JSP页面中,我们将在员工个人页面旁边显示一个链接,该链接看起来像http:// localhost:8080 / timesheet-app / employees / {id} ,其中ID是员工的ID。 这是RESTful URL,因为它是面向资源的,我们正在直接标识资源。 RESTless URL类似于http:// localhost:8080 / timesheet-app / employees.html?id = 123。 那是面向行动的,不能识别资源。

让我们向控制器添加另一个方法来处理此URL:

@RequestMapping(value = '/{id}', method = RequestMethod.GET)public String getEmployee(@PathVariable('id') long id, Model model) {Employee employee = employeeDao.find(id);model.addAttribute('employee', employee);return 'employees/view';}

同样,在/ jsp / employees文件夹下创建view.jsp页面。 在此页面上,我们还想更改员工。 我们只是访问相同的URL,但使用不同的Web方法-POST。 这意味着,我们正在从有限模型中提供数据以进行更新。

此方法处理员工更新:

@RequestMapping(value = '/{id}', method = RequestMethod.POST)public String updateEmployee(@PathVariable('id') long id, Employee employee) {employee.setId(id);employeeDao.update(employee);return 'redirect:/employees';}

在这种情况下,我们使用GET或POST方法访问employee / {id}。 但是,如果我们要删除员工怎么办? 我们将访问相同的URL,但使用不同的方法-DELETE 。 我们将在EmployeeDao中使用其他业务逻辑。 如果出现任何问题,我们将引发包含无法删除的员工的异常。 因此,在这种情况下,请添加控制器方法:

/*** Deletes employee with specified ID* @param id Employee's ID* @return redirects to employees if everything was ok* @throws EmployeeDeleteException When employee cannot be deleted*/@RequestMapping(value = '/{id}', method = RequestMethod.DELETE)public String deleteEmployee(@PathVariable('id') long id)throws EmployeeDeleteException {Employee toDelete = employeeDao.find(id);boolean wasDeleted = employeeDao.removeEmployee(toDelete);if (!wasDeleted) {throw new EmployeeDeleteException(toDelete);}// everything OK, see remaining employeesreturn 'redirect:/employees';}

注意,我们正在从该方法返回重定向 。 redirect:前缀表示应将请求重定向到它之前的路径。

创建包org.timesheet.web.exceptions并将EmployeeDeleteException放在下面:

package org.timesheet.web.exceptions;import org.timesheet.domain.Employee;/*** When employee cannot be deleted.*/
public class EmployeeDeleteException extends Exception {private Employee employee;public EmployeeDeleteException(Employee employee) {this.employee = employee;}public Employee getEmployee() {return employee;}
}

可以说,可以直接从DAO抛出此异常。 现在我们该如何处理? Spring有一个特殊的注释,称为@ExceptionHandler 。 我们将其放置在控制器中,并在抛出指定异常时,使用ExceptionHandler注释的方法将对其进行处理并解析正确的视图:

/*** Handles EmployeeDeleteException* @param e Thrown exception with employee that couldn't be deleted* @return binds employee to model and returns employees/delete-error*/@ExceptionHandler(EmployeeDeleteException.class)public ModelAndView handleDeleteException(EmployeeDeleteException e) {ModelMap model = new ModelMap();model.put('employee', e.getEmployee());return new ModelAndView('employees/delete-error', model);}

好的,时间到了JSP。 我们将使用一些资源(例如* .css或* .js),以便在您的Web应用程序根目录中创建resources文件夹。 WEB-INF不是root,它是上面的文件夹。 因此,资源和WEB-INF现在应该在目录树中处于同一级别。 我们已经将调度程序servlet配置为处理每个请求(使用/ url模式),但是我们不想让它的atm处理静态资源。 我们将通过简单地将默认servlet的映射放入我们的web.xml文件中来解决该问题:

<servlet-mapping><servlet-name>default</servlet-name><url-pattern>/resources/*</url-pattern></servlet-mapping>

在那些资源下创建styles.css文件。 即使我们稍后将使用lota之类的东西,我们现在也将CSS放在整个应用程序中。

table, th {margin: 10px;padding: 5px;width: 300px;
}.main-table {border: 2px solid green;border-collapse: collapse;
}.wide {width: 600px;
}.main-table th {background-color: green;color: white;
}.main-table td {border: 1px solid green;
}th {text-align: left;
}h1 {margin: 10px;
}a {margin: 10px;
}label {display: block;text-align: left;
}#list {padding-left: 10px;position: relative;
}#list ul {padding: 0;
}#list li {list-style: none;margin-bottom: 1em;
}.hidden {display: none;
}.delete {margin: 0;text-align: center;
}.delete-button {border: none;background: url('/timesheet-app/resources/delete.png') no-repeat top left;color: transparent;cursor: pointer;padding: 2px 8px;
}.task-table {width: 150px;border: 1px solid #dcdcdc;
}.errors {color: #000;background-color: #ffEEEE;border: 3px solid #ff0000;padding: 8px;margin: 16px;
}

现在,让我们创建employeeess / list.jsp页面:

<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<%@ taglib prefix='fmt' uri='http://java.sun.com/jsp/jstl/fmt' %>
<%@ taglib prefix='spring' uri='http://www.springframework.org/tags' %>
<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core'%>
<%@ taglib prefix='sf' uri='http://www.springframework.org/tags/form'%><html>
<head><title>Employees</title><link rel='stylesheet' href='/timesheet-app/resources/style.css' type='text/css'>
</head>
<body><h1>List of employees</h1><a href='employees?new'>Add new employee</a><table cellspacing='5' class='main-table'><tr><th>Name</th><th>Department</th><th>Details</th><th>Delete</th></tr><c:forEach items='#{employees}' var='emp'><tr><td>${emp.name}</td><td>${emp.department}</td><td><a href='employees/${emp.id}'>Go to page</a></td><td><sf:form action='employees/${emp.id}' method='delete' cssClass='delete'><input type='submit' class='delete-button' value='' /></sf:form></td></tr></c:forEach></table><br /><a href='welcome'>Go back</a>
</body>
</html>

在该页面上,我们正在资源下链接css(具有包括应用程序上下文的全名)。 还有链接到员工详细信息页面(view.jsp)的链接,该页面由员工的ID解析。
最有趣的部分是SF taglib的用法。 为了保持对Web 1.0的友好,我们很遗憾不能直接使用DELETE。 直到HTML4和XHTML1,HTML表单只能使用GET和POST。 解决方法是,如果实际上应将POST用作DELETE,则使用标记的隐藏字段。 这正是Spring免费为我们服务的-仅使用sf:form前缀。 因此,我们正在通过HTTP POST隧道传送DELETE,但它将被正确调度。 为此,我们必须为此在web.xml中添加特殊的Spring过滤器:

<filter><filter-name>httpMethodFilter</filter-name><filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class></filter><filter-mapping><filter-name>httpMethodFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>

即使JSP是实际上已编译为servlet的Java特定技术,我们也可以像使用任何HTML页面一样使用它。 我们添加了一些CSS,现在我们添加了最受欢迎的javascript库– jQuery。 转到https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js并下载jquery.js文件,并将其拖放到资源文件夹中。 我们将允许用户使用POST更新资源,因此我们将使用jQuery进行某些DOM操作-只是出于幻想。 您可以在普通HTML页面中使用几乎所有内容。

现在让我们创建/employees/view.jsp -这是员工的详细页面。

<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<%@ taglib prefix='sf' uri='http://www.springframework.org/tags/form'%><html>
<head><title>Employee page</title><link rel='stylesheet' href='/timesheet-app/resources/style.css' type='text/css'>
</head>
<body><h2>Employee info</h2><div id='list'><sf:form method='post'><ul><li><label for='name'>Name:</label><input name='name' id='name' value='${employee.name}' disabled='true'/></li><li><label for='department'>Department:</label><input name='department' id='department' value='${employee.department}' disabled='true' /></li><li><input type='button' value='Unlock' id='unlock' /><input type='submit' value='Save' id='save' class='hidden' /></li></ul></sf:form></div><br /><br /><a href='../employees'>Go Back</a><script src='/timesheet-app/resources/jquery-1.7.1.js'></script><script>(function() {$('#unlock').on('click', function() {$('#unlock').addClass('hidden');// enable stuff$('#name').removeAttr('disabled');$('#department').removeAttr('disabled');$('#save').removeClass('hidden');});})();</script>
</body>
</html>

在页面内部,我们引用jQuery文件,并具有自动调用的匿名功能-单击具有ID“解锁”的按钮后,我们将其隐藏,显示提交按钮并解锁字段,以便可以更新员工。 按下“保存”按钮后,我们将被重定向回员工列表,并且此列表已更新。

我们将在Employee上完成CRUD的最后一项功能是添加。 我们将通过使用GET和我们称之为new的额外参数来访问员工来解决这一问题。 因此,用于添加员工的URL将是: http:// localhost:8080 / timesheet-app / employees?new
让我们为此修改控制器:

@RequestMapping(params = 'new', method = RequestMethod.GET)public String createEmployeeForm(Model model) {model.addAttribute('employee', new Employee());return 'employees/new';}

这将为新的JSP页面提供服务-/ WEB-INF / jsp / employees / new.jsp

<%@ taglib prefix='sf' uri='http://www.springframework.org/tags/form' %>
<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<html>
<head><title>Add new employee</title><link rel='stylesheet' href='/timesheet-app/resources/style.css' type='text/css'>
</head>
<body><h2>Add new Employee</h2><div id='list'><sf:form method='post' action='employees'><ul><li><label for='name'>Name:</label><input name='name' id='name' value='${employee.name}'/></li><li><label for='department'>Department:</label><input name='department' id='department'value='${employee.department}' /></li><li><input type='submit' value='Save' id='save' /></li></ul></sf:form></div><br /><br /><a href='employees'>Go Back</a>
</body>
</html>

该页面与view.jsp非常相似。 在现实世界的应用程序中,我们将使用Apache Tiles之类的方法来减少冗余代码,但是现在让我们不必担心。

请注意,我们提交的表格带有“员工”操作。 回到我们的控制器,让我们使用POST http方法处理员工:

@RequestMapping(method = RequestMethod.POST)public String addEmployee(Employee employee) {employeeDao.add(employee);return 'redirect:/employees';}

而且,当我们无法删除雇员jsp / employees / delete-error.jsp时,请不要忘记错误的JSP页面:

<html>
<head><title>Cannot delete employee</title>
</head>
<body>Oops! Resource <a href='${employee.id}'>${employee.name}</a> can not be deleted.<p>Make sure employee doesn't have assigned any task or active timesheet.</p><br /><br /><br /><a href='../welcome'>Back to main page.</a>
</body>
</html>

就是这样,我们为员工提供了完整的CRUD功能。 让我们回顾一下我们刚刚做的基本步骤:

  • 添加了EmployeeController类
  • 在Web根目录中为静态内容创建资源文件夹
  • 在web.xml中为默认servlet添加了映射
  • 在资源文件夹中添加了styles.css
  • 在web.xml中使用过滤器配置了POST-DELETE隧道
  • 下载jQuery.js并添加到我们的资源文件夹中
  • 添加了employeeess / list.jsp页面
  • 添加了employeeess / view.jsp页面
  • 添加了employeeess / new.jsp页面
  • 添加了employees / delete-error.jsp页面

现在,这是控制器的完整代码:

package org.timesheet.web;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.timesheet.domain.Employee;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.web.exceptions.EmployeeDeleteException;import java.util.List;/*** Controller for handling Employees.*/
@Controller
@RequestMapping('/employees')
public class EmployeeController {private EmployeeDao employeeDao;@Autowiredpublic void setEmployeeDao(EmployeeDao employeeDao) {this.employeeDao = employeeDao;}public EmployeeDao getEmployeeDao() {return employeeDao;}/*** Retrieves employees, puts them in the model and returns corresponding view* @param model Model to put employees to* @return employees/list*/@RequestMapping(method = RequestMethod.GET)public String showEmployees(Model model) {List<Employee> employees = employeeDao.list();model.addAttribute('employees', employees);return 'employees/list';}/*** Deletes employee with specified ID* @param id Employee's ID* @return redirects to employees if everything was ok* @throws EmployeeDeleteException When employee cannot be deleted*/@RequestMapping(value = '/{id}', method = RequestMethod.DELETE)public String deleteEmployee(@PathVariable('id') long id)throws EmployeeDeleteException {Employee toDelete = employeeDao.find(id);boolean wasDeleted = employeeDao.removeEmployee(toDelete);if (!wasDeleted) {throw new EmployeeDeleteException(toDelete);}// everything OK, see remaining employeesreturn 'redirect:/employees';}/*** Handles EmployeeDeleteException* @param e Thrown exception with employee that couldn't be deleted* @return binds employee to model and returns employees/delete-error*/@ExceptionHandler(EmployeeDeleteException.class)public ModelAndView handleDeleteException(EmployeeDeleteException e) {ModelMap model = new ModelMap();model.put('employee', e.getEmployee());return new ModelAndView('employees/delete-error', model);}/*** Returns employee with specified ID* @param id Employee's ID* @param model Model to put employee to* @return employees/view*/@RequestMapping(value = '/{id}', method = RequestMethod.GET)public String getEmployee(@PathVariable('id') long id, Model model) {Employee employee = employeeDao.find(id);model.addAttribute('employee', employee);return 'employees/view';}/*** Updates employee with specified ID* @param id Employee's ID* @param employee Employee to update (bounded from HTML form)* @return redirects to employees*/@RequestMapping(value = '/{id}', method = RequestMethod.POST)public String updateEmployee(@PathVariable('id') long id, Employee employee) {employee.setId(id);employeeDao.update(employee);return 'redirect:/employees';}/*** Creates form for new employee* @param model Model to bind to HTML form* @return employees/new*/@RequestMapping(params = 'new', method = RequestMethod.GET)public String createEmployeeForm(Model model) {model.addAttribute('employee', new Employee());return 'employees/new';}/*** Saves new employee to the database* @param employee Employee to save* @return redirects to employees*/@RequestMapping(method = RequestMethod.POST)public String addEmployee(Employee employee) {employeeDao.add(employee);return 'redirect:/employees';}}

如果您使用的是SpringSource Tool Suite,则可以直接在IDE中检查映射。 将“ Spring项目性质”添加到您的项目中,在Properties-> Spring-> Bean Support中配置Spring的配置文件。 然后右键单击项目,然后按Spring Tools-> Show Request Mappings,您应该看到类似以下内容:

关于员工的最后一件事是编写JUnit测试。 由于我们的WEB-INF中有timesheet-servlet.xml,因此无法在JUnit测试中访问其bean。 我们要做的是从timesheet-servlet.xml中 删除以下行:

<context:component-scan base-package='org.timesheet.web' />

现在,我们在src / main / resources中创建新的Spring Bean配置,并将其称为controllers.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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd'><context:component-scan base-package='org.timesheet.web' /></beans>

为了使上下文知道那些spring bean,请像下面这样更改web.xml中的context-param:

<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:persistence-beans.xmlclasspath:controllers.xml</param-value></context-param>

另外,我们现在必须将bean从controllers.xml导入到timesheet-servlet.xml中,因此,我们添加了以下内容,而不是从timesheet-servlet.xml中删除<context:component-scan…行:

<import resource='classpath:controllers.xml' />

这将使我们能够将控制器自动连接到测试。 好的,因此在测试源文件夹中,创建包org.timesheet.web,然后将EmployeeControllerTest放在那里。 这非常简单,我们仅将控制器测试为POJO,以及它如何影响持久层(通过DAO验证)。 但是,我们做了一个例外。 在方法testDeleteEmployeeThrowsException中 ,我们将明确告诉DAO在尝试删除雇员时返回false。 这将为我们节省复杂的对象创建和附加DAO的注入。 我们将为此使用流行的模拟框架Mockito 。

向您的pom.xml添加依赖项:

<dependency><groupId>org.mockito</groupId><artifactId>mockito-all</artifactId><version>1.9.0</version></dependency>

测试EmployeeController:

package org.timesheet.web;import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Employee;
import org.timesheet.service.dao.EmployeeDao;
import org.timesheet.web.exceptions.EmployeeDeleteException;import java.util.Collection;
import java.util.List;import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;@ContextConfiguration(locations = {'/persistence-beans.xml', '/controllers.xml'})
public class EmployeeControllerTest extends DomainAwareBase {@Autowiredprivate EmployeeDao employeeDao;@Autowiredprivate EmployeeController controller;private Model model; // used for controller@Beforepublic void setUp() {model = new ExtendedModelMap();}@Afterpublic void cleanUp() {List<Employee> employees = employeeDao.list();for (Employee employee : employees) {employeeDao.remove(employee);}}@Testpublic void testShowEmployees() {// prepare some dataEmployee employee = new Employee('Lucky', 'Strike');employeeDao.add(employee);// use controllerString view = controller.showEmployees(model);assertEquals('employees/list', view);List<Employee> listFromDao = employeeDao.list();Collection<?> listFromModel = (Collection<?>) model.asMap().get('employees');assertTrue(listFromModel.contains(employee));assertTrue(listFromDao.containsAll(listFromModel));}@Testpublic void testDeleteEmployeeOk() throws EmployeeDeleteException {// prepare ID to deleteEmployee john = new Employee('John Lennon', 'Singing');employeeDao.add(john);long id = john.getId();// delete & assertString view = controller.deleteEmployee(id);assertEquals('redirect:/employees', view);assertNull(employeeDao.find(id));}@Test(expected = EmployeeDeleteException.class)public void testDeleteEmployeeThrowsException() throws EmployeeDeleteException {// prepare ID to deleteEmployee john = new Employee('John Lennon', 'Singing');employeeDao.add(john);long id = john.getId();// mock DAO for this callEmployeeDao mockedDao = mock(EmployeeDao.class);when(mockedDao.removeEmployee(john)).thenReturn(false);EmployeeDao originalDao = controller.getEmployeeDao();try {// delete & expect exceptioncontroller.setEmployeeDao(mockedDao);controller.deleteEmployee(id);} finally {controller.setEmployeeDao(originalDao);}}@Testpublic void testHandleDeleteException() {Employee john = new Employee('John Lennon', 'Singing');EmployeeDeleteException e = new EmployeeDeleteException(john);ModelAndView modelAndView = controller.handleDeleteException(e);assertEquals('employees/delete-error', modelAndView.getViewName());assertTrue(modelAndView.getModelMap().containsValue(john));}@Testpublic void testGetEmployee() {// prepare employeeEmployee george = new Employee('George Harrison', 'Singing');employeeDao.add(george);long id = george.getId();// get & assertString view = controller.getEmployee(id, model);assertEquals('employees/view', view);assertEquals(george, model.asMap().get('employee'));}@Testpublic void testUpdateEmployee() {// prepare employeeEmployee ringo = new Employee('Ringo Starr', 'Singing');employeeDao.add(ringo);long id = ringo.getId();// user alters Employee in HTML formringo.setDepartment('Drums');// update & assertString view = controller.updateEmployee(id, ringo);assertEquals('redirect:/employees', view);assertEquals('Drums', employeeDao.find(id).getDepartment());}@Testpublic void testAddEmployee() {// prepare employeeEmployee paul = new Employee('Paul McCartney', 'Singing');// save but via controllerString view = controller.addEmployee(paul);assertEquals('redirect:/employees', view);// employee is stored in DBassertEquals(paul, employeeDao.find(paul.getId()));}
}

注意,我们如何在try / finally块中使用模拟的dao进行设置。 仅用于那一次调用以确保引发正确的异常。 如果您从未见过嘲笑,我绝对建议您了解有关此技术的更多信息。 有很多模拟框架。 我们选择的一种-Mockito-带有非常简洁的语法,该语法大量使用Java静态导入。

现在,经理与员工非常相似,因此没有任何大问题,让我们为经理添加非常相似的内容:

首先,在WEB-INF / jsp中创建管理器文件夹。

现在让我们编写控制器并注入相应的DAO:

@Controller
@RequestMapping('/managers')
public class ManagerController {private ManagerDao managerDao;@Autowiredpublic void setManagerDao(ManagerDao managerDao) {this.managerDao = managerDao;}public ManagerDao getManagerDao() {return managerDao;}
}

列表管理员的添加方法:

/*** Retrieves managers, puts them in the model and returns corresponding view* @param model Model to put employees to* @return managers/list*/@RequestMapping(method = RequestMethod.GET)public String showManagers(Model model) {List<Manager> employees = managerDao.list();model.addAttribute('managers', employees);return 'managers/list';}

list.jsp添加到jsp / managers:

<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<%@ taglib prefix='fmt' uri='http://java.sun.com/jsp/jstl/fmt' %>
<%@ taglib prefix='spring' uri='http://www.springframework.org/tags' %>
<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core'%>
<%@ taglib prefix='sf' uri='http://www.springframework.org/tags/form'%><html>
<head><title>Managers</title><link rel='stylesheet' href='/timesheet-app/resources/style.css' type='text/css'>
</head>
<body><h1>List of managers</h1><a href='managers?new'>Add new manager</a><table cellspacing='5' class='main-table'><tr><th>Name</th><th>Details</th><th>Delete</th></tr><c:forEach items='#{managers}' var='man'><tr><td>${man.name}</td><td><a href='managers/${man.id}'>Go to page</a></td><td><sf:form action='managers/${man.id}' method='delete' cssClass='delete'><input type='submit' value='' class='delete-button' /></sf:form></td></tr></c:forEach></table><br /><a href='welcome'>Go back</a>
</body>
</html>

添加删除管理员的方法:

/*** Deletes manager with specified ID* @param id Manager's ID* @return redirects to managers if everything was OK* @throws ManagerDeleteException When manager cannot be deleted*/@RequestMapping(value = '/{id}', method = RequestMethod.DELETE)public String deleteManager(@PathVariable('id') long id)throws ManagerDeleteException {Manager toDelete = managerDao.find(id);boolean wasDeleted = managerDao.removeManager(toDelete);if (!wasDeleted) {throw new ManagerDeleteException(toDelete);}// everything OK, see remaining managersreturn 'redirect:/managers';}

删除失败时的异常:

package org.timesheet.web.exceptions;import org.timesheet.domain.Manager;/*** When manager cannot be deleted*/
public class ManagerDeleteException extends Exception {private Manager manager;public ManagerDeleteException(Manager manager) {this.manager = manager;}public Manager getManager() {return manager;}
}

处理此异常的方法:

/*** Handles ManagerDeleteException* @param e Thrown exception with manager that couldn't be deleted* @return binds manager to model and returns managers/delete-error*/@ExceptionHandler(ManagerDeleteException.class)public ModelAndView handleDeleteException(ManagerDeleteException e) {ModelMap model = new ModelMap();model.put('manager', e.getManager());return new ModelAndView('managers/delete-error', model);}

添加获取经理页面的方法:

/*** Returns manager with specified ID* @param id Managers's ID* @param model Model to put manager to* @return managers/view*/@RequestMapping(value = '/{id}', method = RequestMethod.GET)public String getManager(@PathVariable('id') long id, Model model) {Manager manager = managerDao.find(id);model.addAttribute('manager', manager);return 'managers/view';}

在jsp / managers下添加经理页面view.jsp

<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<%@ taglib prefix='sf' uri='http://www.springframework.org/tags/form'%><html>
<head><title>Manager page</title><link rel='stylesheet' href='/timesheet-app/resources/style.css' type='text/css'>
</head>
<body><h2>Manager info</h2><div id='list'><sf:form method='post'><ul><li><label for='name'>Name:</label><input name='name' id='name' value='${manager.name}' disabled='true'/></li><li><input type='button' value='Unlock' id='unlock' /><input type='submit' value='Save' id='save' class='hidden' /></li></ul></sf:form></div><br /><br /><a href='../managers'>Go Back</a><script src='/timesheet-app/resources/jquery-1.7.1.js'></script><script>(function() {$('#unlock').on('click', function() {$('#unlock').addClass('hidden');// enable stuff$('#name').removeAttr('disabled');$('#save').removeClass('hidden');});})();</script>
</body>
</html>

JSP页面,用于处理删除时的错误:

<html>
<head><title>Cannot delete manager</title>
</head>
<body>Oops! Resource <a href='${manager.id}'>${manager.name}</a> can not be deleted.<p>Make sure manager doesn't have assigned any task or active timesheet.</p><br /><br /><br /><a href='../welcome'>Back to main page.</a>
</body>
</html>

添加更新管理器的方法:

/*** Updates manager with specified ID* @param id Manager's ID* @param manager Manager to update (bounded from HTML form)* @return redirects to managers*/@RequestMapping(value = '/{id}', method = RequestMethod.POST)public String updateManager(@PathVariable('id') long id, Manager manager) {manager.setId(id);managerDao.update(manager);return 'redirect:/managers';}

添加用于返回新经理表格的方法:

/*** Creates form for new manager* @param model Model to bind to HTML form* @return manager/new*/@RequestMapping(params = 'new', method = RequestMethod.GET)public String createManagerForm(Model model) {model.addAttribute('manager', new Manager());return 'managers/new';}

在jsp / managers下为新经理new.jsp添加页面:

<%@ taglib prefix='sf' uri='http://www.springframework.org/tags/form' %>
<%@ page contentType='text/html;charset=UTF-8' language='java' %>
<html>
<head><title>Add new manager</title><link rel='stylesheet' href='/timesheet-app/resources/style.css' type='text/css'>
</head>
<body><h2>Add new Manager</h2><div id='list'><sf:form method='post' action='managers'><ul><li><label for='name'>Name:</label><input name='name' id='name' value='${manager.name}'/></li><li><input type='submit' value='Save' id='save' /></li></ul></sf:form></div><br /><br /><a href='managers'>Go Back</a>
</body>
</html>

最后,添加用于添加管理器的方法:

/*** Saves new manager to the database* @param manager Manager to save* @return redirects to managers*/@RequestMapping(method = RequestMethod.POST)public String addManager(Manager manager) {managerDao.add(manager);return 'redirect:/managers';}

好了,这部分的最后一段代码是ManagerController的测试用例:

package org.timesheet.web;import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;
import org.springframework.web.servlet.ModelAndView;
import org.timesheet.DomainAwareBase;
import org.timesheet.domain.Manager;
import org.timesheet.service.dao.ManagerDao;
import org.timesheet.web.exceptions.ManagerDeleteException;import java.util.Collection;
import java.util.List;import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;@ContextConfiguration(locations = {'/persistence-beans.xml', '/controllers.xml'})
public class ManagerControllerTest extends DomainAwareBase {@Autowiredprivate ManagerDao managerDao;@Autowiredprivate ManagerController controller;private Model model; // used for controller@Beforepublic void setUp() {model = new ExtendedModelMap();}@Afterpublic void cleanUp() {List<Manager> managers = managerDao.list();for (Manager manager : managers) {managerDao.remove(manager);}}@Testpublic void testShowManagers() {// prepare some dataManager manager = new Manager('Bob Dylan');managerDao.add(manager);// use controllerString view = controller.showManagers(model);assertEquals('managers/list', view);List<Manager> listFromDao = managerDao.list();Collection<?> listFromModel = (Collection<?>) model.asMap().get('managers');assertTrue(listFromModel.contains(manager));assertTrue(listFromDao.containsAll(listFromModel));}@Testpublic void testDeleteManagerOk() throws ManagerDeleteException {// prepare ID to deleteManager john = new Manager('John Lennon');managerDao.add(john);long id = john.getId();// delete & assertString view = controller.deleteManager(id);assertEquals('redirect:/managers', view);assertNull(managerDao.find(id));}@Test(expected = ManagerDeleteException.class)public void testDeleteManagerThrowsException() throws ManagerDeleteException {// prepare ID to deleteManager john = new Manager('John Lennon');managerDao.add(john);long id = john.getId();// mock DAO for this callManagerDao mockedDao = mock(ManagerDao.class);when(mockedDao.removeManager(john)).thenReturn(false);ManagerDao originalDao = controller.getManagerDao();try {// delete & expect exceptioncontroller.setManagerDao(mockedDao);controller.deleteManager(id);} finally {controller.setManagerDao(originalDao);}}@Testpublic void testHandleDeleteException() {Manager john = new Manager('John Lennon');ManagerDeleteException e = new ManagerDeleteException(john);ModelAndView modelAndView = controller.handleDeleteException(e);assertEquals('managers/delete-error', modelAndView.getViewName());assertTrue(modelAndView.getModelMap().containsValue(john));}@Testpublic void testGetManager() {// prepare managerManager george = new Manager('George Harrison');managerDao.add(george);long id = george.getId();// get & assertString view = controller.getManager(id, model);assertEquals('managers/view', view);assertEquals(george, model.asMap().get('manager'));}@Testpublic void testUpdateManager() {// prepare managerManager ringo = new Manager('Ringo Starr');managerDao.add(ringo);long id = ringo.getId();// user alters manager in HTML formringo.setName('Rango Starr');// update & assertString view = controller.updateManager(id, ringo);assertEquals('redirect:/managers', view);assertEquals('Rango Starr', managerDao.find(id).getName());}@Testpublic void testAddManager() {// prepare managerManager paul = new Manager('Paul McCartney');// save but via controllerString view = controller.addManager(paul);assertEquals('redirect:/managers', view);// manager is stored in DBassertEquals(paul, managerDao.find(paul.getId()));}
}

请求映射现在看起来像这样:

因此,在这一部分中,我们学习了什么是Spring MVC,如何将实体用作模型,如何以POJO风格编写控制器,RESTful设计的外观,如何使用JSP创建视图以及如何使用CSS和JavaScript设置应用程序。

我们为员工和经理编写了控制器。 在下一部分中,我们将继续为“任务和时间表”编写控制器。 在进行下一部分之前,请确保到目前为止一切正常。

这是src文件夹(仅扩展了新内容。不必担心.iml文件,它们用于IntelliJ):

这是网络文件夹:

参考: 第4部分–添加Spring MVC –第1部分来自vrtoonjava博客上的JCG合作伙伴 Michal Vrtiak。

翻译自: https://www.javacodegeeks.com/2012/09/spring-adding-spring-mvc-part-1.html

Spring–添加SpringMVC –第1部分相关推荐

  1. Spring–添加SpringMVC –第2部分

    在上一部分中,我们为经理和员工实现了控制器. 既然我们知道了解决方法,我们将做很少(但仅做很少)更复杂的事情–任务和时间表的控制器. 因此,让我们从org.timesheet.web开始. TaskC ...

  2. 框架:springboot组合spring、springmvc、mybatis的一个小demo

    Spring+SpringMVC+Mybatis就是大名顶顶的SSM了. 这里我们使用IDEA的SpringBoot和版本控制器MAVEN构建一个SSM的demo. 介绍: 在搭建过程中spring整 ...

  3. 框架:spring、springmvc、springboot

    先来说说spring.springmvc.springboot的关系. spring boot只是一个配置工具,整合工具,辅助工具. springmvc是框架,项目中实际运行的代码 Spring 框架 ...

  4. Spring与SpringMVC的区别

    Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring. 简单点的话可以将SpringMVC类比于Strut ...

  5. SpringBoot配置与应用 SpringBoot与(Spring和springmvc的区别)

    1.什么是springboot? springboot与springmvc的区别,springboot代替了springmvc? ssm包含了spring与springmvc还有mybatis. 用到 ...

  6. SSM框架终极篇——Spring、SpringMVC、MyBatis整合练习(超级详细)

    SSM框架终极篇--Spring.SpringMVC.MyBatis整合练习 一.准备工作 环境: 新版IDEA MySQL 5.7 Tomcat 9.0.2 Maven 要求: 熟练掌握MySQL数 ...

  7. Java项目:养老院管理系统(java+Spring Boot + SpringMVC + MyBatis+HTML+CSS+JavaScrip+ Layui+maven+mysql)

    源码获取:博客首页 "资源" 里下载! 关注公众号,带你学Java 项目介绍 : Spring Boot + SpringMVC + MyBatis+ Mysql + druid ...

  8. springmvc和php,spring和springmvc的区别

    Spring与SpringMVC的区别 Spring是IOC和AOP的容器框架,SpringMVC是基于Spring功能之上添加的Web框架,想用SpringMVC必须先依赖Spring. 简单点的话 ...

  9. Spring+Mybatis+SpringMVC+Maven+MySql(SSM框架)搭建实例

    这篇文章我们来实现使用maven构建工具来搭建Spring+Mybatis+SpringMVC+MySql的框架搭建实例.工程下载 使用maven当然得配置有关环境了,不会配置的请看我前几篇文章,都有 ...

最新文章

  1. 由几个问题引出EJB的概念
  2. 听说最近你读过不少书
  3. 科大星云诗社动态20210121
  4. 香帅的北大金融学课笔记16 -- 公司决策
  5. boost::sort模块实现弦平面稳定测试
  6. 学生信息管理信息系统--添加用户
  7. 20162303 实验五 网络编程与安全
  8. C++动态内存会被自动释放吗?
  9. Unity3D基础31:脚本生命周期
  10. centos 卸载_9个Linux发行版本,教你如何分别使用命令行卸载安装包
  11. python jupyter notebook 上传文件_使用jupyter notebook将文件保存为Markdown,HTML等文件格式...
  12. ba网络c语言编程,如何用C语言程序构造随机网络和BA无尺度网络
  13. 尚学堂马士兵struts2操作手册
  14. Unity实时GI与烘焙GI
  15. 抖音的广告位在哪?抖音信息流广告样式?
  16. notion函数_【notion】关于进度管理-时间函数的一点小心得
  17. 7-18 二分法求多项式单根
  18. HGETALL key
  19. 【论文解读】FA-GANs: 面部吸引力增强、面部美容、几何与外观双分支GAN一致性增强
  20. MySQL01-Pycharm连接MySQL出现时差问题

热门文章

  1. spring boot actuator 入门荔枝
  2. 利用java求积分(定积分和无穷限积分)
  3. char类型和Unicode编码
  4. java流与文件——读写二进制数据(DataOutput + DataInput)
  5. 客户端服务端防止用户重复提交表单
  6. graphql 有必要吗_您准备好观看GraphQL了吗?
  7. java set方法不生效_使您的Java 8方法引用生效
  8. cov/cor中有遗漏值_协调遗漏的效果–使用简单的NIO客户端/服务器测量回送延迟...
  9. Mockito匹配器优先
  10. Eclipse系列的隐藏宝藏-2019年版