OSGi enRoute – 2.3_8 – 一个Web应用程序
在本章中你将学到什么
到现在为止我们已经通过Gogo shell对我们的表达式计算器进行了测试。在本章中,我们会创建一个小的web应用程序,它会通过web来调用表达式计算器。
我们将会使用HTML5和基于Angular的JavaScript来创建一个小的GUI,使用OSGi enRoute简单的REST功能来调用表达式计算器服务。
对于很多Java开发者来说,JavaScript、CSS和HTML5是陌生而可怕的。不过,这个样例仅需要关于这些技术的很少的一部分知识。难道你不想编写一些让你的男朋友或女朋友觉得很酷的东西吗?如果你对Web应用程序完全你不感兴趣,则可以跳过本章。
请确保你位于顶层文件夹:
1 |
$ cd ~/workspaces/osgi.enroute.examples.eval |
工程
1 2 3 4 |
osgi.enroute.examples.eval $ mkdir application osgi.enroute.examples.eval $ cd application application $ vi pom.xml // 编辑pom |
这个工程的pom文件如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.osgi</groupId> <artifactId>osgi.enroute.examples.eval</artifactId> <version>1.0.0-SNAPSHOT</version> </parent> <artifactId>osgi.enroute.examples.eval.application</artifactId> <description>A web application to evaluate expressions</description> <dependencies> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.enroute.examples.eval.api</artifactId> <version>1.0.0-SNAPSHOT</version> <scope>compile</scope> </dependency> </dependencies> </project> |
应用程序的Java源码
我们的应用程序类必须通过REST来访问表达式计算器,因此需要一个符合OSGi模型的简单REST provider。这样可以使得通过JavaScript应用程序来访问OSGi环境中的Java 服务变得更加容易。应用程序的Java源码
应用程序类的第二个目标在于作为需求图(Requirements graph)中的root。它定义了很多无法从代码中分析出来的Requirement,我们使用注解来添加这些Requirement。这些注解会由bnd转换为OSGi Requirement。
1 2 3 |
application $ mkdir -p src/main/java/osgi/enroute/examples/eval/application application $ vi src/main/java/osgi/enroute/examples/eval/application/EvalApplication.java // 添加源码 |
源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package osgi.enroute.examples.eval.application; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import osgi.enroute.configurer.api.RequireConfigurerExtender; import osgi.enroute.examples.eval.api.Eval; import osgi.enroute.google.angular.capabilities.RequireAngularWebResource; import osgi.enroute.rest.api.REST; import osgi.enroute.twitter.bootstrap.capabilities.RequireBootstrapWebResource; import osgi.enroute.webserver.capabilities.RequireWebServerExtender; @RequireAngularWebResource(resource={"angular.js","angular-resource.js", "angular-route.js"}, priority=1000) @RequireBootstrapWebResource(resource="css/bootstrap.css") @RequireWebServerExtender @Component(name="osgi.enroute.examples.eval") public class EvalApplication implements REST { @Reference Eval eval; public double getEval(String string) throws Exception { return eval.eval(string); } } |
@RequireAngularWebResource –除了通过在index.html中列出JavaScript和CSS依赖,我们还可以使用注解来定义需要引入的内容。这个注解可以单独定义一个需要被引入的资源。这个Angular注解会引入对应的Angular JavaScript资源。下面是注解的一个简要介绍:
● @RequireBootstrapWebResource – 引入Bootstrap CSS
● @RequireWebServerExtender – The Webserver extender使得提供静态Context变得更容易。它还为Web资源提供了运行时支持。
GUI
我们希望应用程序看起来如下图所示:
关于 Angular
对于大多数Java开发者来说,Web开发是一个可怕的领域。在OSGi enRoute中我们选择了Angular来开发GUI,尽管我们并不期望所有的Java OSGi开发者都去赶这个时髦(它们每3个月会进行一次改变),不过它的确能够创建非常简单、强大和可伸缩的应用程序。
Angular的关键概念是通过JavaScript变量来直接更新GUI。这使得业务逻辑与传统的模型和GUI进行了非常清晰的解耦。GUI自身通过HTML5来建模,并且往往其中会带有业务逻辑。不过,这个视图/控制逻辑应该限制在控制和视图的方面,并且不会包含业务逻辑。
关于 HTML 5
大多数Java开发者仍然将HTML视为XML的变体。事实并非如此,它更加宽松。在很多情况下,它无需将属性值放到括号中,并且很多标签不需要关闭。尽管这听起来对那些习惯了使用XML的人们来说是一种挑衅,但是它确实更加具有可读性并且有时候没有关闭标签会避免无意中因为标签之间的空白导致一个精心设计的布局被破坏。
静态Web内容
OSGi enRoute webserver bundle会将bundle中的任何名为static的文件夹映射到web server的根路径中。在OSGi enRoute 应用程序中,使用应用程序的全名来作为根路径是一个惯例。在这个文件夹中的内容除了index.html文件之外,都无法直接被访问。因此,我们具有如下静态web资源文件:
1 2 3 4 5 6 7 8 |
static/ osgi.enroute.examples.eval/ index.html favicon.ico htm/ main/ about.htm home.htm |
index.html
index.html文件是我们的JavaScript应用程序的根。这是一个Angular应用程序。它使用基于的URL路由,这使得它比最简单的Angular应用程序稍微复杂一点,但这使得它用起来更难。
在index.html文件中,我们会使用macros 和/或属性。因此我们需要bnd展开(expand)文件。这是必须的操作,因为web资源需要使用Bundle Symbolic Name和bundle中用到的真实版本号,这个版本号与Maven版本号有些不同。
index.html应该被存储到src/main/resources/static/osgi.enroute.examples.eval/index.html,这使得它可以在web服务器上被访问到,对应的路径为/osgi.enroute.examples.eval/index.htm。
1 2 3 |
application $ mkdir -p src/main/resources/static/osgi.enroute.examples.eval application $ vi src/main/resources/static/osgi.enroute.examples.eval/index.html // 添加随后的内容 |
index.html文件的第一部分是非常简明的HTML5代码,与Internet Explorer quirk模式兼容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="description" content=""> <meta name="author" content=""> <link rel="icon" href=/osgi.enroute.examples.eval/favicon.ico"> <title>OSGi ENROUTE EXAMPLE APPLICATION</title> |
下面的链接将会包含应用程序所需的CSS。CSS(在我们的例子中是Bootstrap)由应用程序类上的@RequireBootstrap注解进行标识。/osgi.enroute.webresource/这个URL被映射到一个servlet之上,它可以根据给定的Bundle-SymbolicName和Bundle Version查找所有必需的web资源。${bsn}和${Bundle-Version}宏会被bnd展开。
1 2 3 |
<link rel ="stylesheet" type="text/css" href="/osgi.enroute.webresource/${bsn}/${Bundle-Version}/*.css"> </head> |
Angular需要一个ng-app属性来作为应用程序的名称。
1 |
<body ng-app="osgi.enroute.examples.eval"> |
我们使用Bootstrap来为内容创建一个容器。
1 2 |
<div class="container"> <div class="header"> |
第一部分是一个导航条,其中我们摆放了2个view。Home view是我们的应用程序,用于运行表达式计算器。第二个view是About窗口。我们使用URL路由机制来进入正确的view。
1 2 3 4 5 6 7 |
<ul class="nav nav-pills pull-right"> <li ng-class="{active: page() == '/'}"> <a href="#/">Home</a> <li ng-class="{active: page() == '/about'}"> <a href="#/about">About</a> </ul> <h3 class="text-muted">OSGi enRoute Example Application</h3> |
下面是一个简单的警告区域。它可以用于报告类似通讯错误等错误信息。我们添加了一个关闭按钮来依次移除警告信息。
1 2 3 4 5 6 7 |
<div ng-repeat ="alert in alerts" class="alert alert-{{alert.type || 'alert'}} alert-dismissible"> <button type="button" ng-click='closeAlert($index)' class="close pull-right"> <span aria-hidden="true">×</span> <span class="sr-only">Close</span> </button> </div> </div> |
下面是见证Angular魔法的时刻,它会根据URL来使用内容(htm/main/home.htm 或 htm/main/about.htm)替代这个元素。
1 2 |
<div ng-view> </div> |
下面是一个简单的footer。如果你在bnd.bnd文件中设置了Bundle-Vendor数据头,则它会被用在此处。否则它将会使用常见的Company标题。还要注意我们是如何使用时间戳的。你可以使用任何bnd宏。
1 2 3 4 |
<div class="footer"> <p>© ${def;Bundle-Vendor;Company} ${tstamp;YYYY}</p> </div> </div> |
下面JavaScript web资源会发生变化。我们会将bundle所需的所有以.js结尾的资源包含进来。最后,bundle中的web文件夹中的内容也会被追加进来。
1 2 3 |
<script src="/osgi.enroute.webresource/${bsn}/${Bundle-Version}/*.js"></script> </body> </html> |
如果要使用真实值来替代bnd宏,我们必须告诉bnd对资源进行预处理。因此我们需要创建一个bnd.bnd文件:
1 2 |
application $ vi bnd.bnd // 添加内容 |
在bnd.bnd文件中需要添加下面这行内容:
1 2 |
-includeresource: \ {static/osgi.enroute.examples.eval/index.html=src/main/resources/static/osgi.enroute.examples.eval/index.html} |
视图
应用程序有两个视图。它们存储在src/main/resources/static/osgi.enroute.examples.eval/main/htm文件夹下。
1 |
application $ mkdir -p src/main/resources/static/osgi.enroute.examples.eval/main/htm |
Home页面
home.htm 是主视图。
1 2 |
application $ vi src/main/resources/static/osgi.enroute.examples.eval/htm/main/home.htm // 添加内容 |
它们如下所示:
1 2 3 |
<section ng-cloak style="min-height:500px"> <p>Enter an arithmetic expression like <code>sin(pi)</code> <div class="input-group"> |
下面是表达式输入框:
1 2 3 4 5 6 7 8 9 |
<input type="text" ng-model=expression class="form-control"> <span class="input-group-btn"> <button class="btn btn-default" type="button" ng-click=eval(expression)> Eval! </button> </span> </div> <hr> <div style="min-height:500px"> |
下面会打印出所有的答案:
1 2 3 4 5 |
<div ng-repeat="i in answers track by $index"> {{i}} </div> </div> </section> |
About页面
这是一个简单的about框。
1 2 |
application $ vi src/main/resources/static/osgi.enroute.examples.eval/htm/main/about.htm // add the content |
1 2 3 4 |
<section> <h1>About</h1> <p>This is a demo application to demonstrate how to build OSGi enRoute Applications with Maven/M2E<p> </section> |
Javascript 代码
Javascript代码必须存放在src/resources/main/web文件夹中。任何在这个文件夹中的内容会被当做web资源,并且会被自动包含到index.html页面之中。文件名不重要,但扩展名很重要,因为在index.html文件中根据扩展名来选择内容。
1 |
application $ mkdir -p src/main/resources/web |
main.js
application $ vi src/main/resources/web/main.js// 添加内容
我们创建了一个JavaScript“module”,这是一个匿名函数。
1 2 |
'use strict'; (function() { |
我们现在创建了一个Angular module。它的名称必须与index.html文件中的ng-app属性一致。
1 |
var MODULE = angular.module('osgi.enroute.examples.eval', [ 'ngRoute' ] ); |
下面的部分配置了路由表信息。路由表会匹配URL中的模式然后选择index.html页面中的ng-view的内容。
1 2 3 4 5 |
MODULE.config( function($routeProvider) { $routeProvider.when('/', { controller: mainProvider, templateUrl:/osgi.enroute.examples.eval/htm/main/home.htm'}); $routeProvider.when('/about', { templateUrl: '/osgi.enroute.examples.eval/htm/main/about.htm'}); $routeProvider.otherwise('/'); }); |
当module准备好运行之后会进行初始化。我们添加了用于显示警告和为导航栏提供当前页面名称的数据结构。
1 2 3 4 5 6 7 8 9 |
MODULE.run( function($rootScope, $location) { $rootScope.alerts = []; $rootScope.closeAlert = function(index) { $rootScope.alerts.splice(index, 1); }; $rootScope.page = function() { return $location.path(); } }); |
下面是一个answer列表,例如历史答案。
1 |
var answers = [ ]; |
当URL以#home结尾时将会调用下面的controller。我们设置了历史答案变量并且添加了一个eval方法。这个方法会调用服务器端。如果我们接收到了一个answer,我们会将其添加到历史answer列表之中。Angular随后会确保将其打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var mainProvider = function($scope, $http) { $scope.answers=answers; $scope.eval = function(expr) { $http.get('/rest/eval/'+expr).then( function(d) { answers.push(d.data + " = " + expr); }, function(d) { $scope.alerts.push( { type: 'danger', msg: 'Failed with ['+ d.status + '] '+ d.statusText }); } ); } } })(); |
构建
你应该将application module添加到parent pom之中然后构建bundle:
1 2 3 4 |
application $ vi ../pom.xml // 添加application module application $ mvn install ... |
运行
要运行这个应用程序我们需要将这个bundle添加到bndrun文件之中。这需要在bndrun工程中按照下面的步骤进行操作。
1 2 3 |
application $ cd ../bndrun bndrun $ vi pom.xml // 编辑dependencies部分 |
pom文件的dependencies部分如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
<dependencies> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.enroute.examples.eval.parsii.provider</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.enroute.examples.eval.command</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.enroute.examples.eval.application</artifactId> <version>1.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.osgi</groupId> <artifactId>osgi.enroute.pom.distro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.apache.felix.gogo.shell</artifactId> <version>1.0.0</version> </dependency> </dependencies> |
然后为应用程序添加新的-runrequires:
1 2 |
bndrun $ vi osgi.enroute.examples.eval.bndrun // Update -runrequires |
1 2 3 4 5 6 |
-runrequires: \ osgi.identity;filter:='(osgi.identity=osgi.enroute.examples.eval.parsii.provider)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.shell)',\ osgi.identity;filter:='(osgi.identity=org.apache.felix.gogo.command)',\ osgi.identity;filter:='(osgi.identity=osgi.enroute.examples.eval.command)',\ osgi.identity;filter:='(osgi.identity=osgi.enroute.examples.eval.application)' |
然后运行mvn install命令来获得新的-runbundles列表。这会给出一个大得多的列表,因为现在我们已经获得了所有的Web特性所需的支持:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
bndrun $ mvn install ... -runbundles: \ org.apache.felix.configadmin;version='[1.8.8,1.8.9)',\ org.apache.felix.gogo.command;version='[0.16.0,0.16.1)',\ org.apache.felix.gogo.runtime;version='[0.16.2,0.16.3)',\ org.apache.felix.log;version='[1.0.1,1.0.2)',\ org.apache.felix.scr;version='[2.0.2,2.0.3)',\ org.eclipse.equinox.metatype;version='[1.4.100,1.4.101)',\ org.osgi.service.metatype;version='[1.3.0,1.3.1)',\ osgi.enroute.examples.eval.command;version='[1.0.0,1.0.1)',\ osgi.enroute.examples.eval.parsii.provider;version='[1.0.0,1.0.1)',\ org.apache.felix.gogo.shell;version='[2.0.0,2.0.1)',\ org.apache.felix.http.jetty;version='[3.2.0,3.2.1)',\ org.apache.felix.http.servlet-api;version='[1.1.2,1.1.3)',\ org.eclipse.equinox.coordinator;version='[1.3.100,1.3.101)',\ org.eclipse.equinox.event;version='[1.3.100,1.3.101)',\ org.osgi.service.event;version='[1.3.1,1.3.2)',\ osgi.enroute.configurer.simple.provider;version='[2.0.0,2.0.1)',\ osgi.enroute.dtos.bndlib.provider;version='[2.0.0,2.0.1)',\ osgi.enroute.examples.eval.application;version='[1.0.0,1.0.1)',\ osgi.enroute.executor.simple.provider;version='[1.0.0,1.0.1)',\ osgi.enroute.google.angular.webresource;version='[1.5.7,1.5.8)',\ osgi.enroute.logger.simple.provider;version='[2.0.0,2.0.1)',\ osgi.enroute.rest.simple.provider;version='[2.0.0,2.0.1)',\ osgi.enroute.twitter.bootstrap.webresource;version='[3.3.5,3.3.6)',\ osgi.enroute.web.simple.provider;version='[2.0.0,2.0.1)' ... |
将新的-runbundles添加到bndrun文件中,然后再次运行mvn install命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
bndrun $ vi osgi.enroute.examples.eval.bndrun // 更新-runbundles bndrun $ mvn install ... bndrun $ java -jar osgi.enroute.examples.eval.jar 2016-10-04 11:44:50.960:INFO::main: Logging initialized @673ms 2016-10-04 11:44:50.989:WARN:oejs.session:main: Sessions created by this manager are immortal (default maxInactiveInterval={})0 2016-10-04 11:44:50.992:INFO:oejs.Server:main: jetty-9.3.8.v20160314 2016-10-04 11:44:51.029:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler@1757cd72{/,null,AVAILABLE} 2016-10-04 11:44:51.030:INFO:oejs.Server:main: Started @742ms 2016-10-04 11:44:51.049:INFO:oejs.ServerConnector:main: Started ServerConnector@68c72235{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} ____ _ ___ _ __ | _ \ ___ _ _| |_ ___ / _ \ '_ \| |_) / _ \| | | | __/ _ \ | __/ | | | _ < (_) | |_| | |_ __/ \___|_| |_|_| \_\___/ \__,_|\__\___| http://enroute.osgi.org/ G! |
Gogo的输出和Log将会显示出来。
我们现在可以打开浏览器访问http://localhost:8080/osgi.enroute.examples.eval,将会看到我们的应用程序的home页面,包含一个用来调用表达式计算器的文本输入框。
Index
如果你查看webserver的root,将会获取到一个空页面,活动状态的web应用可以被列在这个页面上,要启用这个功能,我们需要在应用程序工程中添加一些元数据。
1 2 3 |
bndrun $ cd ../application application $ vi bnd.bnd // 增加下面的内容 |
1 2 3 4 5 6 |
Bundle-Description: \ Provides a Web based application to evaluate the evaluator EnRoute-Application: osgi.enroute.examples.eval -includeresource: \ {static/osgi.enroute.examples.eval/index.html=\ src/main/resources/static/osgi.enroute.examples.eval/index.html} |
如果我们现在再次进行构建并运行,则应该能看到在home页面上列出了应用程序清单。
1 2 3 4 5 6 |
application $ mvn install ... application $ cd ../bndrun bndrun $ mvn install bndrun $ java -jar osgi.enroute.examples.eval.jar ... |
如果没有其他的东西被注册在root中,则这个列表会由Web Server创建。这个列表看起来如下所示:
点击其中的链接,你将会在浏览器中看到应用运行情况。我们可以在浏览器中访问 http://localhost:8080来进行测试。


from: 新闻快报(2018-08-12) – OSGi enRoute 全部上线 | JavaFX、OSGi、Eclipse开源资料