8.3 Spring Boot集成Scala混合Java開發(fā)
本章我們使用Spring Boot集成Scala混合Java開發(fā)一個(gè)Web性能測(cè)試平臺(tái)涝开。
使用到的相關(guān)技術(shù):
后端:
- phantomjs
- scala
- java
- springboot
- velocity
- jpa
- maven
- mysql
前端:
- jquery
- bootstrap
- adminLTE
- html/css
Scala是一門JVM上的語言。它精心整合了面向?qū)ο蠛秃瘮?shù)式編程語言拟赊,支持面向?qū)ο缶幊谭妒剑С趾瘮?shù)式編程范式匾旭,語法動(dòng)態(tài)簡潔表達(dá)力豐富划鸽,具備靜態(tài)強(qiáng)類型和豐富的泛型。
新建maven工程答憔,配置pom
添加SpringBoot parent依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
</parent>
因?yàn)槲覀円褂胹cala + java混合開發(fā),添加scala依賴
<!-- scala -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
然后掀抹,我們使用velocity模板引擎虐拓,數(shù)據(jù)庫ORM層使用jpa
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-velocity</artifactId>
</dependency>
構(gòu)建周期build插件配置:
<plugin>
<groupId>org.scala-tools</groupId>
<artifactId>maven-scala-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
<configuration>
<recompileMode>incremental</recompileMode>
<scalaVersion>${scala.version}</scalaVersion>
<launchers>
<launcher>
<id>app</id>
<mainClass>com.light.sword.ylazy.LightSwordApplication</mainClass>
<args>
<arg>-deprecation</arg>
</args>
<jvmArgs>
<jvmArg>-Xms256m</jvmArg>
<jvmArg>-Xmx2048m</jvmArg>
</jvmArgs>
</launcher>
</launchers>
</configuration>
<dependencies>
<!-- spring熱部署-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
<configuration>
<includeScope>system</includeScope>
</configuration>
</plugin>
其中build周期中的maven-scala-plugin是編譯期依賴,scala代碼需要scala的compiler,所以在maven構(gòu)建過程中,使用一個(gè)編譯scala代碼的maven插件.這是typesafe(scala背後的公司)的工程師Josh Suereth開發(fā)的,遵循maven插件開發(fā)規(guī)範(fàn).
然後,org.scala-lang:scala-library是Scala應(yīng)用運(yùn)行時(shí)的依賴.這樣,我們就可以像使用scala來開發(fā)SpringBoot應(yīng)用了。
構(gòu)建標(biāo)準(zhǔn)maven工程目錄
工程目錄如下:
配置數(shù)據(jù)庫
我們數(shù)據(jù)庫使用mysql傲武,ORM框架使用spring-jpa蓉驹,在application.properties配置如下:
#mysql
spring.datasource.url = jdbc:mysql://localhost:3306/ylazy?useUnicode=true&characterEncoding=UTF8
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.max-active=0
spring.datasource.max-idle=0
spring.datasource.min-idle=0
spring.datasource.max-wait=10000
spring.datasource.max-wait-millis=31536000
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
寫領(lǐng)域?qū)嶓w層代碼
package com.light.sword.ylazy.entity
import java.util.Date
import javax.persistence.{Entity, GeneratedValue, GenerationType, Id}
import scala.beans.BeanProperty
import scala.language.implicitConversions
@Entity
class LazyTask {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@BeanProperty
var id: Integer = _
@BeanProperty
var url: String = _
@BeanProperty
var name: String = _
//用例狀態(tài): -1未執(zhí)行 0失敗 1成功
@BeanProperty
var state: Integer = _
@BeanProperty
var owner: String = _
@BeanProperty
var resultJson: String = _
@BeanProperty
var executeTimes: Integer = _
@BeanProperty
var executor: Integer = _
@BeanProperty
var gmtCreate: Date = _
@BeanProperty
var gmtModify: Date = _
}
Scala中可以為類、方法揪利、字段态兴、局部變量和參數(shù)添加注解,與Java一樣疟位≌叭螅可以同時(shí)添加多個(gè)注解,先后順序沒有影響甜刻。 在Scala中绍撞,注解可以影響編譯過程,比如@BeanProperty注解得院。
我們使用@Entity注解標(biāo)記數(shù)據(jù)庫實(shí)體類LazyTask傻铣,jpa會(huì)自動(dòng)對(duì)應(yīng)到數(shù)據(jù)表lazy_task, 同時(shí)我們使用@BeanProperty標(biāo)記實(shí)體bean里面的屬性字段,jpa會(huì)自動(dòng)映射到表里面的字段尿招,自動(dòng)映射對(duì)應(yīng)的類型矾柜。用scala的@BeanProperty注解阱驾,會(huì)自動(dòng)生成JavaBeans的getter,setter方法就谜。
Dao層代碼
package com.light.sword.ylazy.dao
import java.util.List
import com.light.sword.ylazy.entity.LazyTask
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository
// JavaConversions
import scala.language.implicitConversions
trait LazyTaskDao extends CrudRepository[LazyTask, Integer] {
def findAll(): List[LazyTask]
def save(t: LazyTask): LazyTask
def findOne(id: Integer): LazyTask
@Query(value = "SELECT * FROM lazy_task where url like '%?1%'", nativeQuery = true)
def listByUrl(url: String): List[LazyTask]
@Query(value = "SELECT * FROM lazy_task where name like '%?1%'", nativeQuery = true)
def listByName(name: String): List[LazyTask]
}
Controller層代碼
package com.light.sword.ylazy.controller
import java.util.Date
import com.light.sword.ylazy.config.DomainConfig
import com.light.sword.ylazy.dao.LazyTaskDao
import com.light.sword.ylazy.engine.PhantomjsExecutor
import com.light.sword.ylazy.entity.LazyTask
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.ui.Model
import org.springframework.web.bind.annotation._
import org.springframework.web.servlet.ModelAndView
@RestController
class LazyTaskController @Autowired()(val lazyTaskDao: LazyTaskDao,
val phantomjsExecutor: PhantomjsExecutor,
val domainConfig: DomainConfig) {
@RequestMapping(value = {
Array("/newTask.do")
})
def newTask_do() = {
new ModelAndView("ylazy/newTask")
}
@RequestMapping(value = {
Array("/ylazy/newTask")
}, method = Array(RequestMethod.POST))
@ResponseBody
def newTask(@ModelAttribute lazyTask: LazyTask) = {
lazyTask.gmtCreate = new Date
lazyTask.gmtModify = new Date
lazyTask.executeTimes = 0
lazyTask.state = -1
lazyTaskDao.save(lazyTask)
}
@RequestMapping(value = {
Array("/list.do")
})
def list_do(model: Model) = {
model.addAttribute("lazyTaskList", lazyTaskDao.findAll())
model.addAttribute("domainName", domainConfig.getDomainName)
model.addAttribute("port", domainConfig.getPort)
new ModelAndView("ylazy/list")
}
/**
* 獲取一條任務(wù)記錄
*
* @param id
* @return
*/
@RequestMapping(value = {
Array("/lazytask")
}, method = Array(RequestMethod.GET))
@ResponseBody
def findOne(@RequestParam(value = "id") id: Integer) = lazyTaskDao.findOne(id)
/**
* 獲取一條任務(wù)記錄的返回json
*
* @param id
* @return
*/
@RequestMapping(value = {
Array("/result/{id}")
}, method = Array(RequestMethod.GET))
@ResponseBody
def findResultJson(@PathVariable(value = "id") id: Integer) = lazyTaskDao.findOne(id).resultJson
/**
* 執(zhí)行任務(wù)
* @param id
* @return
*/
@RequestMapping(value = {
Array("/ylazy/runTask")
}, method = Array(RequestMethod.GET))
@ResponseBody
def runTask(@RequestParam(value = "id") id: Integer) = {
phantomjsExecutor.ylazyById(id)
}
@RequestMapping(value = {
Array("/ylazy")
}, method = Array(RequestMethod.GET))
@ResponseBody
def ylazy(@RequestParam(value = "url") url: String) = phantomjsExecutor.ylazy(url)
}
前端代碼
對(duì)應(yīng)的前端模板代碼我們放在src/main/resources/templates/ylazy/list.html
#parse("/common/header.html")
#parse("/common/aside.html")
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
YLazy
<small>Web性能測(cè)試平臺(tái)</small>
</h1>
</section>
<section class="content">
<div class="row">
<div class="box box-success">
<div class="box-header">
<i class="fa fa-sticky-note-o"></i>
<h3 class="box-title">新增任務(wù)</h3>
</div>
<form class="box-body" id="newTaskForm">
<div class="form-group">
<label>任務(wù)名稱</label>
<input name='name' class="form-control">
</div>
<div class="form-group">
<label>URL</label>
<input name='url' class="form-control">
</div>
<div class="form-group">
<button id='newTaskBtn' type="button" class="btn-sm btn-success">
<i class="fa fa-plus"></i>
添加
</button>
</div>
</form>
</div>
</div>
<div class="row">
<div class="box box-success">
<div class="box-header">
<i class="fa fa-list-alt"></i>
<h3 class="box-title">任務(wù)列表</h3>
</div>
<div class="box-body">
<table id="dataTable"
class="table table-hover table-condensed table-responsive">
<thead>
<tr>
<th>Id</th>
<th>名稱</th>
<th>URL</th>
<th>運(yùn)行次數(shù)</th>
<th>更新時(shí)間</th>
<th>執(zhí)行結(jié)果</th>
<th>操作</th>
<th>運(yùn)行狀態(tài)</th>
</tr>
</thead>
<tbody>
#foreach ($t in $lazyTaskList)
<tr>
<td>$!t.id</td>
<td>$!t.name</td>
<td><a target="_blank" href="$!t.url">$!t.url</a></td>
<td>$!t.executeTimes</td>
#set($testTime=$!DateTool.format('yyyy-MM-dd HH:mm:ss', $t.gmtModify))
<td>$testTime</td>
<td>
<button onclick='reportDetail("$testTime","$t.url",$t.id)' type="button"
class="btn-sm btn-link">
查看
</button>
</td>
<td>
<button id='btn-$t.id'
type="button"
data-loading-text="執(zhí)行中"
class='btn-sm btn-success text-center'
autocomplete="off"
onclick='runTest($t.id,this)'>運(yùn)行
</button>
<p id="msg-$t.id"></p>
</td>
<td id="result-$t.id"></td>
</tr>
#end
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
<script>
function reportDetail(testTime, url, id) {
var detailUrl = "harviewer/index.htm?testTime=" + encodeURIComponent(testTime) +
"&testUrl=" + url +
"&path=http://$domainName:$port/result/" + id;
window.open(detailUrl, "_blank");
}
function runTest(id, thisBtn) {
$(thisBtn).button('loading');
$(thisBtn).attr('disabled', 'disabled');
var resultId = '#result-' + id;
/**
* 運(yùn)行任務(wù)
*/
var url = 'ylazy/runTask?id=' + id;
$.ajax({
url: url,
success: function (data) {
if (data) {
$(thisBtn).button('reset');
$(resultId).html('<p style="color: #00a65a;font-size: 12px;">執(zhí)行成功</p>');
} else {
$(thisBtn).button('reset');
$(resultId).html('<p style="color:red;font-size: 12px;">執(zhí)行失敗</p>');
}
}
});
}
$(function () {
$('#newTaskBtn').on('click', function () {
var data = $('#newTaskForm').serialize();
var url = 'ylazy/newTask';
//新增任務(wù)
$.ajax({
url: url,
data: data,
type: 'POST',
success: function (result) {
if (result) {
BootstrapDialog.show({
title: '新增任務(wù)',
message: '響應(yīng)結(jié)果:' + JSON.stringify(result, null, 2),
type: BootstrapDialog.TYPE_SUCCESS,
closable: false,
cssClass: 'dialog_mar',
buttons: [{
label: '確認(rèn)',
cssClass: 'con_btn',
action: function (dialogRef) {
dialogRef.close();
location.reload();
}
}, {
label: '取消',
action: function (dialogRef) {
dialogRef.close();
}
}]
});
} else {
BootstrapDialog.show({
title: '新增任務(wù)',
message: '添加失敗', type: BootstrapDialog.TYPE_DANGER,
closable: false,
cssClass: 'dialog_mar',
buttons: [{
label: '確認(rèn)',
cssClass: 'con_btn',
action: function (dialogRef) {
dialogRef.close();
location.reload();
}
}, {
label: '取消',
action: function (dialogRef) {
dialogRef.close();
}
}]
});
}
}
});
});
//任務(wù)列表datatables
var dataTableOptions = {
"bDestroy": true,
dom: 'lfrtip',
"paging": true,
"lengthChange": true,
"searching": true,
"ordering": true,
"info": true,
"autoWidth": true,
"processing": true,
"stateSave": true,
responsive: true,
fixedHeader: false,
order: [[3, "desc"]],
"aLengthMenu": [7, 10, 20, 50, 100, 200],
language: {
"search": "<div style='border-radius:10px;margin-left:auto;margin-right:2px;width:760px;'>_INPUT_ <span class='btn-sm btn-success'>搜索</span></div>",
paginate: {//分頁的樣式內(nèi)容
previous: "上一頁",
next: "下一頁",
first: "第一頁",
last: "最后"
}
},
zeroRecords: "沒有內(nèi)容",//table tbody內(nèi)容為空時(shí),tbody的內(nèi)容里覆。
//下面三者構(gòu)成了總體的左下角的內(nèi)容丧荐。
info: "總計(jì) _TOTAL_ 條,共 _PAGES_ 頁,_START_ - _END_ ",//左下角的信息顯示喧枷,大寫的詞為關(guān)鍵字虹统。
infoEmpty: "0條記錄",//篩選為空時(shí)左下角的顯示弓坞。
infoFiltered: ""http://篩選之后的左下角篩選提示
};
$('#dataTable').DataTable(dataTableOptions);
})
</script>
#parse("/common/footer.html")
完整的工程源代碼:
https://github.com/EasySpringBoot/ylazy
運(yùn)行測(cè)試
在pom.xml所在目錄,命令行運(yùn)行:
mvn clean scala:compile scala:run -Dlauncher=app
瀏覽器訪問:http://localhost:9050/list.do
你將看到如下頁面:
小結(jié)
本章給出了一個(gè)使用Scala進(jìn)行SpringBoot應(yīng)用的開發(fā)實(shí)例车荔。
關(guān)于SpringBoot集成Scala開發(fā)渡冻,還可以參考本書中的另外的工程實(shí)例源碼:
HTTP接口測(cè)試平臺(tái):
https://github.com/EasySpringBoot/lightsword
Teda自動(dòng)化用例調(diào)度執(zhí)行平臺(tái):
https://github.com/EasySpringBoot/teda