かんがるーさんの日記

最近自分が興味をもったものを調べた時の手順等を書いています。今は Spring Boot をいじっています。

Spring Boot でメール送信する Web アプリケーションを作る ( 番外編 )( IntelliJ Doma support plugin、JRebel のインストール )

概要

Community Edition をインストールしている PC に Ultimate Edition をインストールして開発環境を移行します。

今回は Doma 2 の IntelliJ Doma support plugin や JRebel をインストールします。

手順

Doma 2 の IntelliJ Doma support plugin のインストール

  1. メインメニューから「File」-「Settings...」を選択します。

  2. 「Settings」ダイアログが表示されます。画面左上の検索フィールドに "plugins" と入力した後、画面右側の「Browse repositories...」ボタンをクリックします。

    f:id:ksby:20150531145449p:plain

  3. 「Browse Repositories」ダイアログが表示されます。画面左上の検索フィールドに "Doma" と入力し、その下のリストから「Doma Support」を選択します。画面右側に「Doma Support」の情報が表示されますので、「Install plugin」ボタンをクリックします。

    f:id:ksby:20150531145740p:plain

  4. 「Download and Install」ダイアログが表示されるので、「Yes」ボタンをクリックします。

  5. ダウンロードが完了するとボタンが「Restart IntelliJ IDEA」に変わるので、そのボタンをクリックします。

    f:id:ksby:20150531145934p:plain

  6. 「Settings」ダイアログに戻るので「OK」ボタンをクリックします。

  7. 「Platform and Plugin Updates」ダイアログが表示されるので、「Restart」ボタンをクリックします。

  8. IntelliJ IDEA が再起動します。再起動後、src/main/java/ksbysample/webapp/email/dao の下の EmailDao.java を開くと、SQL ファイルが存在する selectById メソッドSQL アイコンが表示され、このアイコンをクリックすると src/main/resources/META-INF/ksbysample/webapp/email/dao/EmailDao の下の selectById.sql が開きます。

    f:id:ksby:20150531151216p:plain

  9. また EmailDao.java に新規 @Select アノテーションを付加したメソッドを定義した後、赤文字で表示されたメソッド名にカーソルを移動して Alt+Enter を押すと、コンテキストメニューが表示されて「SQLファイルを作る。」が表示されます。

    f:id:ksby:20150531151501p:plain

  10. SQLファイルを作る。」メニューを選択すると「Choose Destination Directory」ダイアログが表示されます。「...\src\main\resources」を選択した後、「OK」ボタンをクリックします。

    f:id:ksby:20150531151827p:plain

  11. "IDE internal error occurred." のエラーメッセージが表示されました。。。

    f:id:ksby:20150531152300p:plain

    Event Log から「IDE Fatal Errors」ダイアログを表示すると下記のメッセージが表示されていました。

    f:id:ksby:20150531153308p:plain

    SQL ファイルがない場合に自動生成してくれる機能は残念ながら使えませんでした。何か使い方を間違えているのかな。。。 とりあえず一旦今はこれで保留にします。

