8.3 Spring Boot集成Scala混合Java開發(fā)

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

參考資料

http://www.reibang.com/p/51535e85bae5

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市忧便,隨后出現(xiàn)的幾起案子族吻,更是在濱河造成了極大的恐慌,老刑警劉巖珠增,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件超歌,死亡現(xiàn)場離奇詭異,居然都是意外死亡蒂教,警方通過查閱死者的電腦和手機(jī)巍举,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來凝垛,“玉大人懊悯,你說我怎么就攤上這事∶纹ぃ” “怎么了定枷?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長届氢。 經(jīng)常有香客問我欠窒,道長,這世上最難降的妖魔是什么退子? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任岖妄,我火速辦了婚禮,結(jié)果婚禮上寂祥,老公的妹妹穿的比我還像新娘荐虐。我一直安慰自己,他們只是感情好丸凭,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布福扬。 她就那樣靜靜地躺著,像睡著了一般惜犀。 火紅的嫁衣襯著肌膚如雪铛碑。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天虽界,我揣著相機(jī)與錄音汽烦,去河邊找鬼。 笑死莉御,一個(gè)胖子當(dāng)著我的面吹牛撇吞,可吹牛的內(nèi)容都是我干的俗冻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼牍颈,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼迄薄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起煮岁,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤噪奄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后人乓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體勤篮,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年色罚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碰缔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡戳护,死狀恐怖金抡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腌且,我是刑警寧澤梗肝,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站铺董,受9級(jí)特大地震影響巫击,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜精续,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一坝锰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧重付,春花似錦顷级、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至删掀,卻和暖如春翔冀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爬迟。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工橘蜜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菊匿,地道東北人付呕。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓计福,卻偏偏與公主長得像,于是被迫代替她去往敵國和親徽职。 傳聞我的和親對(duì)象是個(gè)殘疾皇子象颖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容