8.3 Spring Boot集成Scala混合Java开发

8.3 Spring Boot集成Scala混合Java开发

本章我们使用Spring Boot集成Scala混合Java开发一个Web性能测试平台。

使用到的相关技术:

后端:

  • phantomjs
  • scala
  • java
  • springboot
  • velocity
  • jpa
  • maven
  • mysql

前端:

  • jquery
  • bootstrap
  • adminLTE
  • html/css

Scala是一门JVM上的语言。它精心整合了面向对象和函数式编程语言,支持面向对象编程范式,支持函数式编程范式,语法动态简洁表达力丰富,具备静态强类型和丰富的泛型。

新建maven工程,配置pom

添加SpringBoot parent依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.5.RELEASE</version>
    </parent>

因为我们要使用scala + java混合开发,添加scala依赖

<!-- scala -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

然后,我们使用velocity模板引擎,数据库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>

构建周期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構建過程中,使用一個編譯scala代碼的maven插件.這是typesafe(scala背後的公司)的工程師Josh Suereth開發的,遵循maven插件開發規範.

然後,org.scala-lang:scala-library是Scala應用運行時的依賴.這樣,我們就可以像使用scala來開發SpringBoot应用了。

构建标准maven工程目录

工程目录如下:

配置数据库

我们数据库使用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

写领域实体层代码

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 = _

  //用例状态: -1未执行 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中可以为类、方法、字段、局部变量和参数添加注解,与Java一样。可以同时添加多个注解,先后顺序没有影响。 在Scala中,注解可以影响编译过程,比如@BeanProperty注解。

我们使用@Entity注解标记数据库实体类LazyTask,jpa会自动对应到数据表lazy_task, 同时我们使用@BeanProperty标记实体bean里面的属性字段,jpa会自动映射到表里面的字段,自动映射对应的类型。用scala的@BeanProperty注解,会自动生成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")
  }


  /**
    * 获取一条任务记录
    *
    * @param id
    * @return
    */
  @RequestMapping(value = {
    Array("/lazytask")
  }, method = Array(RequestMethod.GET))
  @ResponseBody
  def findOne(@RequestParam(value = "id") id: Integer) = lazyTaskDao.findOne(id)

  /**
    * 获取一条任务记录的返回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


  /**
    * 执行任务
    * @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)


}

前端代码

对应的前端模板代码我们放在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性能测试平台</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">新增任务</h3>
                </div>

                <form class="box-body" id="newTaskForm">
                    <div class="form-group">
                        <label>任务名称</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">任务列表</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>运行次数</th>
                            <th>更新时间</th>
                            <th>执行结果</th>
                            <th>操作</th>
                            <th>运行状态</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="执行中"
                                        class='btn-sm btn-success text-center'
                                        autocomplete="off"
                                        onclick='runTest($t.id,this)'>运行
                                </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;

        /**
         * 运行任务
         */
        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;">执行成功</p>');

                } else {
                    $(thisBtn).button('reset');
                    $(resultId).html('<p style="color:red;font-size: 12px;">执行失败</p>');

                }
            }
        });

    }

    $(function () {

        $('#newTaskBtn').on('click', function () {
            var data = $('#newTaskForm').serialize();
            var url = 'ylazy/newTask';
            //新增任务
            $.ajax({
                url: url,
                data: data,
                type: 'POST',
                success: function (result) {
                    if (result) {
                        BootstrapDialog.show({
                            title: '新增任务',
                            message: '响应结果:' + JSON.stringify(result, null, 2),
                            type: BootstrapDialog.TYPE_SUCCESS,
                            closable: false,
                            cssClass: 'dialog_mar',
                            buttons: [{
                                label: '确认',
                                cssClass: 'con_btn',
                                action: function (dialogRef) {
                                    dialogRef.close();
                                    location.reload();
                                }
                            }, {
                                label: '取消',
                                action: function (dialogRef) {
                                    dialogRef.close();
                                }
                            }]
                        });

                    } else {
                        BootstrapDialog.show({
                            title: '新增任务',
                            message: '添加失败', type: BootstrapDialog.TYPE_DANGER,
                            closable: false,
                            cssClass: 'dialog_mar',
                            buttons: [{
                                label: '确认',
                                cssClass: 'con_btn',
                                action: function (dialogRef) {
                                    dialogRef.close();
                                    location.reload();
                                }
                            }, {
                                label: '取消',
                                action: function (dialogRef) {
                                    dialogRef.close();
                                }
                            }]
                        });
                    }
                }
            });

        });

        //任务列表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: {//分页的样式内容
                    previous: "上一页",
                    next: "下一页",
                    first: "第一页",
                    last: "最后"
                }
            },
            zeroRecords: "没有内容",//table tbody内容为空时,tbody的内容。
            //下面三者构成了总体的左下角的内容。
            info: "总计 _TOTAL_ 条,共 _PAGES_ 页,_START_ - _END_ ",//左下角的信息显示,大写的词为关键字。
            infoEmpty: "0条记录",//筛选为空时左下角的显示。
            infoFiltered: ""//筛选之后的左下角筛选提示
        };

        $('#dataTable').DataTable(dataTableOptions);
    })
</script>

#parse("/common/footer.html")



完整的工程源代码:

https://github.com/EasySpringBoot/ylazy

运行测试

在pom.xml所在目录,命令行运行:

mvn clean scala:compile scala:run -Dlauncher=app

浏览器访问:http://localhost:9050/list.do

你将看到如下页面:

小结

本章给出了一个使用Scala进行SpringBoot应用的开发实例。

关于SpringBoot集成Scala开发,还可以参考本书中的另外的工程实例源码:

HTTP接口测试平台:
https://github.com/EasySpringBoot/lightsword

Teda自动化用例调度执行平台:
https://github.com/EasySpringBoot/teda

参考资料

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容