JRebel のインストール

  1. https://my.jrebel.com/ にアクセスします。サイトの右側にある「Connect w/ Twitter」ボタンをクリックします。

    f:id:ksby:20150531172105p:plain

  2. Twitter の画面が表示されます。ユーザー名とパスワードを入力して「連携アプリを認証」ボタンをクリックします。

  3. Account Details 入力フォームが表示されます。情報を入力して「SIGN UP」ボタンをクリックします。

    f:id:ksby:20150531172544p:plain

  4. Account Details 入力フォームに入力したメールアドレスにメールが送信されます。メールの中の URL をクリックします。

  5. 「Complete registration for myJRebel」ページが表示されます。情報を入力して「COMPLETE REGISTRATION」ボタンをクリックします。

    f:id:ksby:20150531173311p:plain

  6. myJRebel の「My Dashboard」ページが表示されます。ページ左側の「Install and Activate」をクリックします。

  7. 「Install and Activate」ページが表示されます。ページ右側に「What's next」として手順が表示されていますので、それに従い JRebel をダウンロードします。

  8. メインメニューから「File」-「Settings...」を選択します。

  9. 「Settings」ダイアログが表示されます。画面左上の検索フィールドに "plugins" と入力した後、画面右側の「Browse repositories...」ボタンをクリックします。

  10. 「Browse Repositories」ダイアログが表示されます。画面左上の検索フィールドに "JRebel" と入力し、その下のリストから「JRebel Plugin」を選択します。画面右側に「JRebel Plugin」の情報が表示されますので、「Install plugin」ボタンをクリックします。

    f:id:ksby:20150531174341p:plain

  11. 「Download and Install」ダイアログが表示されるので、「Yes」ボタンをクリックします。

  12. ダウンロードが完了するとボタンが「Restart IntelliJ IDEA」に変わるので、そのボタンをクリックします。

  13. 「Settings」ダイアログに戻るので「OK」ボタンをクリックします。

  14. 「Platform and Plugin Updates」ダイアログが表示されるので、「Restart」ボタンをクリックします。

  15. IntelliJ IDEA が再起動します。再起動後、画面右上に「Get started with JRebel」のメッセージが表示されるので、その下の「Activate your JRebel Trial.」のリンクをクリックします。

    f:id:ksby:20150531174818p:plain

  16. 「Activate JRebel」ダイアログが表示されます。画面上部の「I already have a license」タブをクリックした後、画面中央の「Activation code」に myJRebel の「Install and Activate」ページに表示されている Activation code を Copy&Paste して「Activate JRebel」ボタンをクリックします。

    f:id:ksby:20150531175252p:plain

  17. 「JRebel activated」のメッセージが表示されたら「Open JRebel Configuration」ボタンをクリックします。

  18. 「Default Settings」ダイアログが表示されます。特に設定は変更せずに、そのまま「OK」ボタンをクリックして閉じます。

  19. build.gradle を リンク先の内容 に変更します。

  20. Gradle projects View の左上にある「Refresh all Gradle projects」ボタンをクリックして更新します。

  21. いろいろ JRebel のドキュメントを見ていたら以下のドキュメントを見つけました。JRebel の Agent はデフォルトでは Legacy Agent が設定されていますが、最新は JRebel 6 Agent だそうです。設定を変更します。

    Switching between JRebel Agents
    http://manuals.zeroturnaround.com/jrebel/misc/upgrade-core.html

    JRebel 6 Released!
    http://zeroturnaround.com/blog/jrebel-6-released/

  22. メインメニューから「File」-「Settings...」を選択します。

  23. 「Settings」ダイアログが表示されます。画面左側のツリーから「JRebel」-「Advanced」を選択します。画面右側の一番上に「JRebel agent to use」という項目があるので「JRebel 6 Agent」を選択後「OK」ボタンをクリックします。

    f:id:ksby:20150531223143p:plain

  24. 「JRebel 6 Agent」に変更した時に "You've changed JRebel agent. Please restart the Java VM for the changes to take effect." という赤文字のメッセージが表示されていたので、念の為 IntelliJ IDEA を一度終了させて起動し直します。

  25. これまでは Gradle projects View から bootRun タスクを実行して Tomcat を起動していましたが、bootRun タスクでは JRebel で実行できないようです。アイコンがグレーのままで押せません。

    f:id:ksby:20150531223935p:plain

    bootRun で JRebel のボタンを押す方法が分かりませんでしたので、Application クラスを直接指定して実行する方法で JRebel を使用できるようにします。

  26. Project View で src/main/java/ksbysample/webapp/email の下の Application クラスを選択し、コンテキストメニューを表示後「Run with JRebel」-「Application」を選択します。

    f:id:ksby:20150531224239p:plain

  27. JRebel で Tomcat が起動しようとしますが、spring.profiles.active が未指定の場合には起動しないよう処理を入れているので、起動しません。spring.profiles.active を設定します。

    WARNING: Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5. というログが出力されますが、WARNING なので一旦無視します。

  28. メインメニューから「Run」-「Edit Configurations...」を選択します。

  29. 「Run/Debug Configurations」ダイアログが表示されます。画面左側のツリーから「Spring Boot」-「Application」を選択します。選択後、画面右側の「VM options」に "-Dspring.profiles.active=develop" を入力して「OK」ボタンをクリックします。

    f:id:ksby:20150531225702p:plain

  30. メイン画面に戻ると「Select Run/Debug Configuration」で「Application」が選択されており、画面右上の JRebel のボタンが押せるようになっています。「Run with JRebel 'Application'」ボタンをクリックします。

    f:id:ksby:20150531225935p:plain

  31. 無事 Tomcat が起動し、"Started Application in ..." のログが出力されました。

    f:id:ksby:20150531230449p:plain

JRebel を試してみる

