介绍如何使用Java EE 7和Angular JS来构建一个非常简单的应用

jopen 10年前

今天这篇文章将会向你介绍如何使用Java EE 7Angular JS来构建一个非常简单的应用。在此之前,先给大家讲一个故事。

我必须承认,我从来都不是一个Javascript的超级粉丝,但我始终记得我第一使用它的情景。我不太记得确切的年份,不过大概是90年代的中期。我的一个页面里有3个frame(是的,frame!记得这个东西吗?那个时候非常流行的),而我想做的是在点击了第3个frame上的一个链接时,能够重新加载前两个frame。那个时候,Javascript是用来在页面上做一些花哨的东西的。不是所有的浏览器都支持,有些浏览器甚至需要你手动打开。时至今日,这个情景已经有了显著变化,Javascript成为一门全栈的开发语言。你甚至可以仅仅用Javascript就能开发出一个完整的应用。但对于我来说,有时我还是会回想到90年代的情形,很难给予Javascript足够的信任,所以这是我为了更好的了解Javascript所做出的尝试。

为什么用Java EE 7?

好吧,我喜欢Java,并且新的Java EE版本非常好用,配合Wildfly或Glassfish使得它简洁而且运行快速。Java EE7的规范可以满足你所有需求,是Java编程中的标准。

为什么用Angular JS?

这里我大概是追随了Angular JS的热潮。因为我在Javascript方面没有太多的经验,不太了解各类的JS库,所以这里只是采纳了一些朋友的建议。而且我也注意到了在最近一次Devoxx大会上对于Angular得到了广泛的接纳,关于Angular的演讲的每一个房间都爆满。所以我想用它尝试一下,也给我自己一个认识的机会。

关于应用

这个应用是一个简单的包含分页的列表,以及一个REST服务用于提供列表中的数据。每当我开始一个新的企业级应用的时候,这通常是我们开始编码时所做的第一件事:创建一张表,存入一些数据,然后列出一些随机数据,所以我认为这个应用是非常合适的。

配置

代码(终于到代码部分了!)

后端-Java EE 7

从后端开始,让我们定义一个简单的Entity类(为了简洁省略了部分代码):

Persion.java

@Entity  public class Person {      @Id      private Long id;        private String name;        private String description;    }

如果你不是很熟悉Java EE的JPA规范,这里就稍作解释。这段代码使用了@Entity注解,从而使一个对象类连接到数据库中同名的表,并产生映射关系,注解@Id用于定义表中的主键。

接下来是persistence.xml文件:

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>  <persistence version="2.1"               xmlns="http://xmlns.jcp.org/xml/ns/persistence"               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"               xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">      <persistence-unit name="myPU" transaction-type="JTA">          <properties>              <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>              <property name="javax.persistence.schema-generation.create-source" value="script"/>              <property name="javax.persistence.schema-generation.drop-source" value="script"/>              <property name="javax.persistence.schema-generation.create-script-source" value="sql/create.sql"/>              <property name="javax.persistence.schema-generation.drop-script-source" value="sql/drop.sql"/>              <property name="javax.persistence.sql-load-script-source" value="sql/load.sql"/>          </properties>      </persistence-unit>  </persistence>

这里有我最喜欢的Java EE 7中的两个特性:现在,你可以使用javax.persistence.schema-generation.*属性来标准化的调用sql。并且,如果你不提供一个数据源的话,它就会绑定到一个默认的数据源。所以在这个例子中,它将会使用内部的Wildfly H2的数据源。

最后,为了提供列表数据,我们需要查询数据库并以REST服务的形式暴露出来:

PersonResource.java

@Stateless  @ApplicationPath("/resources")  @Path("persons")  public class PersonResource extends Application {      @PersistenceContext      private EntityManager entityManager;        private Integer countPersons() {          Query query = entityManager.createQuery("SELECT COUNT(p.id) FROM Person p");          return ((Long) query.getSingleResult()).intValue();      }        @SuppressWarnings("unchecked")      private List<Person> findPersons(int startPosition, int maxResults, String sortFields, String sortDirections) {          Query query = entityManager.createQuery("SELECT p FROM Person p ORDER BY " + sortFields + " " + sortDirections);          query.setFirstResult(startPosition);          query.setMaxResults(maxResults);          return query.getResultList();      }        public PaginatedListWrapper<Person> findPersons(PaginatedListWrapper<Person> wrapper) {          wrapper.setTotalResults(countPersons());          int start = (wrapper.getCurrentPage() - 1) * wrapper.getPageSize();          wrapper.setList(findPersons(start,                                      wrapper.getPageSize(),                                      wrapper.getSortFields(),                                      wrapper.getSortDirections()));          return wrapper;      }        @GET      @Produces(MediaType.APPLICATION_JSON)      public PaginatedListWrapper<Person> listPersons(@DefaultValue("1")                                                      @QueryParam("page")                                                      Integer page,                                                      @DefaultValue("id")                                                      @QueryParam("sortFields")                                                      String sortFields,                                                      @DefaultValue("asc")                                                      @QueryParam("sortDirections")                                                      String sortDirections) {          PaginatedListWrapper<Person> paginatedListWrapper = new PaginatedListWrapper<>();          paginatedListWrapper.setCurrentPage(page);          paginatedListWrapper.setSortFields(sortFields);          paginatedListWrapper.setSortDirections(sortDirections);          paginatedListWrapper.setPageSize(5);          return findPersons(paginatedListWrapper);      }  }

