Статьи

Ваш инструмент сборки — ваш хороший друг: что sbt может сделать для разработчика Java

Я думаю, что для разработчиков выбор правильного инструмента сборки — очень важный выбор. В течение многих лет я придерживался Apache Maven и, честно говоря, он выполняет свою работу достаточно хорошо, даже сейчас это хороший инструмент для использования. Но я всегда чувствую, что это можно сделать намного лучше … и тогда появился Градл

Несмотря на то, что я потратил много часов на то, чтобы привыкнуть к Gradle , я наконец сдался и вернулся к Apache Maven . Причина — я не чувствовал себя комфортно с этим, в основном из-за Groovy DSL. В любом случае, я думаю, что Gradle — это отличный, мощный и расширяемый инструмент для сборки, который способен выполнять любые задачи, которые нужны вашему процессу сборки.

Но все больше и больше занимаясь Scala , я быстро обнаружил, что кто-то другой . Хотя sbt является аббревиатурой от « простого инструмента сборки », мое первое впечатление было совершенно противоположным: я нашел его сложным и трудным для понимания. По некоторым причинам, мне все же понравилось, и, проводя больше времени за чтением документации (которая становится все лучше и лучше), во многих экспериментах, я бы, наконец, сказал, что выбор сделан. В этой статье я хотел бы показать пару замечательных вещей, которые sbt может сделать, чтобы облегчить жизнь Java-разработчику (некоторые знания Scala были бы очень полезны, но это не обязательно).

Прежде чем перейти к реальному примеру, пара фактов о sbt . Он использует Scala в качестве языка для сценария сборки и требует запуска, который можно скачать отсюда (версия, которую мы будем использовать, — 0.13.1 ). Есть несколько способов описать сборку в sbt , один из которых демонстрирует этот пост, это использование Build.scala с одним проектом.

Наш пример — это простое консольное приложение Spring с парой тестовых примеров JUnit : этого достаточно, чтобы увидеть, как строится сборка с внешними зависимостями и выполняются тесты. Приложение содержит только два класса:

01
02
03
04
05
06
07
08
09
10
package com.example;
 
import org.springframework.stereotype.Service;
 
@Service
public class SimpleService {
    public String getResult() {
        return "Result";
    }
}

и

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package com.example;
 
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.GenericApplicationContext;
 
public class Starter {
    @Configuration
    @ComponentScan( basePackageClasses = SimpleService.class )
    public static class AppConfig { 
    }
 
    public static void main( String[] args ) {
        try( GenericApplicationContext context = new AnnotationConfigApplicationContext( AppConfig.class ) ) {
            final SimpleService service = context.getBean( SimpleService.class );
            System.out.println( service.getResult() );
        }
    }
}

Теперь давайте посмотрим, как выглядит сборка sbt . По договоренности Build.scala должен находиться в подпапке проекта . Кроме того, должен присутствовать файл build.properties с желаемой версией sbt и plugins.sbt с внешними плагинами (мы будем использовать плагин sbteclipse для генерации файлов проекта Eclipse ). Начнем с build.properties, который содержит только одну строку:

1
sbt.version=0.13.1

и продолжим с plugins.sbt , который в нашем случае тоже всего одна строка:

1
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")

Наконец, давайте начнем с сердца нашей сборки: Build.scala . В нем будет две части: общие настройки для всех проектов в нашей сборке (полезно для многопроектных сборок, но сейчас у нас есть только одна), и вот фрагмент этой части:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import sbt._
import Keys._
import com.typesafe.sbteclipse.core.EclipsePlugin._
 
object ProjectBuild extends Build {
  override val settings = super.settings ++ Seq(
    organization := "com.example",   
    name := "sbt-java",   
    version := "0.0.1-SNAPSHOT",   
 
    scalaVersion := "2.10.3",
    scalacOptions ++= Seq( "-encoding", "UTF-8", "-target:jvm-1.7" ),   
    javacOptions ++= Seq( "-encoding", "UTF-8", "-source", "1.7", "-target", "1.7" ),       
    outputStrategy := Some( StdoutOutput ),
    compileOrder := CompileOrder.JavaThenScala,
 
    resolvers ++= Seq(
      Resolver.mavenLocal,
      Resolver.sonatypeRepo( "releases" ),
      Resolver.typesafeRepo( "releases" )
    ),       
 
    crossPaths := false,           
    fork in run := true,
    connectInput in run := true,
 
    EclipseKeys.executionEnvironment := Some(EclipseExecutionEnvironment.JavaSE17)
  )
}

