天天看點

《Spring Boot極簡教程》第9章 Spring Boot內建Scala混合Java開發第9章 Spring Boot內建Scala混合Java開發參考資料

第9章 Spring Boot內建Scala混合Java開發

本章我們使用Spring Boot內建Scala混合Java開發一個Web性能測試平台。

使用到的核心技術:

後端:

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

前端:

  • jquery
  • bootstrap
  • adminLTE
  • html/css

建立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>
                <!--<configuration>-->
                <!--<fork>true</fork><!– 如果沒有該項配置,肯呢個devtools不會起作用,即應用不會restart –>-->
                <!--</configuration>-->
            </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>
        <!--<ol class="breadcrumb">-->
        <!--<li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li>-->
        <!--<li class="active">Dashboard</li>-->
        <!--</ol>-->
    </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應用的開發執行個體。

Scala是一門JVM上的語言。它精心整合了面向對象和函數式程式設計語言,支援面向對象程式設計範式,支援函數式程式設計範式,文法動态簡潔表達力豐富,具備靜态強類型和豐富的泛型。

參考資料

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