LoginSignup
19
9

More than 5 years have passed since last update.

Javaで書かれたSpring BootプロジェクトをKotlin対応した話

Last updated at Posted at 2017-12-16

背景

NORELチームのエンジニア間で使用技術のモダナイズを進めており、「Java / SpringBootで書かれたシステムをどうせならKotlinで書きたいよね」という話が出てきたので、Kotlinで開発できるように対応を進めています。

最初からKotlinで始める(Gradle)といった記事はよく見かけますが、Javaで書かれている途中からKotlinに対応する(Maven)という記事があまり見つからなかったので、手探りで対応していきました。

既存の環境

  • macOS High Sierra
  • JDK version => 1.8
  • Spring Boot => 1.5.8
  • Lombokを使用している
  • ビルドシステム => Maven
  • IntelliJ派とSTS派がいる
    • 私は前者です。

基本的にはSpringBootではREST APIのみを実装しています。

Kotlin対応させる方針

  • 並行でJavaでの開発が進んでいるため、全コードをJavaからKotlinに変換するのは今回は見送り
    • Controller / ServiceはKotlinで書けるようにする
    • JPAなどのデータアクセス層はJavaのまま残しておく
    • 最終的には全部Kotlinにしたい

また、今回の作業はIntelliJ上で行いました。

移行作業

pom.xmlの設定

※今回の投稿に関係ある部分を中心に記載しています。

  • Kotlinバージョンの定義
pom.xml
  <properties>
...
     <java.version>1.8</java.version>
+    <kotlin.version>1.2.10</kotlin.version>
+    <kotlin.compiler.incremental>true</kotlin.compiler.incremental>
  </properties>
  • Kotlinの依存ライブラリを追加
pom.xml
  <dependencies>