Вышеприведенная сборка выглядит достаточно чистой и понятной: resolvers — это прямая аналогия репозиториев Apache Maven , EclipseKeys.executionEnvironment — это настройка среды выполнения (Java SE 7) для сгенерированного проекта Eclipse . Все эти ключи очень хорошо документированы .

Вторая часть намного меньше и определяет наш основной проект с точки зрения зависимостей и основного класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
lazy val main = Project(
  id = "sbt-java"
  base = file("."),
  settings = Project.defaultSettings ++ Seq(             
    mainClass := Some( "com.example.Starter" ),
 
    initialCommands in console += """
      import com.example._
      import com.example.Starter._
      import org.springframework.context.annotation._
    """,
 
    libraryDependencies ++= Seq(
      "org.springframework" % "spring-context" % "4.0.0.RELEASE",
      "org.springframework" % "spring-beans" % "4.0.0.RELEASE",
      "org.springframework" % "spring-test" % "4.0.0.RELEASE" % "test",
      "com.novocode" % "junit-interface" % "0.10" % "test",
      "junit" % "junit" % "4.11" % "test"
    )
  )
)

InitialCommands требует небольшого пояснения: sbt может запускать консоль Scala (REPL), и этот параметр позволяет добавлять стандартные операторы импорта, чтобы мы могли немедленно использовать наши классы. Зависимость от junit-интерфейса позволяет sbt запускать тестовые случаи JUnit, и это первое, что мы сделаем: добавим несколько тестов. Перед созданием реальных тестов мы запустим sbt и попросим его запускать тестовые примеры при каждом изменении кода, вот так:

1
sbt ~test

Пока sbt работает, мы добавим тестовый пример:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.example;
 
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
 
import com.example.Starter.AppConfig;
 
public class SimpleServiceTestCase {
    private GenericApplicationContext context;
    private SimpleService service;
 
    @Before
    public void setUp() {
        context = new AnnotationConfigApplicationContext( AppConfig.class );
        service = context.getBean( SimpleService.class );
    }
 
    @After
    public void tearDown() {
        context.close();
    }
 
    @Test
    public void testSampleTest() {
        assertThat( service.getResult(), equalTo( "Result" ) );
    }
}

В консоли мы должны увидеть, что sbt автоматически выбрал изменение и запустил все тестовые случаи. К сожалению, из-за этой проблемы, которая уже исправлена ​​и должна быть доступна в следующей версии junit-интерфейса , мы не можем пока использовать аннотации @RunWith и @ContextConfiguration для запуска тестовых случаев Spring .

sbt.test

Для практиков TDD это потрясающая возможность. Следующая потрясающая функция, которую мы собираемся рассмотреть, это консоль Scala (RELP), которая дает возможность играть с приложением, фактически не запуская его. Это может быть вызвано, набрав:

1
sbt console

и наблюдаем что-то подобное в терминале (как мы видим, импорт из initialCommands автоматически включается):

sbt.console

На данный момент игровая площадка создана, и мы можем сделать много очень интересных вещей, например: создать контекст, получить bean-компоненты и вызвать любые методы для них:

sbt.console.2

sbt заботится о classpath, поэтому все ваши классы и внешние зависимости доступны для использования. Я нашел этот способ обнаружить вещи намного быстрее, чем с помощью отладчика или других методов.

На данный момент в Eclipse нет хорошей поддержки sbt, но очень просто сгенерировать файлы проекта Eclipse с помощью плагина sbteclipse, к которому мы обращались ранее:

1
sbt eclipse

Потрясающие! Не говоря уже о других замечательных плагинах, которые любезно перечислены здесь, и о возможности импортировать POM-файлы Apache Maven с помощью externalPom (), который действительно упрощает миграцию. В заключение, с моей стороны, если вы ищете лучший, современный, расширяемый инструмент для сборки вашего проекта, пожалуйста, посмотрите на sbt . Это отличный кусок программного обеспечения, созданного на основе удивительного, продуманного языка.

  • Полный проект доступен на GitHub .