Groovy学习笔记之Jetty+Docker实现快速原型
实现一个快速原型是一个苦逼的全栈程序员经常碰到的事情,对于使用快速原型进行迭代开发,最可怕的情况就是80%的时间花在了原型的框架选型开发环境部署上,而20%的时间才用在解决和实现业务上。这是有多低的效率,可能是仅次于去机关办证的低效率了吧!而浪费的时间远远大于坐马桶上刷手机微博所消耗的时间总和。
今天,我们要在使用Jetty + Docker快速实现和部署一个能显示随机正态分布的页面,非常简单,最终效果如下:
图中的钟型曲线是使用Java的Random.nextGaussian产生的。用户可以在页面上输入限制最大值、最小值、数学期望值、标准差和样本总量并由这些参数生成正态分布图。
Jetty服务器
要实现这个快速原型,首先假设你已经在自己的机器上安装了Java,Groovy和Docker。
上述各软件安装好了之后,我们需要写一个Groovy脚本来跑Jetty,首先新建一个文件夹,把这个文件夹作为这个原型的根目录。
然后在原型根目录中新建app.groovy作为程序入口。这个脚本主要的任务就是为我们启动Jetty服务器,内容如下:
@Grab('org.eclipse.jetty.aggregate:jetty-server')
@Grab('org.eclipse.jetty.aggregate:jetty-servlet')
@Grab('javax.servlet:javax.servlet-api')
import groovy.servlet.GroovyServlet
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.servlet.DefaultServlet
import org.eclipse.jetty.servlet.ServletContextHandler
def server = new Server(8080)
def context = new ServletContextHandler(server, '/', ServletContextHandler.SESSIONS)
context.with {
resourceBase = 'webroot' // 使用webroot文件夹作为根目录
addServlet(DefaultServlet, '/') // 挂入DefaultServlet为*.groovy脚本以外的文件提供访问
addServlet(GroovyServlet, '*.groovy') // Groovy脚本用的Servlet
welcomeFiles = ['index.groovy'] // welcome文件设置为index.groovy,此处Optional
}
server.start()
简单说明一下这个脚本的作用。
首先我们用@Grab抓取需要的jar包,完了之后导入一些Servlet和Server,然后把这些Servlet挂到端口为8080的Jetty Server上,最后启动Jetty。
服务器有了,接下来我们需要写正态分布的页面了。
正态分布
在原型根目录下新建webroot,在其中新建一个脚本,名字随意取,在这里我们取名叫test1.groovy。我不准备在这里贴这个脚本的全部代码了,你可以在这个项目里面看到源代码。我准备首先说一下算法的基本思路,然后贴一下怎么使用markupBuilder生产html页面配合chart.js生成钟图。
随机正态分布数据的收集
Java的java.util.Random
中自带一个能生成数学期望(u)是0、标准差(a)是1的近似随机标准正态分布的随机方法,叫做nextGaussian
,我们用它来生成随机数。
由标准正态分布的特性可知,99%以上的数值落在(u-2.58a, u+2.58a)区间中,由于标准正态分布u = 0, a = 1所以我们可以假定nextGaussian
方法生成的双精度值范围在-2.58 ~ +2.58之间,我们要做的只是对这个结果值做一下线性平移,然后收集结果即可。
譬如,我们可以把nextGaussian
产生的结果a(99%以上落于(-2.58 ~ +2.58)区间)乘以100,然后截取整数位,放入一个map做key,value设置成1。下次再随机到这个值时,map中的value + 1。然后用样本总量值作为循环次数收集产生的随机值。
收集完成后,将map.key进行升序排序,然后map.key的第一个值便是随机生成的最小值min,map.key的最后一个值是最大值max。
之后用Groovy的Range生成从min到max的范围,针对范围中每一个值i,map[i]便可以取得该值出现过的次数。我们可以用'='*map[i]
打印到Console中,就能很形象地看到一个正态分布图像了。
废话不多说,看代码吧
def random = new Random()
def map = [:] // 收集结果的map
10000.times { // 样本总量10000,循环10000次
int gaussian = (int) (random.nextGaussian() * 100) // 放大100倍,大部分随机数应该落在(-258 ~ +258)范围内,小数点截断。
map[gaussian] = map[gaussian] ? map[gaussian] + 1 : 1 // 计数
}
def sorted = map.keySet().sort() // 排序
(sorted[0]..sorted[-1]).each {
println it.toString().padLeft(3, ' ') + '=' * (map[it]?:0) // 打印结果
}
运行一下,你应该能看到console上打印出下面这坨东西(太长,截取部分)
...
-20======================================
-19============================================
-18===================================
-17====================================
-16=================================================
-15==========================================
-14======================================
-13=======================================
-12===============================================
-11=============================================
-10===================================
-9========================================
-8=========================================================
-7===============================================
-6====================================
-5===============================
-4=============================================
-3================================================
-2====================================
-1=====================================================
0========================================================================
1=======================================
2========================================
3==============================================
4==============================================
5===========================================
6========================================
7=======================================
8=============================================
9============================
10========================================
11==============================
12===================================
13============================
14==========================================
15=================================
16========================================
17=========================================
18========================================
19==============================
20=================================
...
如果你把它打印成txt文件(用groovy test.groovy > out.txt
),缩小后能看到
看到没,近似正态分布了吧。就是console中比较丑,没事儿,接下来我们把上面的概念想办法搞到html页面上去。
test1.groovy脚本中代码虽然不是完全按照上面的来,但是思路是一样的。具体代码可以看这里。
GroovyServlet
好了,正态分布的数据有了,接下来要可视化。我们准备使用chart.js的曲线图来显示。chart.js的曲线图实现起来比较简单,只要提供data和options(可忽略),把canvas包到Chart类中去即可,具体可以参考这里的中文文档。
好了,有了chart.js的加持,我们只要生成html和json的数据即可了。首先我们来看看怎么生成html。
你可以把test1.groovy看做一个servlet,如果你查看groovy.servlet.ServletBinding
,可以看到在GroovyServlet中,已经默认绑定了以下几个变量供我们调遣
Eager variables
- “request” : the HttpServletRequest object
- “response” : the HttpServletRequest object
- “context” : the ServletContext object
- “application” : same as context
- “session” : shorthand for request.getSession(false) - can be null!
- “params” : map of all form parameters - can be empty
- “headers” : map of all request header fields
Lazy variables
- “out” : response.getWriter()
- “sout” : response.getOutputStream()
- “html” : new MarkupBuilder(response.getWriter()) - expandEmptyElements flag is set to true
- “json” : new JsonBuilder()
Methods
- “forward(String path)” : request.getRequestDispatcher(path).forward(request, response)
- “include(String path)” : request.getRequestDispatcher(path).include(request, response)
- “redirect(String location)” : response.sendRedirect(location)
有没有jsp中的隐含变量的感觉?OK,里面有个叫html的MarkupBuilder,好了我们用它来生成html。咱先把chart.js的javascript和form表单部分省略了吧。
html.html {
head {
title 'Gaussian Distribution Test'
script src:'//cdn.bootcss.com/Chart.js/1.0.2/Chart.min.js'
}
body {
h1 '(伪)正态分布研究'
canvas id:'myChart', width: 800, height: 500
script {
// TODO 在这里生成chart.js的javascript代码
}
}
}
会生成以下html页面源码
<html>
<head>
<title>Gaussian Distribution Test</title>
<script src='//cdn.bootcss.com/Chart.js/1.0.2/Chart.min.js'></script>
</head>
<body>
<h1>(伪)正态分布研究</h1>
<canvas id='myChart' width='800' height='500'></canvas>
<script>
// 等下chart.js的javascript代码会在这里生成。这段注释不会由markupBuilder生成。
</script>
</body>
</html>
好了,html的框架有了,接下来我们要把之前算出来的随机数结果改造成chart.js能用的数据格式然后显示,我们来看看怎么做:
def map = [:]
// ...省略正态分布的随机算法...
// 假设你到这里已经把所有结果都放在了map里
// 对结果key进行排序
def keys = map.keySet().sort()
// 生成最小到最大值的list提供给chart.js的labels用
def dataLabels = (keys[0]..keys[-1]).collect { it.toString() }
// 生成labels对应的数据list
def dataList = (keys[0]..keys[-1]).collect { map[it]?:0 }
html.html {
// ...省略head部分..
body {
h1 '(伪)正态分布研究'
canvas id:'myChart', width: 800, height: 500
script {
mkp.yield 'var data =' // mkp.yield会吧参数直接打印到out中去
json { // 此处开始使用JsonBuilder生成json对象
labels dataLabels // 之前生成的dataLabels对象
datasets ([[
fillColor : "rgba(151,187,205,0.5)",
strokeColor : "rgba(151,187,205,1)",
pointColor : "rgba(151,187,205,1)",
pointStrokeColor : "#fff",
data : dataList // 之前生成的dataList对象
]])
}
// mkp.yieldUnescaped和yield一样,只是不escape了(这不是废话嘛)
mkp.yieldUnescaped '''
var ctx = document.getElementById("myChart").getContext("2d");
new Chart(ctx).Line(data);
'''
}
}
}
完成!浏览器访问http://localhost:8080/test1.groovy
,塔拉~~~
哦,form提交什么的很简单的就不再赘述啦请戳这里看源代码。
部署
部署Groovy其实是个很简单的事情,只要你有Java环境然后安装上Groovy,然后用groovy运行即可。
什么?你说这没有一点部署服务器的庄重感?感觉太简单不能完成你的需求?怕运维人员失业?好吧,那我们就用Docker部署吧,因为很简单,写个Dockerfile就行了。
# 使用openjdk-8
FROM java:openjdk-8-jdk
# 安装wget和unzip
RUN apt-get update && \
apt-get -y install wget unzip && \
apt-get clean
# 设定环境变量
ENV GROOVY_VERSION=2.4.5
ENV JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64 \
GROOVY_HOME=/opt/groovy-${GROOVY_VERSION}
ENV PATH=$GROOVY_HOME/bin/:$JAVA_HOME/bin:$PATH
# Install groovy
ADD http://dl.bintray.com/groovy/maven/apache-groovy-binary-${GROOVY_VERSION}.zip /tmp/
RUN unzip -d /opt/ /tmp/apache-groovy-binary-${GROOVY_VERSION}.zip \
&& rm /tmp/apache-groovy-binary-${GROOVY_VERSION}.zip
# 复制代码
ADD ./src/ /groovyApp
EXPOSE 8080
WORKDIR /groovyApp
# 运行groovy
ENTRYPOINT ["groovy", "app.groovy"]
Docker源文件可以在这里看到。
OK,然后在docker环境中运行:(如果你是docker-machine的话,就是mac或者windows的docker,就是在Docker Quickstart Terminal
中,对就是头上有个金鱼鲸鱼的那个终端)
docker build -t GaussianRandDemo .
等待Docker下载并构建成功后运行
docker run -d -p 8080:8080 GaussianRandDemo
浏览器访问http://localhost:8080, 如果是docker-machine环境请访问http://192.168.99.100:8080
塔拉~~~完成。
哦对了,首次运行Groovy需要下载依赖包, 可能需要点时间, 请耐心等候.
好了,现在,你有了一个app,有可以通过命令行直接运行的源代码(命令行运行groovy app.groovy
),还有了可以通过registry push到生产环境的docker image,怎么?还不满意?没事儿,你还可以用maven、gradle这种构建工具来编译打包groovy项目,当然,还可以用spring CLI直接运行groovy脚本,或者一键打包成可执行jar或可部署war,可选项太多了。
对了,还有数据库相关的都还没说呢。
且听下回分解。
- 完 -
