webapp框架_Project Student:维护Webapp(只读)

webapp框架

webapp框架

这是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的可选层-充当REST服务前端的常规webapp。 由于各种原因,我在应用程序中使用Tapestry 5 ,但这是一个任意决定,我不会花很多时间研究Tapestry特定的代码。

您可以使用创建新的挂毯项目

$ mvn archetype:generate -DarchetypeCatalog=http://tapestry.apache.org

我已经在http://jumpstart.doublenegative.com.au/jumpstart/examples/找到了有价值的示例。 在适当的地方,我会注明出处。

稍后,我还将创建webapp的第二个可选层–使用SeleniumWebDriver (即Selenium 2.0)进行功能和回归测试。

局限性

只读–分解webapp需要大量工作,因此初始版本将仅提供对简单表的只读访问。 没有更新,没有一对多映射。

用户身份验证–尚未进行身份验证的工作。

加密–尚未对通信进行加密。

数据库锁–我们在Hibernate版本中使用机会锁,而不是显式数据库锁。 安全说明:根据最少公开的原则,除非需要,否则我们不想使该版本可见。 一个好的规则是,您将看到它是否请求了一个特定的对象,但没有在列表中看到它。

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

    @Property
    private GridDataSource courses;

    // Generally useful bits and pieces

    @Inject
    private CourseFinderService courseFinderService;

    @InjectComponent
    private Grid grid;

    // The code

    void 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;
    }

    @Override
    public int getAvailableRows() {
        long count = courseFinderService.count();
        return (int) count;
    }

    @Override
    public 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;
    }

    @Override
    public Object getRowValue(final int index) {
        return preparedResults.get(index - startIndex);
    }

    @Override
    public 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 resource
        final String resource = "http://localhost:8080/student-ws-webapp/rest/course/";
        finder = new CourseFinderRestClientImpl(resource);

        // load some initial data
        initCache(new CourseManagerRestClientImpl(resource));
    }

    @Override
    public long count() {
        // FIXME: grossly inefficient but good enough for now.
        return finder.getAllCourses().length;
    }

    @Override
    public long countByTestRun(TestRun testRun) {
        // FIXME: grossly inefficient but good enough for now.
        return finder.getAllCourses().length;
    }

    @Override
    public Course findCourseById(Integer id) {
        // unsupported operation!
        throw new ObjectNotFoundException(id);
    }

    @Override
    public Course findCourseByUuid(String uuid) {
        return finder.getCourse(uuid);
    }

    @Override
    public List<Course> findAllCourses() {
        return Arrays.asList(finder.getAllCourses());
    }

    @Override
    public 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的Web服务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://www.javacodegeeks.com/2014/01/project-student-maintenance-webapp-read-only.html

webapp框架

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值