- 投稿日:2020-07-06T23:57:51+09:00
VSCodeでSpringBootアプリケーションを開発しているときにハマったこと
始めに
最近は、Javaでの開発もVSCodeに一本化しようと思い色々と試しています。
既に同じようなことでハマってる方がいましたが、自分へのメモとして。
環境
一応、私の環境セットを載せてありますが、フレームワークは何でもよいです。(VSCode, Gradle環境で再現します。)
- IDE:VSCode
- 言語:Java
- フレームワーク:Spring Boot
- ビルドツール:Gradle
事象
build.gradle
のdependenciesに依存ライブラリを追加で定義する。例えば、Spring BootアプリケーションにDoma2への依存を追加することにする。build.gradle// ... 省略 dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' } // Doma2への依存を追加 annotationProcessor "org.seasar.doma.boot:doma-spring-boot-starter:1.4.0" } // ... 省略依存ライブラリダウンロード後、dependencies treeを表示する。(Doma2の依存関係が追加されていることを確認するため)
// ... 省略 | \--- org.seasar.doma.boot:doma-spring-boot-autoconfigure:1.4.0 | +--- org.seasar.doma.boot:doma-spring-boot-core:1.4.0 | | +--- org.seasar.doma:doma-core:2.35.0 | | +--- org.springframework:spring-context:4.3.25.RELEASE -> 5.2.7.RELEASE (*) | | +--- org.springframework:spring-jdbc:4.3.25.RELEASE -> 5.2.7.RELEASE (*) | | \--- org.springframework.data:spring-data-commons:1.13.23.RELEASE -> 2.3.1.RELEASE | | +--- org.springframework:spring-core:5.2.7.RELEASE (*) | | +--- org.springframework:spring-beans:5.2.7.RELEASE (*) | | \--- org.slf4j:slf4j-api:1.7.26 -> 1.7.30 | +--- org.springframework:spring-jdbc:4.3.25.RELEASE -> 5.2.7.RELEASE (*) | \--- org.springframework.boot:spring-boot-autoconfigure:1.5.22.RELEASE -> 2.3.1.RELEASE (*) // ... 省略試しに、Entityクラスを定義してみると、
@Entity
や@Table
といったアノテーションの自動補完が効かない事象が発生。EmployeeEntity.java// ... 省略 @Getter @Setter // @Entity <- 自動補完が効かない! // @Table(name = "employees") <- 自動補完が効かない! public class EmployeeEntity { private Integer id; private String name; }解決方法
VSCodeのJavaのimport補完は、Eclipseと同様に
.classpath
を読み込むため、依存ライブラリを追加した段階で.classpath
を再生成すればよい。build.gradleplugins { id 'org.springframework.boot' version '2.3.1.RELEASE' id 'io.spring.dependency-management' version '1.0.9.RELEASE' id 'java' id 'eclipse' // 追加 } // ... 省略$ ./gradlew eclipse BUILD SUCCESSFUL in 1s 3 actionable tasks: 3 executed終わりに
やっぱり軽量なのが個人的にいいなと思うポイントです。
とはいえ、ユーザライブラリを読み込むような開発ケースではEclipseでしか対応できないのでまだ完全移行とはいかなそうです。
参考
- 投稿日:2020-07-06T23:20:50+09:00
[Java] Mockitoでコンストラクタに引数を渡す/メソッドのデフォルト呼び出しをcallRealMethodにする
Mockitoのちょっとしたメモ
コンストラクタに引数を渡す
package jp.jig.product.live.server.common.service; import org.junit.Test; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ApiServiceTest { public static class ApiService { private final String endpoint; private final int timeout; public ApiService(String endpoint, int timeout) { this.endpoint = endpoint; this.timeout = timeout; } public String request() { if (endpoint == null) { throw new IllegalStateException("endpoint is null"); } return endpoint; } public boolean validTimeout() { return timeout > 0; } } @Test public void test() { ApiService apiService = mock(ApiService.class); when(apiService.request()).thenCallRealMethod(); apiService.request(); // -> throw IllegalStateException } }例えば上記のような
ApiService
のmockを作って、request()
メソッドを callRealMethod する場合などで
コンストラクタで値を設定したいといった場面があると思います。このとき
mock()
の第2引数にMockSetting#useConstructor
を使うことでmockオブジェクトを作りつつ、コンストラクタに引数を渡すことができます@Test public void test() { ApiService apiService = mock(ApiService.class, withSettings().useConstructor("http://localhost", 100)); when(apiService.request()).thenCallRealMethod(); apiService.request(); // -> http://localhost }コンストラクタにロギングを仕込んでみると、ログも出力されるので、コンストラクタ内の処理も実行されていることがわかります。
デフォルト呼び出しをcallRealMethodにする
テストの際に基本的には本来のメソッドを呼び出したいが、一部のメソッドだけモックしたい。。。といったことがあると思います。
ApiService apiService = mock(ApiService.class, withSettings().defaultAnswer(CALLS_REAL_METHODS));こちらは
MockSetting#defaultAnswer
にCALLS_REAL_METHODS
を渡すことで、デフォルトでcallRealMethodになります。ただし注意点が一つあり、callRealMethodになっているメソッドで
when()
を使うときは注意が必要になります@Test public void test() { ApiService apiService = mock(ApiService.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); when(apiService.request()).thenReturn("hogehoge"); // -> throw IllegalStateException }上記コードは一見普通に動きそうですが、
when(apiService.request())
の時点でメソッドが呼び出されてしまいます。
これを回避するためにはdoReturn
を使うのがいいらしいです。@Test public void test() { ApiService apiService = mock(ApiService.class, withSettings().defaultAnswer(CALLS_REAL_METHODS)); doReturn("hogehoge").when(apiService).request(); apiService.request(); // -> hogehoge }doReturnを使うことで callRealMethod を呼び出さずにmockを設定することができます。
- 投稿日:2020-07-06T16:59:07+09:00
【Java】祝日判定サンプル
概要
平日のみbatchを稼働させたくて、土日はcronで簡単に除外できるけど祝日はサクッと除外できないのでJava側で祝日判定した。
ちょっと検索した感じJavaのサンプルがあんまり見当たらなかったので公開します。注意
- 振替休日はちょっと複雑なので考慮しません
- 過去の年は考慮しません
- 今年(2020年)は特殊なので考慮しません
- 春分の日と秋分の日の計算式は1980~2099年のみ有効
環境
- Java 1.8
- SpringBoot 2.2.1.RELEASE
1. 祝日の定義classを作成
- 固定日付、ハッピーマンデー、春分の日、秋分の日の4パターン
PublicHoliday.javapackage com.tamorieeeen.sample.consts; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.AllArgsConstructor; import lombok.Getter; /** * 祝日 * * @author tamorieeeen * */ @Getter @AllArgsConstructor public enum PublicHoliday { NEWYEAR("元日", "fixed", 1, 1, 0), ADULT("成人の日", "monday", 1, 0, 2), FOUNDATION("建国記念の日", "fixed", 2, 11, 0), BIRTHDAY("天皇誕生日", "fixed", 2, 23, 0), SPRING("春分の日", "spring", 3, 0, 0), SHOWA("昭和の日", "fixed", 4, 29, 0), CONSTITUTION("憲法記念日", "fixed", 5, 3, 0), GREEN("みどりの日", "fixed", 5, 4, 0), CHILDREN("こどもの日", "fixed", 5, 5, 0), SEA("海の日", "monday", 7, 0, 3), MOUNTAIN("山の日", "fixed", 8, 11, 0), AGED("敬老の日", "monday", 9, 0, 3), AUTUMN("秋分の日", "autumn", 9, 0, 0), SPORTS("スポーツの日", "monday", 10, 0, 2), CULTURE("文化の日", "fixed", 11, 3, 0), THANKSGIVING("勤労感謝の日", "fixed", 11, 23, 0); private String name; // fixed: 日付固定 // monday: 第何月曜日 // spring: 春分の日 // autumn: 秋分の日 private String type; // 月 private int month; // 日付型: 日 private int dayOfMonth; // 第何月曜日型: 第何か private int weekOfMonth; /** * 固定祝日を取得 */ public static List<PublicHoliday> getFixedHoliday() { return Stream.of(values()) .filter(v -> v.type.equals("fixed")) .collect(Collectors.toList()); } /** * ハッピーマンデーを取得 */ public static List<PublicHoliday> getHappyMonday() { return Stream.of(values()) .filter(v -> v.type.equals("monday")) .collect(Collectors.toList()); } /** * 春分の日を取得 */ public static PublicHoliday getSpringDay() { return Stream.of(values()) .filter(v -> v.type.equals("spring")) .findFirst() .orElseThrow(() -> new IllegalArgumentException()); } /** * 秋分の日を取得 */ public static PublicHoliday getAutumnDay() { return Stream.of(values()) .filter(v -> v.type.equals("autumn")) .findFirst() .orElseThrow(() -> new IllegalArgumentException()); } }2. 祝日判定classを作成
- 第何月曜日は
Calendar.DAY_OF_WEEK_IN_MONTH
を使う- 春分の日:
int(20.8431+0.242194*(年-1980)-int((年-1980)/4))
- 秋分の日:
int(23.2488+0.242194*(年-1980)-int((年-1980)/4))
HolidayService.javapackage com.tamorieeeen.sample.service; import java.time.LocalDate; import java.util.Calendar; import org.springframework.stereotype.Service; import com.tamorieeeen.sample.consts.PublicHoliday; /** * * @author tamorieeeen * */ @Service public class HolidayService { // 1年は365.242194日なので、1年あたり0.242194日進む private static final double DIFF_OF_YEAR = 0.242194; // うるう年であった1980年から計算 private static final int LEAP_YEAR = 1980; // 1980年の春分の時刻「20.8431日」 private static final double SPRING_DAY_BASE = 20.8431; // 1980年の秋分の時刻「23.2488日」 private static final double AUTMN_DAY_BASE = 23.2488; /** * 祝日かどうか */ public boolean isHoliday() { LocalDate today = LocalDate.now(); if (this.isFixedHoliday(today) || this.isHappyMonday(today) || this.isSpringDay(today) || this.isAutmnDay(today)) { return true; } return false; } /** * 固定祝日かどうか */ private boolean isFixedHoliday(LocalDate today) { return PublicHoliday.getFixedHoliday() .stream() .map(h -> LocalDate.of( today.getYear(), h.getMonth(), h.getDayOfMonth())) .anyMatch(h -> h.isEqual(today)); } /** * ハッピーマンデーかどうか */ private boolean isHappyMonday(LocalDate today) { return PublicHoliday.getHappyMonday() .stream() .map(h -> LocalDate.of( today.getYear(), h.getMonth(), this.getDayOfMonth(h.getMonth(), h.getWeekOfMonth()))) .anyMatch(h -> h.isEqual(today)); } /** * ハッピーマンデーの日付を取得 */ private int getDayOfMonth(int month, int weekOfMonth) { Calendar cal = Calendar.getInstance(); cal.set(Calendar.MONTH, month - 1); cal.set(Calendar.DAY_OF_WEEK, 2); cal.set(Calendar.DAY_OF_WEEK_IN_MONTH, weekOfMonth); return cal.get(Calendar.DAY_OF_MONTH); } /** * 春分の日かどうか */ private boolean isSpringDay(LocalDate today) { int dayOfMonth = this.getMovedDays(today.getYear(), SPRING_DAY_BASE) - this.getLeapYearDays(today.getYear()); LocalDate springDay = LocalDate.of( today.getYear(), PublicHoliday.getSpringDay().getMonth(), dayOfMonth); return today.isEqual(springDay); } /** * 秋分の日かどうか */ private boolean isAutmnDay(LocalDate today) { int dayOfMonth = this.getMovedDays(today.getYear(), AUTMN_DAY_BASE) - this.getLeapYearDays(today.getYear()); LocalDate autmnDay = LocalDate.of( today.getYear(), PublicHoliday.getAutumnDay().getMonth(), dayOfMonth); return today.isEqual(autmnDay); } /** * 進んでる分を取得 */ private int getMovedDays(int year, double baseDay) { return (int) Math.floor(baseDay + this.getDiffDays(year)); } /** * 1980年からの差分を取得 */ private double getDiffDays(int year) { return DIFF_OF_YEAR * (year - LEAP_YEAR); } /** * うるう年の分を取得 */ private int getLeapYearDays(int year) { return (int) Math.floor((year - LEAP_YEAR) / 4.0); } }3. 呼び出しclassを作成
holidayService.isHoliday()
がtrueなら祝日、falseなら平日SampleController.javapackage com.tamorieeeen.sample.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import com.tamorieeeen.sample.service.HolidayService; import lombok.extern.slf4j.Slf4j; /** * * @author tamorieeeen * */ @Slf4j @Controller public class SampleController { @Autowired private HolidayService holidayService; @GetMapping("/sample") public String top() { if (holidayService.isHoliday()) { log.info("today is holiday."); } else { log.info("today is weekday."); } return "sample"; } }感想
春分の日と秋分の日の計算がめんどくさい!
参考
- 投稿日:2020-07-06T14:08:30+09:00
JAVAでimmutableなクラスをつくる
imutableとは
immutableとは不変のこと。
つまり、immutableなクラスとはインスタンスの中身が変わらない(=書き換えられない)クラスのことです。
immudatableなクラスを書き換えるということは、インスタンスを再生産するということになります(繰り返し処理の中で何度もインスタンスが作られると古いインスタンスが溜まっていき、処理速度が遅くなるため、使い方は注意しないといけません)。immutableなクラスを利用する時には、中身を変更してないか、参照型の値を利用してないか等、確認する必要があります。immutableのメリット
immutableなオブジェクトは、作成時から状態が変わらないため、処理の途中で意図しない形に値が変化する心配はありません。
immutableなクラスで代表的なものに、Stringクラスがあります。作成時の条件
immutableなクラスを作るには、以下の4つの条件を満たす必要があります。
1.クラスをfinalとして宣言
2.すべてのフィールドをfinalかつprivateにする
クラスをfinalとして宣言、またはコンストラクタをプライベートにして、ファクトリメソッドでインスタンス生成3.setterを定義しない
クラスをfinalとして宣言、またはコンストラクタをプライベートにして、ファクトリメソッドでインスタンス生成4.フィールドにmmutable(可変)なオブジェクトへの参照を含んでいない
Fruits.javapublic final class Fruits { private final String color; private final int amount; public Fruits(String name, String, int age) { this.name = color; this.age = amount; } public String getColor() { return color; } public int getAmount() { return amount; } }変更しようとするとエラーになります。
FruitsTest.java@Test public void fruitsTest() { final Person Fruits = new Fruits("Red", 28); // error // fruits.setColor("yellow"); // fruits.amount(30); }
- 投稿日:2020-07-06T11:30:44+09:00
Javaの比較 compareTo()メソッドを使って
filename.rbpublic static void main(String[] args) { System.out.println("-----a.compareTo(b)を使ってみよう!-----"); //aが大きい場合 Integer a = 2; Integer b = 1; System.out.println("a の方が大きい場合" + " " +a.compareTo(b)); // 1 が返る //aが小さい場合 a = 1; b = 2; System.out.println("a の方が小さい場合" + " " +a.compareTo(b)); // -1 が返る //a と b が同じ数字の場合 a = 1; b = 1; System.out.println("a と b が同じ場合" + " " +a.compareTo(b)); // 0 が返る }compareTo()・・・・まだ使い道がよくわかりません(笑)
- 投稿日:2020-07-06T07:21:54+09:00
VSCode Remote Containers を利用して最強のローカル開発環境を作りたい
はじめに
VSCodeの神拡張機能であるRemote Containersの自分なりの設定の紹介です
公式サンプルは公開されていますが、そのままだと流石に使いずいので自分なりに使いやすいように編集した設定を紹介します
なお、本記事で紹介する設定ファイルは全て以下のリポジトリで公開しています(紹介していない環境のものも入っています)
https://github.com/sabure500/remote-container-sampleまた、Remote Containersを使ってみて良いなと思ったので色々使いやすいように設定を弄っていますが、本記事は最強のローカル環境を「作りたい」なので、ここをこうした方が良いといった案があったら是非教えてくれると嬉しいです
VSCode Remote Containers とは
VSCodeの拡張機能であり、使用することでコンテナの中でVSCodeを開いて作業を行うことができるようになる
コンテナの中で直接VSCodeを開いて作業ができるようになるため、開発環境をサンドボックス化してローカルマシン上には全く影響しないところで開発を行うことができる
類似の拡張機能シリーズでこれ以外にも「Remote SSH」と「Remote WSL」が存在し、これはそれぞれSSH接続先またはWSLの中でVSCodeを開いて作業を行うことができるようになる
それぞれの詳細は 公式サイト を参照インストール
VSCode Remote Containers で開発環境を作る場合は以下の2つのインストールが必要です
逆にいうと、以下の2つがあればローカルマシンには他に何も入れずにNode,python,Go,Java等の環境が作れます
* Visual Studia Code
* Docker Desktop for Windows or MacDocker Desktop for Windows or Mac
以下の公式ページからインストーラをダウンロードする
https://www.docker.com/products/docker-desktopVisual Studio Code
VSCode本体のインストール
以下の公式ページからダウンロードする
https://code.visualstudio.com/docsRemote Containersの導入
Remote Containersは通常の拡張機能なので、VSCodeインストール後に起動して左のタブから拡張機能を選択し「Remote Container」と検索することで一覧に出てくるのでそこからインストールできる
もしくは以下のマーケットプレースのページからインストールしても良い
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containersRemote Containersの起動
後ほど紹介するRemoteContainersの設定ファイルがある場所をWorkspaceとして起動後に、VSCode左下の緑の「><」マークをクリックし、「Remote-Containers : Reopen in Container」を選択する
環境構築設定の紹介
Remote Containersにおける環境構築は.devcontainerディレクトリ上にdevcontainer.jsonというRemote Containers用の設定ファイルとDockerfile(もしくは、docker-compose.yaml等)を配置することで行う
環境毎の設定を紹介していくGoogleCloudSDK
ローカル環境でGoogleCloudSDKのコマンドを利用するときもRemote Containersを利用している
設定ファイルは以下で公開しており、基本的にはこれをそのまま使うことで誰でもすぐに同じGoogleCloudSDKの環境を利用できる
その環境構築用の設定を記述していく
全体のディレクトリ構成は以下のようになっている. ├ .devcontainer ├ devcontainer.json ├ Dockerfile ├ .config/fish/config.fish └ .local/share/fish/fish_historyDockerfile
実際に開発環境として使うコンテナを作成する用のファイル
最初に全体像を示し、その後各行を解説するDockerfileFROM google/cloud-sdk:297.0.1-alpine # ===== common area ===== RUN apk add --no-cache fish git openssh curl COPY .config/fish/config.fish /root/.config/fish/config.fish # ======================= # ===== kubernetes resource install ===== ENV KUBECTL_VERSION 1.18.4 ENV KUSTOMIZE_VERSION 3.1.0 ENV ARGOCD_VERSION 1.5.2 RUN curl -sfL -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl \ && curl -sfL -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 \ && curl -sfL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v${ARGOCD_VERSION}/argocd-linux-amd64 \ && chmod +x /usr/local/bin/kubectl /usr/local/bin/kustomize /usr/local/bin/argocd # =======================
ベースイメージ
FROM google/cloud-sdk:297.0.1-alpineベースイメージは google/cloud-sdk:297.0.1-alpine を利用する
環境で利用する汎用的なパッケージのインストール
RUN apk add --no-cache fish git openssh curlベースイメージがalpineなので、apkを利用して開発環境上で利用したいパッケージをインストールする
ここではfish,git,ssh,curlを入れているが、bashを使いたい場合はfishではなくbashを導入する等各自カスタマイズするfishシェル用の設定
COPY .config/fish/config.fish /root/.config/fish/config.fishconfig/fish/config.fishset normal (set_color normal) set magenta (set_color magenta) set yellow (set_color yellow) set green (set_color green) set red (set_color red) set gray (set_color -o black) # Fish git prompt set __fish_git_prompt_showdirtystate 'yes' set __fish_git_prompt_showstashstate 'yes' set __fish_git_prompt_showuntrackedfiles 'yes' set __fish_git_prompt_showupstream 'yes' set __fish_git_prompt_color_branch yellow set __fish_git_prompt_color_upstream_ahead green set __fish_git_prompt_color_upstream_behind red # Status Chars set __fish_git_prompt_char_dirtystate '⚡' set __fish_git_prompt_char_stagedstate '→' set __fish_git_prompt_char_untrackedfiles '☡' set __fish_git_prompt_char_stashstate '↩' set __fish_git_prompt_char_upstream_ahead '+' set __fish_git_prompt_char_upstream_behind '-' function fish_prompt set last_status $status set_color $fish_color_cwd printf '%s' (prompt_pwd) set_color normal printf '%s ' (__fish_git_prompt) set_color normal end作業用のシェルとしてはfishシェルを利用する
初期設定のままでは使いづらいので、Gitのブランチを表示する等のプロンプトを変更する設定ファイルをコンテナ上にコピーして配置する
fishの設定ファイルは以下のブログの記事を参考にさせてもらっています
https://www.martinklepsch.org/posts/git-prompt-for-fish-shell.htmlGoogleCloudSDkと一緒に使うコマンド類のインストール
# ===== kubernetes resource install ===== ENV KUBECTL_VERSION 1.18.4 ENV KUSTOMIZE_VERSION 3.1.0 ENV ARGOCD_VERSION 1.5.2 RUN curl -sfL -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/v${KUBECTL_VERSION}/bin/linux/amd64/kubectl \ && curl -sfL -o /usr/local/bin/kustomize https://github.com/kubernetes-sigs/kustomize/releases/download/v${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64 \ && curl -sfL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/download/v${ARGOCD_VERSION}/argocd-linux-amd64 \ && chmod +x /usr/local/bin/kubectl /usr/local/bin/kustomize /usr/local/bin/argocd # =======================GCPのリソースとして専らGKEを使うことが多いので、Kubernetes関連のリソースをコンテナ内にインストールしています
devcontainer.json
VSCodeからコンテナを開く際の設定ファイル
利用するDockerfileやコンテナ上でVSCodeを利用する際の拡張機能、またローカル環境からのVolume等を記述する
他に何ができるかの詳細は公式のリファレンスを参照
最初に全体像を示し、その後各行を解説するdevcontainer.json{ "name": "Google Cloud SDK Remote-Container", "build" : { "dockerfile": "Dockerfile" }, "settings": { "terminal.integrated.shell.linux": "/usr/bin/fish", }, "extensions": [ "alefragnani.bookmarks", "mhutchie.git-graph", "redhat.vscode-yaml", "zainchen.json" ], "mounts": [ "source=${localEnv:HOME}/.ssh/,target=/root/.ssh/,type=bind,consistency=cached", "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached", "source=${localWorkspaceFolder}/.devcontainer/.local/share/fish/fish_history,target=/root/.local/share/fish/fish_history,type=bind,consistency=cached", "source=${localEnv:HOME}/.config/gcloud/,target=/root/.config/gcloud/,type=bind,consistency=cached", "source=${localEnv:HOME}/.kube/,target=/root/.kube/,type=bind,consistency=cached", ], }
利用するコンテナイメージ
"build" : { "dockerfile": "Dockerfile" },Dockerfileの置いてある場所を指定する
最初にディレクトリ構造で示した通り、同じディレクトリ上にあるのでそのまま"Dockerfile"と書いているコンテナ固有のVSCodeの設定
"settings": { "terminal.integrated.shell.linux": "/usr/bin/fish", },コンテナ上独自で設定したいVSCodeの設定を記載する
例えば、ローカル上では導入しないがコンテナ上では導入するExtension用の設定等
settingsの記述に関してはローカル上で書かれていることは改めてdevcontainer.json上で書かなくてもコンテナ上で引き継がれる
ここではコンテナ上ではターミナルのシェルはfishを利用することだけ記述しているコンテナ環境上のVSCodeで利用する拡張機能の設定
"extensions": [ "alefragnani.bookmarks", "mhutchie.git-graph", "redhat.vscode-yaml", "zainchen.json" ],コンテナ環境上で利用したい拡張機能を記述する
拡張機能に関してはsettingsの設定と違い、ローカル上で導入されていてもdevcontainer.json上で書かれていないものはコンテナ上で導入されないので注意ローカル環境からのマウント
"mounts": [ "source=${localEnv:HOME}/.ssh/,target=/root/.ssh/,type=bind,consistency=cached", "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached", "source=${localWorkspaceFolder}/.devcontainer/.local/share/fish/fish_history,target=/root/.local/share/fish/fish_history,type=bind,consistency=cached", "source=${localEnv:HOME}/.config/gcloud/,target=/root/.config/gcloud/,type=bind,consistency=cached", "source=${localEnv:HOME}/.kube/,target=/root/.kube/,type=bind,consistency=cached", ],ローカル環境上のファイルを使いたい、または、コンテナを再起動しても消去されて欲しくないファイルをマウントする
"source="でローカルのパス、"target="でコンテナ上のパスを指定している
また、${localEnv:XXXX}と書くことで、ローカル環境上で環境変数「XXXX」を利用できる
ここでは5つのファイル・ディレクトリをマウントしている
- sshの設定 sshの設定は複数の環境から利用されるため、ローカル環境からマウントして利用する
- gitの設定 gitの設定も複数の環境から利用されるため、ローカル環境からマウントして利用する
- fishの操作履歴 ここが結構ポイントで、コンテナ上で作業しているとコンテナを停止すると操作履歴が全て削除されてしまう 自分は作業をするときにカーソル↑等を利用して過去のhistoryのコマンド履歴を利用することが多く消されると不便だったので、消えないようにWorkspaces上でマウントしておく
- gcpの設定 GCPログイン情報等も毎回消えると面倒なのでローカル上からマウントする
- kubectlの設定 同じくGKEのクラスタ登録等も毎回消えると面倒なのでローカル上からマウントする
Java
Java用の環境として、OpenJDK+Wildfly+maven+Gradleが入った環境を使用している
このJava用の環境も上で紹介したGoogleCloudSDKの環境と相違点に絞って紹介する
全体のディレクトリ構成は以下のようになっている. ├ .devcontainer ├ devcontainer.json ├ docker-compose.yaml ├ Dockerfile ├ .m2/ ├ .gradle/ ├ .config/fish/config.fish └ .local/share/fish/fish_historydocker-compose
Javaの環境は別にコンテナで立てるDB環境と接続するためにDockerのNetworkを利用するためにDockerfileではなく、docker-composeを利用する
最初に全体像を示し、その後各行を解説するdocker-compose.yamlversion: "3" services: jdk-wildfly-maven: build: . ports: - "8080:8080" - "9990:9990" command: /bin/sh -c "while sleep 1000; do :; done" volumes: - $HOME/.ssh:/root/.ssh - $HOME/.gitconfig:/root/.gitconfig - .local/share/fish/fish_history:/root/.local/share/fish/fish_history - ..:/workspace - ./jboss_home/configuration/standalone.xml:/opt/wildfly/standalone/configuration/standalone.xml - .m2:/root/.m2 - .gradle:/root/.gradle networks: - remote-container_common-network networks: remote-container_common-network: external: true
ローカル環境からコンテナ環境へのポートフォワード
ports: - "8080:8080" - "9990:9990"Wildflyのデフォルトポートである8080と9990に対して、ローカル環境で対象のポートにアクセスした際にコンテナ上にアクセスするようにする
コンテナのデフォルトコマンドの上書き
command: /bin/sh -c "while sleep 1000; do :; done"コンテナ起動時のデフォルトコマンドが失敗したり終了したりした場合にコンテナが停止しないように、デフォルトコマンドを上書きする
ここで記述しているコマンドはdocker-composeを利用しない場合のRemote Containersのデフォルト設定
docker-composeを利用する場合は明示的に書いてあげる必要があるローカル環境からのマウント
volumes: - $HOME/.ssh:/root/.ssh - $HOME/.gitconfig:/root/.gitconfig - .local/share/fish/fish_history:/root/.local/share/fish/fish_history - ..:/workspace - ./jboss_home/configuration/standalone.xml:/opt/wildfly/standalone/configuration/standalone.xml - .m2:/root/.m2 - .gradle:/root/.gradleローカル環境からのマウントはdevcontainer.jsonではなくdocker-compose.yamlで書く必要がある
Dockerfileの場合と違うところは、"..:/workspace"と書いているようにworkspace自体を明示的に指定してマウントしている
java環境独自の設定として".m2:/root/.m2"や".gradle:/root/.gradle"でGradleやMavenの設定やリポジトリをマウントしている。これはこの環境でしか利用しないものなので、ユーザーホーム($HOME)ではなくWorkSpace上に直接マウントしている
またWildflyの設定ファイルであるstandalone.xmlについては、データソースの設定等が開発中に常に変更される可能性があり、コンテナ再起動のたびに削除されても困るので"./jboss_home/configuration/standalone.xml:/opt/wildfly/standalone/configuration/standalone.xml"といった形でマウントしているdocker networkの利用
networks: - remote-container_common-network networks: remote-container_common-network: external: trueJavaはアプリケーションの実行環境として利用するのでDBと接続をしたい
DBに関してもローカル環境ではコンテナとして起動するので他のコンテナと接続するためにDocker Networkを作成してそれを利用する
そのためこの環境を利用するためには以下のコマンドで事前にネットワークを作成する必要があるdocker network create --driver bridge remote-container_common-network
Dockerfile
docker-compose.yamlで指定しているOpenJDK+Wildfly+maven+Gradle環境を作成するDockerfile
ベースイメージとしてはJBoss公式イメージ"jboss/wildfly"はサイズが大きく使いずらかったりalpineベースのイメージを使いたかったこともあり、Adoptopenjdkを元にして作成しているDockerfileFROM adoptopenjdk/openjdk11:alpine-slim # ===== common area ===== ENV WORKSPACE_DIR "/workspace" RUN apk add --no-cache fish git openssh curl wget tar unzip\ && mkdir -p $WORKSPACE_DIR COPY .config/fish/config.fish /root/.config/fish/config.fish # ======================= # ==== wildfly install ===== ENV JBOSS_HOME "/opt/wildfly" ENV WILDFLY_VERSION "20.0.0.Final" RUN wget -P /opt http://download.jboss.org/wildfly/${WILDFLY_VERSION}/wildfly-${WILDFLY_VERSION}.tar.gz \ && tar -zxvf /opt/wildfly-${WILDFLY_VERSION}.tar.gz -C /opt \ && rm /opt/wildfly-${WILDFLY_VERSION}.tar.gz \ && mv /opt/wildfly-${WILDFLY_VERSION} ${JBOSS_HOME} \ && $JBOSS_HOME/bin/add-user.sh admin admin --silent # ======================= # ==== maven install ===== ENV MAVEN_HOME "/opt/maven" ENV MAVEN_VERSION 3.6.3 ENV PATH "$PATH:$MAVEN_HOME/bin" ENV MAVEN_CONFIG "$HOME/.m2" RUN curl -fsSL -o /opt/apache-maven-${MAVEN_VERSION}-bin.tar.gz http://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \ && tar -zxvf /opt/apache-maven-${MAVEN_VERSION}-bin.tar.gz -C /opt \ && rm /opt/apache-maven-${MAVEN_VERSION}-bin.tar.gz \ && mv /opt/apache-maven-${MAVEN_VERSION} /opt/maven # ======================= # ==== gradle install ==== ENV GRADLE_HOME "/opt/gradle" ENV GRADLE_VERSION 6.5 ENV PATH "$PATH:$GRADLE_HOME/bin" RUN curl -fsSL -o /opt/gradle-${GRADLE_VERSION}-bin.zip https://downloads.gradle-dn.com/distributions/gradle-${GRADLE_VERSION}-bin.zip \ && unzip -d /opt /opt/gradle-${GRADLE_VERSION}-bin.zip \ && rm /opt/gradle-${GRADLE_VERSION}-bin.zip \ && mv /opt/gradle-${GRADLE_VERSION} /opt/gradle # =======================devcontainer.json
Dockerfileを利用する場合とdocker-composeを利用する場合で、devcontainer.jsonのデフォルトの設定や利用できる設定が変わっている(例えば、マウントはdevcontainer.jsonには記述しても効かなくなり、docker-composeにて記述しなければならなくなる)
詳細は公式リファレンス参照devcontainer.json{ "name": "JDK&Wildfly&Maven&Gradle Remote-Container", "dockerComposeFile": "docker-compose.yaml", "service" : "jdk-wildfly-maven", "workspaceFolder": "/workspace", "settings": { "terminal.integrated.shell.linux": "/usr/bin/fish", "java.home": "/opt/java/openjdk", "maven.executable.preferMavenWrapper": false, "maven.executable.path": "/opt/maven/bin", "maven.terminal.useJavaHome": true, "java.jdt.ls.vmargs": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication -javaagent:\"/root/.vscode/extensions/gabrielbb.vscode-lombok-1.0.1/server/lombok.jar\"", }, "extensions": [ "alefragnani.bookmarks", "mhutchie.git-graph", "vscjava.vscode-java-pack", "shengchen.vscode-checkstyle", "gabrielbb.vscode-lombok", "naco-siren.gradle-langua" ], "shutdownAction": "stopCompose" }
利用するイメージとサービス
"dockerComposeFile": "docker-compose.yaml", "service" : "jdk-wildfly-maven",docker-composeを利用する場合は"dockerComposeFile"を利用してdocker-compose.yamlの場所を指定する
また、docker-composeの場合は複数のコンテナが起動している可能性があるので、serviceでどのコンテナでVSCodeを開くのかも指定するworkspaceフォルダの指定
"workspaceFolder": "/workspace",Dockerfileの場合と違い、docker-composeの場合は明示的にマウントするworkspaceの場所を指定する必要がある
この場合に存在しないディレクトリは指定できないため、Dockerfile上で先に/workspaceといったディレクトリを作っておくjava特有の設定
"settings": { "terminal.integrated.shell.linux": "/usr/bin/fish", "java.home": "/opt/java/openjdk", "maven.executable.preferMavenWrapper": false, "maven.executable.path": "/opt/maven/bin", "maven.terminal.useJavaHome": true, "java.jdt.ls.vmargs": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication -javaagent:\"/root/.vscode/extensions/gabrielbb.vscode-lombok-1.0.1/server/lombok.jar\"", }, "extensions": [ "alefragnani.bookmarks", "mhutchie.git-graph", "vscjava.vscode-java-pack", "shengchen.vscode-checkstyle", "gabrielbb.vscode-lombok", "naco-siren.gradle-langua" ],ローカル環境のVSCode上では入れていないが、Java環境では利用したい拡張機能を指定している
また、その拡張機能に対応したコンテナ上でのみ適用したいsettingsの内容を記述しているMySQL
前の章で作成したJavaから利用するDBもコンテナで作成する
この環境はあまりRemote Containersで作成する意味はないが、統一するために一応Remote Containersで作っている
単純にdocker-composeで起動しても特に問題はない
他のコンテナ(Java環境)と接続するためにこの環境を作る前に以下のコマンドでDocker networkを作っておくこと
(すでにある場合は問題なし)docker network create --driver bridge remote-container_common-network
全体のディレクトリ構成や設定ファイルは以下のようになっている
. ├ .devcontainer │ ├ devcontainer.json │ ├ docker-compose.yaml │ └ my.cnf └ dbdocker-compose.yamlversion: "3" services: mysql: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: pass TZ: "Asia/Tokyo" ports: - "3306:3306" volumes: - ../db/data:/var/lib/mysql - ./my.cnf:/etc/mysql/conf.d/my.cnf networks: - remote-container_common-network phpmyadmin: image: phpmyadmin/phpmyadmin environment: PMA_ARBITRARY: 1 ports: - 3307:80 depends_on: - mysql networks: - remote-container_common-network networks: remote-container_common-network: external: truedevcontainer.json{ "name": "MySQL Remote-Container", "dockerComposeFile": "docker-compose.yaml", "service" : "mysql", "workspaceFolder": "/root", "settings": { "terminal.integrated.shell.linux": "/bin/bash", }, "extensions": [], "shutdownAction": "stopCompose" }補足
今回はあくまで自分用のローカル環境の構築なので、複数のアプリケーションを1つの環境で扱うことを想定してRemote Containersの設定を作っている
つまり以下のようなこと. ├ .devcontainer ├ application1 │ ├ .git │ └ source code ├ application2 │ ├ .git │ └ source code上記のような構成ではなく、1つのアプリケーションに対して1つのRemote Containersの設定を作る方がgit上でそのアプリの開発環境も管理でき開発者全員が同じ環境を使える等のメリットがあるため良いと思われる
つまり以下のようなこと. ├ application1 │ ├ .devcontainer │ ├ .git │ └ source code ├ application2 │ ├ .devcontainer │ ├ .git │ └ source codeただし、このような管理の仕方の場合は今回紹介したようなfishの独自設定をぶち込んだりすると戦争が起りかねないのでチームでよく相談してどうするかを決定した方が良いと思われます
VSCode Remote Containers のメリット・デメリット
最後に使っていて思ったメリット・デメリットをまとめて終わりにする
メリット
- 自分のローカル環境が汚れない
VSCodeとDockerさえあれば余計なものを何も入れなくて良い点がすごく良い
- 開発環境の設定もアプリ開発者の間で共有できる
同じ開発環境を共有していることになるので人によっては動く動かないといった事象の発生をなくせる
また、そもそもDockerfileが必須になるため本番環境もDockerfileで作れば開発と本番での差異を限りなく少なくできる
ただし、あくまで開発環境用のDockerfileと本番環境用のDockerfileは別で作った方が良い
(本記事では紹介していないがRemote Containersのextendの機能を利用して本番環境のDockerfileを元に開発にい必要なパッケージの導入部分のみ上書いて開発環境で利用することもできる)- 開発環境で作ったイメージがCI/CDで流用できる
開発環境上でツールを使ってチェック等をしている時は、CI/CDの時にも同様のチェックをすることは多い
この時に今のCICDツールはJOBのコンテナ上での実行をサポートしていることが多いので、開発環境作成時に作ったコンテナをそのままであったり一部だけ改良して利用できたりするデメリット
- VSCodeでしか使えない
VSCodeの拡張機能なので、それ以外のIDEでは使えない
かなり有用な機能なので自分が知らないだけで他のIDEでも同じようなことができるのかもしれないですが...
- コンテナ上では利用できない拡張機能もある
Remote Containersで起動したコンテナ上のVSCodeでは利用できない拡張機能が一部存在する
- 利用するツールについてある程度理解していないと環境構築できない
Dockerfileを作成することが必須であり、このツールの設定ファイルはどこに展開されるかといったことやマウントした方が良い部分とそうでない部分といったことを理解して意識する必要がある
ただし、設定を自作するのではなく他人が作成した設定ファイルをそのまま利用する場合は今までよりさらに何も知らなくても使えます
- 一つのコンテナ環境にどこまで詰め込んで良いか分かりづらい
今回紹介したものだとJavaとGoogleCloudSDKで分けているが、やろうと思えばこれはさらに細分化できるし、逆に1つにまとめることもできる
アプリケーションの単位でremote-containerの設定を作るなら分かりやすいが、GoogleCloudSDKのように自分用のローカル環境として作る場合は境目が難しく基準となる答えはまだ持っていない参考
- VSCode Remote Containers公式サンプル : https://github.com/Microsoft/vscode-dev-containers
- VSCode Remote Containers公式ドキュメント : https://code.visualstudio.com/docs/remote/remote-overview
- fishプロンプト設定の参考ブログ : https://www.martinklepsch.org/posts/git-prompt-for-fish-shell.html
- 投稿日:2020-07-06T02:42:56+09:00
Java Gold対策:フォーマット
はじめに
本稿は、Java Goldの学習のまとめとしてフォーマットについてまとめる。
フォーマットとは
Javaで扱うデータを表示箇所に応じて、
書式を変更したい場合に書式化するためのもの。数値や、日付などでそれぞれフォーマットする
方法が異なるため、以下にそのフォーマットの
方法をまとめる。数値のフォーマットで使用する主なクラス一覧
java.text.NumberFormat
java.text.DecimalFormat
■ java.text.NumberFormat
NumberFormatはnewすることが出来ないため、
staticメソッドによってオブジェクトを取得する。NumberFormatの主なメソッドは以下
メソッド 内容 static final NumberFormat getInstance() デフォルトロケールに対応した数値formatオブジェクトを返却 static final NumberFormat getInstance(Locale locale) 引数に指定したロケールに対応した数値formatオブジェクトを返却 static final getCurrencyInstance() デフォルトロケールに対応した通貨formatオブジェクトを返却 static final getCurrencyInstance() 引数に指定したロケールに対応した通貨formatオブジェクトを返却 Number parse(String string) throws ParseException 引数で指定された文字列を数値として返却 ▼ 使用例
java.text.NumberFormat
// 出力結果:1,000 NumberFormat format = NumberFormat.getInstance(); System.out.println(format.format(1000)); // 出力結果:¥1,000 NumberFormat format = NumberFormat.getCurrencyInstance(); System.out.println(format.format(1000)); // 出力結果:1000 try { NumberFormat format = NumberFormat.getInstance(Locale.JAPAN); String numberStr = "1000"; Number value1 = format.parse(numberStr); System.out.println(value1); } catch (ParseException e) { // 指定したロケールと異なる値(numberStr = "$1000")を指定した場合 }■ java.text.DecimalFormat
DecimalFormatは、NumberFormatと異なりnewでオブジェクトを取得する。
また、以下パターンを使用することで自由にフォーマットをすることが出来る。
記号 内容 0 数字(その桁に数値が無い場合は"0"を表示) ♯ 数字(その桁に数値が無い場合はブランク) . 数字桁区切り文字 - マイナス記号 , カンマ区切り % 100売してパーセントを表す ¥u00a5 通貨記号 ▼ 使用例
// 出力:1,11111 DecimalFormat formatter = new DecimalFormat("#,#####"); System.out.println(formatter.format(111111)); // 出力:01,1111 DecimalFormat formatter = new DecimalFormat("00,0000"); System.out.println(formatter.format(11111)); // 出力:¥11,11,11 DecimalFormat formatter = new DecimalFormat("\u00a5##,##"); System.out.println(formatter.format(111111)); // 備考(0埋め) // DecimalFormat型 // 出力:0100.00 DecimalFormat formatter = new DecimalFormat("####.##"); formatter.setMinimumFractionDigits(2); formatter.setMaximumFractionDigits(4); formatter.setMinimumIntegerDigits(4); formatter.setMaximumIntegerDigits(6); System.out.println(formatter.format(100));日付のフォーマットで使用する主なクラス一覧
java.time.format.DateTimeFormatter
■ DateTimeFormatter
主に使用するメソッド一覧
メソッド 内容 static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) 日付のフォーマット static DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle) 時間のフォーマット static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) 日付と時間のフォーマット static DateTimeFormatter ofPattern(String pattern) 引数に任意のパターン文字列を指定してフォーマッターを作成(パターンは以下に記載) パターン文字列一覧
パターン 内容 G 西暦 y 年 M 月 d 月の日 E 曜日 a 午前/午後 h 時間(12時表記) H 時間(24時表記) m 分 s 秒 SS ミリ秒 x タイムゾーン LocalDate date = LocalDate.of(2020, Month.JULY, 6); LocalTime time = LocalTime.of(10, 20, 30); LocalDateTime dateTime = LocalDateTime.of(date, time); // 以下出力結果 // 2020/07/06 // 2020/07/06 DateTimeFormatter formatter1 = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); System.out.println(formatter1.format(date)); System.out.println(formatter1.format(dateTime)); // 10:20:30 // dateをformatするとUnsupportedTemporalTypeException DateTimeFormatter formatter2 = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM); System.out.println(formatter2.format(time)); // 10:20 DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("hh:mm"); System.out.println(formatter3.format(time));※ FormatStyleについてはこちらを参照
- 投稿日:2020-07-06T00:09:21+09:00
【JavaServlet】千里の道も一歩から 五歩目
進行状況
- jsp⇔サーブレット間のパラメータの受け渡し
- クッキー/セッション管理
今回やること
- EL式
- JSTLタグ
EL式使いましょう
EL式(Expression Language)とは、ざっくり言うとjsp側でリクエストパラメータの受け渡しを簡潔に記述できる方法です。
${パラメータ名}
と書くだけです。(オブジェクトの場合は${パラメータ名.プロパティ名}
)jsp側で
name
という名前のパラメータを受け取って表示させる場合、mypage.jsp(今まで)<% String username=request.getAttribute("name").toString(); %> <%=username%>さん、ようこそと二段階のプロセスを踏んでいましたが、EL式を使うと
mypage.jsp${name}さん、ようこそと書くだけで良くなります。
記述量が減って見やすくなるのもそうですが、javaのロジックを書く必要がなくなる
ことが最大のメリットです。
EL「式」と付いているように、${age > 20}のように簡単な式を記述することも可能です。JSTLタグ使いましょう
前項でEL式が使えるようになりましたが、ループや条件分岐といった処理はどうしてもjavaのコードを埋め込まないといけません。
そこで登場するのがJSTL
と呼ばれるタグライブラリ
です。タグライブラリとは、<forEach>
のように、HTMLのようなタグを使ってjavaのロジックを表現することができる機能を集めたファイル群です。導入方法
JSTLはApacheが提供している無料で使えるオープンソースのライブラリです。
1.上記リンクを開き、ページ下部のjarファイルのうち
impl、spec、jstl
をダウンロード。
2.プロジェクトフォルダのWEB-INF/lib
内に上記3つのjarファイルを格納。
3.jspファイルに以下を記述。mypage.jsp<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>4.
web.xml
のappバージョンが3.0以上の場合、2.4に書き換えましょう。(このコーナーでは3.1で作っていましたが、2.4以降ではJSTL内でEL式が使えないらしいです。不便!)web.xml<!-- 3.1はコメントアウト --> <!-- <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> --> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">ちなみに、appバージョン別のweb-appタグの記述方法は以下のサイトに詳しく載っています。
KATSUMI KOKUZAWA'S BLOG使い方
- if文
mypage.jsp<c:choose> <c:when test="${name == 'hato'}">管理者さん、こんにちは</c:when> <c:when test="${name == ''}">名無しさん、こんにちは</c:when> <c:otherwise>一般ユーザーさん、こんにちは</c:when> </c:choose>
タグ 要素 内容 choose - このタグで囲った中身が一連のif文ブロック。 when - if文のif elseに相当。 when item このitemがtrueの場合、タグで囲まれた部分が実行。 otherwise - if文のelseに相当。whenのどこにもヒットしなかった場合に実行。
- for文
LoginServlet.javaList<String> list=Arrays.asList("one","two","three","four"); req.setAttribute("list",list);mypage.jsp<c:forEach var="v" items="${list}" varStatus="list"> ${v} ${list.count} <!-- one 1 two 2 three 3 four 4 が出力 --> </c:forEach> <c:forEach begin="1" end="7" step="2" varStatus="c"> hello ${c.count} <!-- hello 1 hello 2 hello 3 が出力 --> </c:forEach>
タグ 要素 内容 forEach var itemsから取り出した要素にアクセスする変数 items コレクションを指定(大体はEL式を書く) varStatus 繰り返しの状態にアクセスする変数(ループ回数など) begin ループの開始位置 end ループの終了位置 step 増加量(基本は1) 上のforEachは拡張for文
exFor.javafor(String item:list){ System.out.println(item); }下のforEachは普通のfor文
For.javafor(int i=1;i<=7;i=i+2){ System.out.println("hello"+i); }と挙動が似ているので覚えやすいです。
その他のタグについて
coreライブラリには他にも
<c:param>
や<c:set>
などありますが、全てを説明するには長いので省略します。また、ライブラリもformat
、xml
など豊富にそろっています。まとめ
- EL式で
<% request.getAttribute("hogehoge"); %>
から卒業- JSTLで
<% if(~) %>
、<% for(~) %>
から卒業 実用的に作る場合はjavascriptはどうしても必要になってきますが、EL式とJSTLを駆使することでjavaのロジックを書く必要がなくなり、jspファイルを簡潔にすることができます。