这里的代码非常像普通的Java POJP,但是使用了Java EE的注解来增强行为。@ApplicationPath("/resources")@Path("persons")会通过yourdomain/resources/personsurl来暴露REST服务,@GET标志了这个逻辑通过http的GET方法调用,@Produces(MediaType.APPLICATION_JSON)把REST响应格式化为JSON格式。仅仅用少量注解就能完成这些操作是很酷的。

为了更容易的获得分页列表所需的数据,我还创建了下面这个包装类:

PaginatedListWrapper.java

public class PaginatedListWrapper<T> {      private Integer currentPage;      private Integer pageSize;      private Integer totalResults;        private String sortFields;      private String sortDirections;      private List<T> list;  }

现在,后端的事情我们已经完成了。

UI-Angular JS

我们使用Angular JS来展示数据。Angular遵循了MVC模式,通过添加一些自定义的标签属性来绑定数据和Javascript中出现的变量,从而扩展了传统的HTML。那么, 让我们来看看我们的html页面:

index.html

<!DOCTYPE html>  <!-- Declares the root element that allows behaviour to be modified through Angular custom HTML tags. -->  <html ng-app="persons">  <head>      <title></title>      <script src="lib/angular.min.js"></script>      <script src="lib/jquery-1.9.1.js"></script>      <script src="lib/ui-bootstrap-0.10.0.min.js"></script>      <script src="lib/ng-grid.min.js"></script>        <script src="script/person.js"></script>        <link rel="stylesheet" type="text/css" href="lib/bootstrap.min.css"/>      <link rel="stylesheet" type="text/css" href="lib/ng-grid.min.css"/>      <link rel="stylesheet" type="text/css" href="css/style.css"/>  </head>    <body>    <br>    <div class="grid">      <!-- Specify a JavaScript controller script that binds Javascript variables to the HTML.-->      <div ng-controller="personsList">          <!-- Binds the grid component to be displayed. -->          <div class="gridStyle" ng-grid="gridOptions"></div>            <!--  Bind the pagination component to be displayed. -->          <pagination direction-links="true" boundary-links="true"                      total-items="persons.totalResults" page="persons.currentPage" items-per-page="persons.pageSize"                      on-select-page="refreshGrid(page)">          </pagination>      </div>  </div>    </body>  </html>

除了Javascript和CSS声明之外,这里只有很少的代码。让人印象深刻的是,Angular还拥有非常广泛的即插即用的组件,所以,我使用了ng-grid来展示数据,用UI Bootstrap提供分页功能。ng-grid其实同样包含了分页的组件,但是我更喜欢UI Bootstrap提供的组件。
这里还缺少了一个文件——一个js文件来是实现功能:

persion.js

var app = angular.module('persons', ['ngGrid', 'ui.bootstrap']);  // Create a controller with name personsList to bind to the html page.  app.controller('personsList', function ($scope, $http) {      // Makes the REST request to get the data to populate the grid.      $scope.refreshGrid = function (page) {          $http({              url: 'resources/persons',              method: 'GET',              params: {                  page: page,                  sortFields: $scope.sortInfo.fields[0],                  sortDirections: $scope.sortInfo.directions[0]              }          }).success(function (data) {              $scope.persons = data;          });      };        // Do something when the grid is sorted.      // The grid throws the ngGridEventSorted that gets picked up here and assigns the sortInfo to the scope.      // This will allow to watch the sortInfo in the scope for changed and refresh the grid.      $scope.$on('ngGridEventSorted', function (event, sortInfo) {          $scope.sortInfo = sortInfo;      });        // Watch the sortInfo variable. If changes are detected than we need to refresh the grid.      // This also works for the first page access, since we assign the initial sorting in the initialize section.      $scope.$watch('sortInfo', function () {          $scope.refreshGrid($scope.persons.currentPage);      }, true);        // Initialize required information: sorting, the first page to show and the grid options.      $scope.sortInfo = {fields: ['id'], directions: ['asc']};      $scope.persons = {currentPage : 1};      $scope.gridOptions = {          data: 'persons.list',          useExternalSorting: true,          sortInfo: $scope.sortInfo      };  });

这个Javascript代码非常简洁,整体性很好。请注意每部分是如何加入到应用的控制器中的,这让你能够分开关注业务逻辑的多个部分。为了实现所需的功能,我们仅仅需要添加少量的方法,包括通过调用REST服务来刷新列表,以及监控表格中的数据来刷新视图。这是最终的结果:

下一步

这个系列的下一篇文章中,我打算:

  • 实现拦截
  • 实现详细视图
  • 实现下一个/上一个浏览导航
  • 在云上部署
  • 管理Javascript依赖

资源

你可以从我的github库中clone一个完整的拷贝,然后部署到Wildfly上,关于如何部署,也可以在上面找到指导,同样,它也应该能在Glassfish上运行。Java EE – Angular JS Source

更新

同时,我还根据管理Javascript依赖一文来更新了我的源码,请通过 release 1.0来下载源码,同样可以clone这个repo,并使用git checkout 1.0命令来下载release 1.0版本的源码。

原文链接: javacodegeeks 翻译: ImportNew.com - xiafei
译文链接: http://www.importnew.com/13195.html