Статьи

Ваш инструмент сборки — ваш хороший друг: что 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 : этого достаточно, чтобы увидеть, как строится сборка с внешними зависимостями и выполняются тесты. Приложение содержит только два класса:

package com.example;

import org.springframework.stereotype.Service;

@Service
public class SimpleService {
    public String getResult() {
        return "Result";
    }
}

и

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,  который содержит только одну строку:

sbt.version=0.13.1

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

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

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

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 MavenEclipseKeys.executionEnvironment  — это настройка среды выполнения (Java SE 7) для сгенерированного   проекта Eclipse . Все эти ключи очень хорошо  документированы .

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

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  и попросим его запускать тестовые примеры при каждом изменении кода, вот так:

sbt ~test

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

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 .

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

sbt console

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

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

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

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

sbt eclipse

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

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