追加・変更した実装が Tomcat を再起動することなく反映されるか確認してみます。

  1. src/main/java/ksbysample/webapp/email/web の下に sample パッケージを新規作成します。

  2. src/main/java/ksbysample/webapp/email/web/sample の下に SampleController.java を新規作成します。作成後、リンク先のその1の内容 に変更します。

  3. Ctrl+F9 を押して make した後、ブラウザから http://localhost:8080/sample にアクセスします。"SampleController" の文字列が表示されます。

    f:id:ksby:20150531232204p:plain

  4. src/main/java/ksbysample/webapp/email/web/sample の下に SampleForm.java を新規作成します。作成後、リンク先のその1の内容 に変更します。

  5. src/main/java/ksbysample/webapp/email/web/sample の下の SampleController.javaリンク先のその2の内容 に変更します。

  6. Ctrl+F9 を押して make した後、ブラウザから http://localhost:8080/sample にアクセスします。"SampleForm error !!" の文字列が表示されます。

    f:id:ksby:20150531233854p:plain

    ブラウザから http://localhost:8080/sample?param=test にアクセスします。"SampleController (test)" の文字列が表示されます。

    f:id:ksby:20150531234321p:plain

  7. src/main/java/ksbysample/webapp/email/web/sample の下に SampleService.java を新規作成します。作成後、リンク先の内容 に変更します。

  8. src/main/java/ksbysample/webapp/email/web/sample の下の SampleController.javaリンク先のその3の内容 に変更します。

  9. Ctrl+F9 を押して make した後、ブラウザから http://localhost:8080/sample?param=test にアクセスします。"SampleController (test の長さは 4 です。)" の文字列が表示されます。

    f:id:ksby:20150601215539p:plain

  10. src/main/resources/templates/sample の下に sample.html を新規作成します。作成後、リンク先のその1の内容 に変更します。

  11. src/main/java/ksbysample/webapp/email/web/sample の下の SampleController.javaリンク先のその4の内容 に変更します。

  12. Ctrl+F9 を押して make した後、ブラウザから http://localhost:8080/sample?param=test にアクセスします。下記の画像の画面が表示されました。

    f:id:ksby:20150601223319p:plain

  13. src/main/java/ksbysample/webapp/email/web/sample の下の SampleForm.javaリンク先のその2の内容 に変更します。

  14. src/main/java/ksbysample/webapp/email/web/sample の下に SampleFormValidator.java を新規作成します。作成後、リンク先の内容 に変更します。

  15. src/main/resources/templates/sample の下の sample.html を リンク先のその2の内容 に変更します。

  16. src/main/java/ksbysample/webapp/email/web/sample の下の SampleController.javaリンク先のその5の内容 に変更します。

  17. Ctrl+F9 を押して make した後、ブラウザから http://localhost:8080/sample?param=tanaka@sample.com&param2=sample にアクセスしますが、エラーにはなりませんでした。SampleFormValidator は呼び出されておらず、さすがにこれはダメなようです。

    f:id:ksby:20150602001621p:plain

  18. Ctrl+F2 を押して Tomcat を停止します。

  19. 以下のパッケージ、ディレクトリを削除します。

    • src/main/java/ksbysample/webapp/email/web/sample
    • src/main/resources/templates/sample

JRebel を試してみた感想は。。。

  • 100% ではありませんでしたが、かなり Tomcat を起動したまま実装を反映することができました。
  • Tomcat の起動に通常の2倍くらい時間がかかります。ノーマルで 24秒で起動していたのが、JRebel で起動すると 51秒かかりました。
  • マシンパワーが必要そうな感じがします。自分のノート PC は Core i7 2.80GHz + メモリ 8GB + SSD256MB + Windows7 Professional SP1 64bit というスペックで、今の ksbysample-webapp-email レベルのクラス数だと結構軽快に動作・反映したのですが、例えば Win7 32bit でメモリ4GBくらいの PC に同じパフォーマンスを期待してよいのかは悩みますね。。。

自分の PC では軽快に動作するので、このまま使い続けてみたいと思います。

commit、push

最後に build.gradle を変更していますので、commit、push しておきます。ブランチは作成しません。

ソースコード

build.gradle

buildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE")
        // for Grgit
        classpath("org.ajoberstar:grgit:1.1.0")
    }
}

apply plugin: 'java'
apply plugin: 'spring-boot'
apply plugin: 'idea'
  • 「buildscript 」の「repositories」の中から maven { url "http://repo.spring.io/repo/" } を削除します。
  • 「buildscript 」の「dependencies」の中から classpath("org.springframework:springloaded:1.2.3.RELEASE") を削除します。

SampleController.java

■その1

package ksbysample.webapp.email.web.sample;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @RequestMapping
    @ResponseBody
    public String index() {
        return "SampleController";
    } 

}

■その2

