这是Project Student的一部分。 其他职位包括带有Jersey的 Web服务 客户端,带有Jersey的 Web服务服务器 , 业务层 , 具有Spring数据的持久性 ,分片集成测试数据 , Webservice集成和JPA标准查询 。
当我开始这个项目时,我有四个目标。 他们没有特别的次序:
- 了解jQuery和其他AJAX技术。 为此,我需要一个了解的REST服务器,
- 捕获最近获得的有关球衣和挂毯的知识,
- 创建一个我可以用来了解其他技术(例如spring MVC,restlet,netty)的框架,以及
- 在工作面试中有什么要讨论的
如果对其他人有用–太好了! 这就是为什么它在Apache许可下可用。
(不言而喻,可接受的用途不包括在没有适当归因的情况下将“ Project Student”变成学生项目!)
学习AJAX的问题在于,我一开始会不确定问题的根源。 jQuery不好吗? 不良的REST服务? 还有吗 广泛的单元和集成测试是一个好的开始,但始终会存在一些不确定性。
另一个考虑因素是,在现实世界中,我们经常需要对数据库的基本视图。 它不会供公众使用,而是当我们遇到WTF时刻时供内部使用。 它也可以用于维护我们不想通过公共界面管理的信息,例如下拉菜单中的值。
对此可以稍作改动,以提供适度的可伸缩性。 将大型服务器用于数据库和REST服务,然后让N台前端服务器运行常规的Web应用程序,充当用户和REST服务之间的中介。 前端服务器可以是相当轻量的,并且可以根据需要旋转。 将缓存服务器放置在前端和REST服务器之间的加分点,因为将读取压倒性的点击量。
这种方法无法扩展到Amazon或Facebook的规模,但是对于许多站点来说已经足够了。
维护Webapp
这将我们带到了webapp onion的可选层上-传统的webapp充当REST服务的前端。 由于各种原因,我在应用程序中使用Tapestry 5 ,但这是一个任意决定,我不会花很多时间研究Tapestry特定的代码。
您可以使用创建新的挂毯项目
$ mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org
我已经在http://jumpstart.doublenegative.com.au/jumpstart/examples/找到了有价值的示例。 在适当的地方,我会注明出处。
稍后,我还将创建webapp的第二个可选层-使用Selenium和WebDriver (即Selenium 2.0)进行功能和回归测试。
局限性
只读 –分解webapp需要大量工作,因此初始版本将仅提供对简单表的只读访问。 没有更新,没有一对多映射。
用户身份验证 –尚未进行身份验证的工作。
加密 –尚未对通信进行加密。
数据库锁 –我们在休眠版本中使用机会锁定,而不是显式数据库锁定。 安全说明:根据最少公开的原则,除非需要,否则我们不想使该版本可见。 一个好的规则是,您将看到它是否请求一个特定的对象,但看不到列表中的对象。
REST客户端 -每种类型的默认GET处理程序非常粗糙-它仅返回对象列表。 我们需要更复杂的响应(例如,记录数,起始索引和结束索引,状态码等),并将让UI驱动它。 现在,我们真正需要的只是一个计数,我们可以只请求列表并计数元素的数量。
目标
我们需要一个列出数据库中所有课程的页面。 它不必担心分页,排序等问题。它应该具有用于编辑和删除记录的链接(可能是无效的)。 它不需要添加新课程的链接。
该页面应如下所示:
课程模板
Tapestry页面上列出的课程很简单–基本上只是修饰的值网格 。
(有关Layout.tml等信息,请参见挂毯原型。)
<html t:type="layout" title="Course List"t:sidebarTitle="Framework Version"xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"xmlns:p="tapestry:parameter"><!-- Most of the page content, including <head>, <body>, etc. tags, comes from Layout.tml --><t:zone t:id="zone"> <p>"Course" page</p><t:grid source="courses" row="course" include="uuid,name,creationdate" add="edit,delete"><p:name><t:pagelink page="CourseEditor" context="course.uuid">${course.name}</t:pagelink></p:name><p:editcell><t:actionlink t:id="edit" context="course.uuid">Edit</t:actionlink></p:editcell><p:deletecell><t:actionlink t:id="delete" context="course.uuid">Delete</t:actionlink></p:deletecell><p:empty><p>There are no courses to display; you can <t:pagelink page="Course/Editor" parameters="{ 'mode':'create', 'courseUuid':null }">add some</t:pagelink>.</p></p:empty></t:grid></t:zone><p:sidebar><p>[<t:pagelink page="Index">Index</t:pagelink>]<br/>[<t:pagelink page="Course/List">Courses</t:pagelink>]</p></p:sidebar>
</html>
具有的属性文件
title=Courses
delete-course=Delete course?
我已经包括了用于编辑和删除的操作链接,但是它们不起作用。
GridDataSources
我们的页面需要显示值的来源。 这需要两个类。 第一个定义了上面使用的课程属性。
package com.invariantproperties.sandbox.student.maintenance.web.tables;import com.invariantproperties.sandbox.student.business.CourseFinderService;public class GridDataSources {// Screen fields@Propertyprivate GridDataSource courses;// Generally useful bits and pieces@Injectprivate CourseFinderService courseFinderService;@InjectComponentprivate Grid grid;// The codevoid setupRender() {courses = new CoursePagedDataSource(courseFinderService);}
}
实际的实现是
package com.invariantproperties.sandbox.student.maintenance.web.tables;import com.invariantproperties.sandbox.student.business.CourseFinderService;
import com.invariantproperties.sandbox.student.domain.Course;
import com.invariantproperties.sandbox.student.maintenance.query.SortCriterion;
import com.invariantproperties.sandbox.student.maintenance.query.SortDirection;public class CoursePagedDataSource implements GridDataSource {private int startIndex;private List<Course> preparedResults;private final CourseFinderService courseFinderService;public CoursePagedDataSource(CourseFinderService courseFinderService) {this.courseFinderService = courseFinderService;}@Overridepublic int getAvailableRows() {long count = courseFinderService.count();return (int) count;}@Overridepublic void prepare(final int startIndex, final int endIndex, final List<SortConstraint> sortConstraints) {// Get a page of courses - ask business service to find them (from the// database)// List<SortCriterion> sortCriteria = toSortCriteria(sortConstraints);// preparedResults = courseFinderService.findCourses(startIndex,// endIndex - startIndex + 1, sortCriteria);preparedResults = courseFinderService.findAllCourses();this.startIndex = startIndex;}@Overridepublic Object getRowValue(final int index) {return preparedResults.get(index - startIndex);}@Overridepublic Class<Course> getRowType() {return Course.class;}/*** Converts a list of Tapestry's SortConstraint to a list of our business* tier's SortCriterion. The business tier does not use SortConstraint* because that would create a dependency on Tapestry.*/private List<SortCriterion> toSortCriteria(List<SortConstraint> sortConstraints) {List<SortCriterion> sortCriteria = new ArrayList<>();for (SortConstraint sortConstraint : sortConstraints) {String propertyName = sortConstraint.getPropertyModel().getPropertyName();SortDirection sortDirection = SortDirection.UNSORTED;switch (sortConstraint.getColumnSort()) {case ASCENDING:sortDirection = SortDirection.ASCENDING;break;case DESCENDING:sortDirection = SortDirection.DESCENDING;break;default:}SortCriterion sortCriterion = new SortCriterion(propertyName, sortDirection);sortCriteria.add(sortCriterion);}return sortCriteria;}
}
应用模块
现在有了GridDataSource,我们可以看到它的需求– CourseFinderService。 虽然有Tapestry-Spring集成,但我们希望保持维护Webapp尽可能的薄,所以现在我们使用标准的Tapestry注入。
package com.invariantproperties.sandbox.student.maintenance.web.services;import com.invariantproperties.sandbox.student.business.CourseFinderService;
import com.invariantproperties.sandbox.student.business.CourseManagerService;
import com.invariantproperties.sandbox.student.maintenance.service.impl.CourseFinderServiceTapestryImpl;
import com.invariantproperties.sandbox.student.maintenance.service.impl.CourseManagerServiceTapestryImpl;/*** This module is automatically included as part of the Tapestry IoC Registry,* it's a good place to configure and extend Tapestry, or to place your own* service definitions.*/
public class AppModule {public static void bind(ServiceBinder binder) {binder.bind(CourseFinderService.class, CourseFinderServiceTapestryImpl.class);binder.bind(CourseManagerService.class, CourseManagerServiceTapestryImpl.class);}....
}
请注意,我们正在使用标准的CourseFinderService接口以及特定于挂毯的实现。 这意味着我们可以直接使用标准实现,只需要对配置文件进行一点改动即可!
CourseFinderServiceTapestryImpl
CourseFinderService接口的本地实现必须使用REST客户端而不是Spring Data实现。 使用早期使用的由外而内的方法,Tapestry模板的需求应推动服务实现的需求,进而驱动REST客户端和服务器的需求。
package com.invariantproperties.sandbox.student.maintenance.service.impl;public class CourseFinderServiceTapestryImpl implements CourseFinderService {private final CourseFinderRestClient finder;public CourseFinderServiceTapestryImpl() {// resource should be loaded as tapestry resourcefinal String resource = "http://localhost:8080/student-ws-webapp/rest/course/";finder = new CourseFinderRestClientImpl(resource);// load some initial datainitCache(new CourseManagerRestClientImpl(resource));}@Overridepublic long count() {// FIXME: grossly inefficient but good enough for now.return finder.getAllCourses().length;}@Overridepublic long countByTestRun(TestRun testRun) {// FIXME: grossly inefficient but good enough for now.return finder.getAllCourses().length;}@Overridepublic Course findCourseById(Integer id) {// unsupported operation!throw new ObjectNotFoundException(id);}@Overridepublic Course findCourseByUuid(String uuid) {return finder.getCourse(uuid);}@Overridepublic List<Course> findAllCourses() {return Arrays.asList(finder.getAllCourses());}@Overridepublic List<Course> findCoursesByTestRun(TestRun testRun) {return Collections.emptyList();}// method to load some test data into the database.private void initCache(CourseManagerRestClient manager) {manager.createCourse("physics 101");manager.createCourse("physics 201");manager.createCourse("physics 202");}
}
我们的JPA Criteria查询可以快速计数,但是我们的REST客户端尚不支持。
包起来
完成艰苦的工作后,我们将获得一个维护文件.war。 我们可以使用webservice .war将其部署在我们的应用服务器上,也可以不部署。 除了Web服务的临时硬编码URL外,这两个.war文件没有理由必须位于同一系统上。
我们首先应该转到http:// localhost:8080 / student-maintenance-webapp / course / list。 我们应该看到如上所示的简短课程清单。 (在那种情况下,我已经重新启动了webapp三次,因此每个条目都会重复三倍。)
现在,我们应该访问位于http:// localhost:8080 / student-ws-webapp / rest / course的webservice webapp,并验证我们是否也可以通过浏览器获取数据。 经过一点清理后,我们应该看到:
{"course":[{"creationDate":"2013-12-28T14:40:21.369-07:00","uuid":"500069e4-444d-49bc-80f0-4894c2d13f6a","version":"0","name":"physics 101"},{"creationDate":"2013-12-28T14:40:21.777-07:00","uuid":"54001b2a-abbb-4a75-a289-e1f09173fa04","version":"0","name":"physics 201"},{"creationDate":"2013-12-28T14:40:21.938-07:00","uuid":"cfaf892b-7ead-4d64-8659-8f87756bed62","version":"0","name":"physics 202"},{"creationDate":"2013-12-28T16:17:54.608-07:00","uuid":"d29735ff-f614-4979-a0de-e1d134e859f4","version":"0","name":"physics 101"},....]
}
源代码
- 源代码位于https://github.com/beargiles/project-student [github]和http://beargiles.github.io/project-student/ [github页面]。
翻译自: https://www.javacodegeeks.com/2014/01/project-student-maintenance-webapp-read-only.html