・・・
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-stdlib</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-reflect</artifactId>
+      <version>${kotlin.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.jetbrains.kotlin</groupId>
+      <artifactId>kotlin-test</artifactId>
+      <version>${kotlin.version}</version>
+      <scope>test</scope>
+    </dependency>
  </dependencies>
  • ビルド設定

元々maven-compiler-pluginは使用していませんでしたが、JavaとKotlinのどちらでも開発ができるように、パッケージをそれぞれ

  • src/main/java
  • src/main/kotlin
  • scr/test/java
  • src/test/kotlin

で分けため、maven-compiler-pluginを使用しています。

また、Springで使用する各種アノテーションを使用できるようにするため、compilerPluginsとしてspringを定義しています。
ドキュメントにも記載されていますが、springプラグインを定義するとall-openの定義は不要になるようです。

pom.xml
  <build>
     <finalName>${project.name}</finalName>
-    <sourceDirectory>src/main/java</sourceDirectory>
-    <testSourceDirectory>src/test/java</testSourceDirectory>
    <resources>
      <resource>
        <directory>${resources.directory}</directory>
      </resource>
      <resource>
        <directory>src/main/resources</directory>
      </resource>
     </resources>
     <testResources>
       <testResource>
         <directory>src/test/resources</directory>
       </testResource>
     </testResources>
     <plugins>
       <plugin>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-maven-plugin</artifactId>
         <configuration>
           <executable>true</executable>
         </configuration>
       </plugin>
+      <plugin>
+        <artifactId>kotlin-maven-plugin</artifactId>
+        <groupId>org.jetbrains.kotlin</groupId>
+        <version>${kotlin.version}</version>
+        <configuration>
+          <compilerPlugins>
+            <plugin>spring</plugin>
+          </compilerPlugins>
+        </configuration>
+        <executions>
+          <execution>
+            <id>compile</id>
+            <goals> <goal>compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
+                <sourceDir>${project.basedir}/src/main/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+          <execution>
+            <id>test-compile</id>
+            <goals> <goal>test-compile</goal> </goals>
+            <configuration>
+              <sourceDirs>
+                <sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
+                <sourceDir>${project.basedir}/src/test/java</sourceDir>
+              </sourceDirs>
+            </configuration>
+          </execution>
+        </executions>
+        <dependencies>
+          <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-maven-allopen</artifactId>
+            <version>${kotlin.version}</version>
+          </dependency>
+        </dependencies>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <executions>
+          <!-- Replacing default-compile as it is treated specially by maven -->
+          <execution>
+            <id>default-compile</id>
+            <phase>none</phase>
+          </execution>
+          <!-- Replacing default-testCompile as it is treated specially by maven -->
+          <execution>
+            <id>default-testCompile</id>
+            <phase>none</phase>
+          </execution>
+          <execution>
+            <id>java-compile</id>
+            <phase>compile</phase>
+            <goals> <goal>compile</goal> </goals>
+          </execution>
+          <execution>
+            <id>java-test-compile</id>
+            <phase>test-compile</phase>
+            <goals> <goal>testCompile</goal> </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
  </build>

これでIntelliJ上でKotlinを書く準備が整いました。

STS(Eclipse)の対応

ここまでの対応で普段STSを使用しているメンバーに確認してもらったところ、うまく動作せずに少しハマったのでSTSでの対応方法も記載します。

  • EclipseマーケットプレイスからKotlin Plugin for Eclipseをインストールします。

  • src/main/kotlinがビルドパスとして認識されていないので、Projects > Properties > Java Build PathのSourceからAdd Fileでkotlinのフォルダをチェック。
    src/test/kotlinも同様

buildPath.png

  • プロジェクトを右クリックし、Configure Kotlin > Add Kotlin Natureを実行

下図では、既にAdd Kotlin Nature済みのため選択できなくなっていますが、実行前であれば選択できます。

AddKotlinNature.png

これでSTS上でもコンパイルが通り、Kotlinでの開発が行えるようになりました。

Kotlinを書く

JavaコードからKotlinコードへコンバート

IntelliJのコンバート機能が優秀なので、基本的にはIntelliJでコンバートしました。
一気にまとめては怖いので、1コードもしくは数コードづつ変換しました。

変換したいコードを選択して、Code > Convert Java File to Kotlin FileでKotlinのコードにコンバートします。

ConvertToKotlin.png

※たまにおかしな変換をするので都度手で修正します。

また、パッケージがsrc/main/java配下のままであるため、src/main/kotlin配下の同パッケージ以下に移動します。
これもIntelliJでRefactor > Move...でいい感じにリファクタリングしてくれます。

DI(Autowiredなど)

たとえば以下のコードは

@Service
public class HogeService {
    @Autowired
    private HogeRepository hogeRepository;
}

Kotlinではlateinitでも宣言できますが、今回はconstructor injectionで定義するようにしました。

@Service
class HogeService(
    private val hogeRepository: HogeRepository
) {
}

Lombokまわり

Lombokを使っていると、Kotlinにコンバートした際にコンパイルエラーが出ることがありました。

基本的には、Kotlinにコンバートするクラス、Kotlinから参照するクラスは、Delombokすることで解消させました。

  • Delombok

IntelliJのlombokプラグインを使用します。
Refactor > DelombokからLombok化を解除してくれます。

Delombok.png

  • Slf4jアノテーション

loggerの宣言を省略して、いきなりログ出力するコードを書くことができます。
Kotlinにした場合、以下のコードでlog変数が解決できずコンパイルエラーとなってしまいます。

@Service
@Slf4j
public class HogeService {
    public void hoge() {
        log.info("hoge");
    }
}

kotlinではcompanion objectでロガーを初期化することで利用できるようになります。

@Service
class HogeService {
    companion object {
        private val log = LoggerFactory.getLogger(HogeService::class.java)
    }
}

アノテーションでのDIの方法などを試されている方もいたので、楽にできるよう検討したいと思います。
http://saiya-moebius.hatenablog.com/entry/2017/11/08/033932

  • Dataアノテーション

以下のようなLombok化されたJavaのコードをKotlinから読み、hogeを取得しようとしてもprivateでアクセスでいないと言われてしまいます。
この場合、上に書いたようにDelombok化して対応しました。
(全部KotlinになればそもそもLombok使わなくても・・)

@Entity
@Data
@EqualsAndHashCode(callSuper = false)
@ToString(callSuper = true)
public class Hoge extends AbstractEntity {
    @Column(name = "hoge")
    private String hoge;
}

今後について

現時点で一部のコードはKotlin化して動作するようになりました。
しかし、実際にコードを書いている途中で問題が起こることが何度かあったので、今後も問題が発生する可能性があります。
そこでの対処方などはどんどん追記していければなと思います。

※Kotlin楽しい!!

19
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
9