package ksbysample.webapp.email.web.sample;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @RequestMapping
    @ResponseBody
    public String index(@Validated SampleForm sampleForm
            , BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "SampleForm error !!";
        }

        return "SampleController (" + sampleForm.getParam() + ")";
    } 

}
  • index メソッド@Validated SampleForm sampleForm, BindingResult bindingResult の引数を追加します。
  • Bean Validation エラー発生時の処理 if (bindingResult.hasErrors()) { ... } を追加します。
  • return の文字列に sampleForm.getParam() が入るようにします。

■その3

package ksbysample.webapp.email.web.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @Autowired
    private SampleService sampleService;
    
    @RequestMapping
    @ResponseBody
    public String index(@Validated SampleForm sampleForm
            , BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return "SampleForm error !!";
        }

        return "SampleController (" + sampleService.getData(sampleForm) + ")";
    }

}
  • private SampleService sampleService; を追加します。
  • return で返す文字列を "SampleController (" + sampleService.getData(sampleForm) + ")" にします。

■その4

package ksbysample.webapp.email.web.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @Autowired
    private SampleService sampleService;

    @RequestMapping
    public String index(@Validated SampleForm sampleForm
            , BindingResult bindingResult
            , Model model) {
        if (bindingResult.hasErrors()) {
            return "SampleForm error !!";
        }

        model.addAttribute("urlparam", sampleForm.getParam());
        model.addAttribute("getdata", sampleService.getData(sampleForm));

        return "sample/sample";
    }

}
  • @ResponseBody を削除します。
  • index メソッドの引数に Model model を追加します。
  • model.addAttribute(...) の処理を追加します。
  • return の戻り値を "sample/sample" に変更します。

■その5

package ksbysample.webapp.email.web.sample;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/sample")
public class SampleController {

    @Autowired
    private SampleFormValidator sampleFormValidator;
    
    @Autowired
    private SampleService sampleService;

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.addValidators(sampleFormValidator);
    }
    
    @RequestMapping
    public String index(@Validated SampleForm sampleForm
            , BindingResult bindingResult
            , Model model) {
        if (bindingResult.hasErrors()) {
            return "sample/sample";
        }

        model.addAttribute("urlparam", sampleForm.getParam());
        model.addAttribute("getdata", sampleService.getData(sampleForm));

        return "sample/sample";
    }

}
  • private SampleFormValidator sampleFormValidator; を追加します。
  • initBinder メソッドを追加します。
  • if (bindingResult.hasErrors()) { ... } の中の return の戻り値を "sample/sample" に変更します。

SampleForm.java

■その1

package ksbysample.webapp.email.web.sample;

import lombok.Data;
import org.hibernate.validator.constraints.NotBlank;

@Data
public class SampleForm {

    @NotBlank
    private String param;

}

■その2

package ksbysample.webapp.email.web.sample;

import lombok.Data;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotBlank;

@Data
public class SampleForm {

    @NotBlank
    @Email
    private String param;

    @NotBlank
    private String param2;
    
}
  • param に @Email アノテーションを追加します。
  • private String param2; を追加します。

SampleService.java

package ksbysample.webapp.email.web.sample;

import org.springframework.stereotype.Controller;

@Controller
public class SampleService {

    public String getData(SampleForm sampleForm) {
        return sampleForm.getParam() + " の長さは "
                + sampleForm.getParam().length() + " です。";
    }

}

sample.html

■その1

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
これは SampleController から呼び出されたページです。<br/>
param=<span th:text="${param}"></span><br/>
sampleService.getData=<span th:text="${data}"></span>
</body>
</html>

■その2

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title></title>
</head>
<body>
<form id="sampleForm" method="post" action="/sample" th:action="@{/sample}" th:object="${sampleForm}">
    <div th:if="${#fields.hasGlobalErrors()}">
        <p th:each="err : ${#fields.globalErrors()}" th:text="${err}">共通エラーメッセージ表示エリア</p>
    </div>
</form>
これは SampleController から呼び出されたページです。<br/>
param=<span th:text="${urlparam}"></span><br/>
sampleService.getData=<span th:text="${getdata}"></span>
</body>
</html>
  • <form id="sampleForm" ...> ... </form> を追加します。

SampleFormValidator.java

package ksbysample.webapp.email.web.sample;

import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;

@Component
public class SampleFormValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return clazz.equals(SampleForm.class);
    }

    @Override
    public void validate(Object target, Errors errors) {
        SampleForm sampleForm = (SampleForm)target;
        
        // param = tanaka@sample.com の場合には param2 = test
        // でなければエラー
        if (StringUtils.equals(sampleForm.getParam(), "tanaka@sample.com")
                && !StringUtils.equals(sampleForm.getParam2(), "test")) {
            // とりあえずエラーメッセージは他のものを使う
            errors.reject("mailsendForm.item.noSelect");
        }
    }

}