Spring–添加SpringMVC –第1部分

欢迎来到本教程的第四部分。 在这一部分中,我们将使用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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/371343.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

access month函数用法_学会了这7个EXCEL日期函数技巧,老板再让你加班,你找我!...

日期函数&#xff0c;常用年月日&#xff0c;时分秒&#xff0c;星期&#xff0c;季度&#xff0c;求差值等&#xff0c;学会以下几个函数&#xff0c;老板再让你加班&#xff0c;你找我&#xff01;1、记录当前时间(不随系统时间变化)NOW()函数与数据有效性结合&#xff0c;记…

meta 的作用 搜集

Meta标签中的format-detection属性及含义 format-detection翻译成中文的意思是“格式检测”&#xff0c;顾名思义&#xff0c;它是用来检测html里的一些格式的&#xff0c;那关于meta的format-detection属性主要是有以下几个设置&#xff1a;<meta name"format-detecti…

ThinkPHP 3.2.x 集成极光推送指北

3.2版本已经过了维护生命周期&#xff0c;官方已经不再维护&#xff0c;请及时更新至5.0版本 —— ThinkPHP 官方仓库 以上&#xff0c;如果有条件&#xff0c;请关闭这个页面&#xff0c;然后升级至 ThinkPHP 5&#xff0c;如果由于各种各样的原因无法升级至 TP 5 &#xff0c…

Java多线程——不变性与安全发布

1、不变性 某个对象在被创建后其状态就不能被修改&#xff0c;那么这个对象就称为不可变对象&#xff0c;不可变对象一定是线程安全的。不可变对象很简单。他们只有一种状态&#xff0c;并且该状态由构造函数来控制。 当满足以下条件时&#xff0c;对象才是不可变的&#xff1a…

中tr不能显示字符_垃圾文本识别中基本操作指南和错误总结,第三部分

创建模型需要用到机器学习的库&#xff0c;所以我们先下载sklearn库sklearn库下载完成后再输入库文件&#xff0c;就可以完美运行。然后就是划分测试集和训练集&#xff0c;需要注意的是&#xff0c;在从数据处理函数中导入数据时&#xff0c;足足运行了有将近30多秒&#xff0…

(转载)20分钟读懂程序集

转自&#xff1a;http://www.cnblogs.com/damonlan/p/3221347.html 说到程序集&#xff0c;我刚开始对这个名词特别的郁闷&#xff01;~。然后 前些天花了些时间 好好读了一下&#xff0c;现在比较清晰了&#xff0c;把一些书上看到的 记下来&#xff0c;以飨读者。希望没浪费你…

大数据胸_喂母乳会导致胸下垂?!你被这个谣言骗了多少年?

很多人认为&#xff0c;给宝宝喂奶会导致胸下垂。有些爱美的妈妈&#xff0c;甚至在宝宝出生6个月后就着急断奶。那么&#xff0c;喂奶真的会导致胸下垂么&#xff1f;给大家讲两个真实的调查结果哈~2004年的一次针对496名新妈妈的调查结果显示&#xff0c;有75%的母乳喂养母亲…

自制ACL+DHCP实验(初版)

&#xff08;实验用gns模拟器&#xff09; ACL 实验拓扑&#xff1a; 实验要求&#xff1a; 1.1.1.1→3.3.3.3 不通 11.11.11.11→3.3.3.3 通 2.2.2.2→3.3.3.3 通 实验步骤&#xff1a; 步骤一&#xff1a;基本配置 R1&#xff1a; R1#conf t R1(config)#int lo0 R1(config-if…

pil 图像最大值_第97天:图像库 PIL(二)

上节我们讲了 Python 的图像处理库 PIL 的基本图像处理功能&#xff0c;打开了 PIL 的神秘面纱。这节我们接着讲 PIL 的 Image 模块的常用方法。Image 模块的方法convertImage.convert(modeNone, matrixNone, ditherNone, palette0, colors256)参数说明&#xff1a;mode&#x…

c#的委托用法delegate

转载于:https://www.cnblogs.com/douzujun/p/6555886.html

np读取csv文件_被 Pandas read_csv 坑了

-- 不怕前路坎坷&#xff0c;只怕从一开始就走错了方向Pandas 是python的一个数据分析包&#xff0c;纳入了大量库和一些标准的数据模型&#xff0c;提供了高效地操作大型数据集所需的工具。Pandas 就是为解决数据分析任务生的&#xff0c;无论是数据分析还是机器学习项目数据预…

铃木uy125摩托车机油_UY125 新瑞梦UM125发布 济南铃木于湖南株洲吹响国IV集结号...

​4月18日&#xff0c;济南铃木在湖南株洲天台开元酒店举行了2019年新品发布会&#xff0c;并于现场发布了两款极具终端战略意义的新款国IV车型&#xff0c;分别为定位“实用运动”的全新个性化踏板车型UY125&#xff0c;以及能够进一步巩固济南铃木在国IV入门级踏板车型领域绝…

js判断时间是早上还是下午_牛奶早上喝好,还是晚上喝好?没想到“最佳时间”是这个点,颠覆了!...

都说喝牛奶好&#xff0c;要多喝。可什么时间喝牛奶最好呢&#xff1f;是饭前、饭后还是睡前&#xff1f;又或者喝酒前&#xff1f;确实得好好说说。传言&#xff1a;空腹时身体比较缺能量&#xff0c;牛奶里的蛋白会去提供能量&#xff0c;不会去构成和修复组织(比如修复皮肤)…

Python TK编程第一部分 Hello Again

当你想写大一点的程序的时候&#xff0c;将你的代码封装到一个或者多个类里会是一个不错的办法。下面hello world这个例子来自Matt Conway的Tkinter Life Preserver. [python]view plain copy from Tkinter import * class App: def __init__(self, master): …

视网膜脱离oct报告图_刚刚,爱尔眼科发布关于艾芬医生诊疗过程的核查报告

刚刚&#xff0c;爱尔眼科医院集团发布关于艾芬女士诊疗过程的核查报告&#xff0c;内容如下&#xff1a;得悉艾芬女士对武汉爱尔眼科医院白内障诊疗存疑&#xff0c;爱尔眼科医院集团高度重视&#xff0c;第一时间成立了工作组奔赴武汉&#xff0c;对事件的诊疗全过程开展了核…

20145233《网络对抗》第二周 后门原理与实践

20145233《网络对抗》第二周 后门原理与实践 实验内容 windows主机与kali虚拟机实现互联互通使用netcat获取主机操作Shell&#xff0c;cron启动使用socat获取主机操作Shell, 任务计划启动使用MSF meterpreter生成可执行文件&#xff0c;利用ncat或socat传送到主机并运行获取主机…

Spring 3.1:缓存和EhCache

如果在网上查找使用Spring 3.1内置缓存的示例&#xff0c;那么通常会碰到Spring的SimpleCacheManager &#xff0c;Spring的家伙说这对“用于测试或简单的缓存声明很有用”。 实际上&#xff0c;我更喜欢将SimpleCacheManager看作是轻量级的&#xff0c;而不是简单的。 在您希望…

mysql-表完整性约束

阅读目录 一 介绍二 not null与default三 unique四 primary key五 auto_increment六 foreign key七 总结一 介绍 回到顶部 约束条件与数据类型的宽度一样&#xff0c;都是可选参数 作用&#xff1a;用于保证数据的完整性和一致性主要分为&#xff1a; PRIMARY KEY (PK) 标识…

可消费消息数量_17 个方面,综合对比 主流消息队列

一、资料文档二、开发语言三、支持的协议四、消息存储五、消息事务六、负载均衡七、集群方式八、管理界面九、可用性十、消息重复十一、吞吐量TPS十二、订阅形式和消息分发十三、顺序消息十四、消息确认十五、消息回溯十六、消息重试十七、并发度本文将从&#xff0c;Kafka、Ra…

opencv2.4.13+python2.7学习笔记--使用 knn对手写数字OCR

阅读对象&#xff1a;熟悉knn、了解opencv和python。 1.knn理论介绍&#xff1a;算法学习笔记&#xff1a;knn理论介绍 2. opencv中knn函数 路径&#xff1a;opencv\sources\modules\ml\include\opencv2\ml\ml.hpp 3.案例 3.1数据集介绍 我们的目的是创建一个可以对手写数字进行…