- 投稿日:2020-11-29T23:32:29+09:00
Java abstractキーワード
1,abstractとは
abstractクラスは、インスタンス化することができませんが、サブクラス(子クラス)を持つことができます。
abstractメソッドは、実装しない宣言です、2,ソースコード
Main.javapublic class Main { public static void main(String[] args) { // abstract = abstract classes cannot be instantiated, but they can have a subclass // abstract methods are declared without an implementation Car car = new Car(); car.go(); } }スーパークラス(親クラス)には、abstractがついているのでインスタンス化できません?
Vehicle.javapublic abstract class Vehicle { abstract void go(); }親クラスに、abstractメソッドがあるので、必ずオーバーライドする必要があります?
Car.javapublic class Car extends Vehicle { @Override void go() { System.out.println("The driver is driving the car."); } }
- 投稿日:2020-11-29T22:53:51+09:00
Java superキーワード
1,superキーワードとは
スーパークラス(親クラス)のオブジェクトを参照するキーワードです。
2,ソースコード
Main.javapublic class Main { public static void main(String[] args) { // super = keyword refers to the superclass (parent) of an object // very similar to the "this" keyword Hero hero1 = new Hero("Batman", 42, "$$$"); Hero hero2 = new Hero("Superman", 43, "everything"); System.out.println(hero1.name); System.out.println(hero1.age); System.out.println(hero1.power); System.out.println(hero2.toString()); } }Personクラス(スーパークラス)?
Person.javapublic class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } public String toString() { return this.name + "\n" + this.age + "\n"; } }Personクラスを継承しているHeroクラス?
Hero.javapublic class Hero extends Person { String power; public Hero(String name, int age, String power) { super(name, age); this.power = power; } public String toString() { return super.toString() + this.power; } }
- 投稿日:2020-11-29T22:44:37+09:00
Groovyの最新動向について紹介してみる
これは、NTTテクノクロス Advent Calendar 2020の初日の記事です。
はじめに
こんにちは。NTTテクノクロスの中野です。
結構前になってしまいますが、会社ブログでGroovy/Grailsの記事を書いてました。ちなみに、この社外ブログでの最新の執筆記事は、2020/10/02に公開したNTT Performance Tuning Challenge 2020 で優勝してきたです(ドヤァ)。このコンテストでもGo言語版を選択したように、最近は業務でもGo言語を使うことが多いのですが、もちろん今もGroovy/Grailsが大好きです。
というわけで、今日は久しぶりにGroovyの情報を紹介していきたいと思います。
Groovy 3.0 がリリースされました!
もう少しで1年経ちそうなくらいなので「何をいまさら」な感じですが、今年の2020/02/10にGroovy 3.0がリリースされています。主なポイントを以下に紹介します。
Javaとのシンタックス互換性の飛躍的向上
Daniel Sun氏の活躍で実装されたParrotパーサーによって、Javaとのシンタックス互換性が一気に高まりました。
元々Groovy 2.x系でもJava 7以前との互換性はかなり高く、Java 7のソースコードをほぼ修正なしでGroovyのソースコードとして扱えたのですが、Java 8以降に採用されたシンタックスへの追随がかなりビハインド状態で、「Javaシンタックスとの親和性」について声高にアピールしづらいもやもや期間が長く続いていました。この状況が、Groovy 3.0のParrotパーサーによって、ついに解消されたわけです。
新しくサポートされたシンタックスをいくつか紹介すると...
- Groovyのクロージャを書く時に、Javaのラムダ記法がつかるようになった
- for文の初期化式としてカンマ区切りで複数の変数の初期化が書けるようになった
- (例):
for(int i = 0, j = 10; i < j; i++, j--) {..}
- Javaと同じ配列の初期化記法が使えるようになった
- (例):
new int[]{1, 2, 3}
- do-while文が使えるようになった
- try-with-resourcesが使えるようになった
- スコープを分けるブロックとして機能するだけの波括弧が使えるようになった
- 以前は、クロージャ記法なのかブロックなのかが曖昧になるため、エラーとしていた
- メソッド参照が使えるようになった
- などなど
- ※ 動作可能なサンプルコードがみたい方はこちらのGistを参照のこと
これで「GroovyはJavaシンタックスとの親和性が高い」と胸を張っていえるようになりましたね。
とはいえ、JavaはJavaでどんどん改善が進んでいて新しいシンタックスが増えていっているので、いたちごっこはまだまだ続くわけですが、新しいParrotパーサーではそういった新しいシンタックスへの追随も柔軟に対応できるようなので、それほど悲観的になる必要はないでしょう。
なお、Groovy 3.0には従来のレガシーパーサーも同梱されているので、以前の挙動を望む場合には自分で切り替えることもできます(Groovy 4.0で削除予定)。
JPMS(Jigsaw)対応
JPMS(Jigsaw)では、モジュールごとに個別のパッケージ名を付ける必要があります。元々Groovyには独自の「モジュール」がありましたが、これはJPMSが要求するようなパッケージ分離性を満たしていませんでした。JPMS要件を満たすためには、多数のクラスを元とは異なるパッケージに移動する必要がありました。しかし、いきなり「移動」してしまうと、既存のGroovyソースコードがGroovy 3.0でコンパイル/実行できなくなってしまいます。そこで、Groovy 3.0では移行期間として、ひとまず「移動」ではなく「コピー」することになりました。
- 実際にコピーされたクラスの例
groovy.util.XmlParser
→groovy.xml.XmlParser
groovy.util.XmlSlurper
→groovy.xml.XmlSlurper
groovy.util.GroovyTestCase
→groovy.test.GroovyTestCase
Groovy 3.0では、
groovy.util.XmlParser
などの古いパッケージ側のクラスも残されていますが、これはGroovy 4.0で削除される予定です。今から実装する場合は、新しいパッケージ側のクラスを使うようにしましょう。というわけで、実際にJPMS準拠となるのはGroovy 4.0からの予定です。Groovy 3.0の段階ではまだJPMS準拠のモジュールとしては使えません。従来通り、クラスパスに配置する必要があります。
Groovy 4.0 はどうなる?
今のところリリース時期は未定のようですが、次のメジャーバージョンであるGroovy 4.0の開発が着々と進められています。
ちなみにアルファ版(4.0.0-alpha-1)はすでに公開されていて、SDKMAN!を使っていれば、以下のコマンドで簡単にインストールして試すことができます。
> sdk install groovy 4.0.0-alpha-1さて、Groovy 4.0でのおもな変更点をピックアップして紹介していきましょう。
JPMS(Jigsaw)対応
前述したとおり、Groovy 3.0で仕込んでおいたJPMS対応の大詰めです。互換性のためにGroovy 3.0で残してあった旧パッケージ側のクラス(または旧パッケージそのもの)がすべて削除されます。古いパッケージのクラスを使っているソースコードは、新しいパッケージの方を使うように修正する必要があります。これによって、JPMS準拠のモジュールとしてGroovyを利用できるようになります(のはず)。
レガシーパーサーの削除
Parrotパーサーのみが同梱されて、以前のレガシーパーサーは削除されます。
Maven/GradleのグループID変更
かなり前になりますが、GroovyはCodehaus1というホスティングサービスを使ってホスティングしていた時期があります。その時期に付けられた
org.codehaus.groovy
というMaven/GradleのグループIDがいまだにそのまま使われ続けていたのですが、ついにというかやっとというか、org.apache.groovy
に変更されます。ビルドスクリプトなどの依存関係定義で、Groovyのバージョンを4に変更するときには、忘れずにグループIDも修正しましょう。新機能: 組み込みの型チェッカー
従来からGroovyの静的型付けチェックには、選択的に特定の型チェックを弱めて動的なコードを通しやすくしたり、逆にむしろJavaよりも厳しくチェックしたりする機能があったようです(そうだっけ?)。一部では使われていたもののあまり広まってなかったので、普及のために使えそうなものを組み込みで用意してみたよ、ということのようです。
たとえば、以下のコードには問題があるのですが、どこかわかりますか?
def whenIs2020Over() { def newYearsEve = '2020-12-31' def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/ // --[1] //... }そうですね。[1]の部分の正規表現の末尾で閉じ丸括弧が漏れているため、実行すると
PatternSyntaxException
がスローされてしまいます。従来であれば、これは実行時エラーとしてしか検出できませんが、Groovy 4.0からは以下のようにアノテーションを付与すると...import groovy.transform.TypeChecked @TypeChecked(extensions = 'groovy.typecheckers.RegexChecker') def whenIs2020Over() { def newYearsEve = '2020-12-31' def matcher = newYearsEve =~ /(\d{4})-(\d{1,2})-(\d{1,2}/ //... }コンパイル時に検出できるようになります。
1 compilation error: [Static type checking] - Bad regex: Unclosed group near index 26 (\d{4})-(\d{1,2})-(\d{1,2} at line: 6, column: 19これは、オプションのgroovy-typecheckersモジュールの機能として提供されます。
新機能: 組み込みのマクロメソッド
Groovy 2.5でGroovy Macroという機能が導入されました。C/C++のマクロのように、グローバル関数のようなものをソースコードに書いておくと、AST変換によって実コードが展開されてコンパイルされる、という仕組みなのですが、こっちも普及のためにいくつか使えそうなものを標準で用意してみることにしたよ、という話のようです。
その一つとして、昔ながらのプリントデバッグを支援するマクロが用意されています。たとえば、次のようにいくつかの変数を定義したとします。
def num = 42 def list = [1, 2, 3] def range = 0..5 def string = 'foo'ここで、デバッグ目的でこれらをプリントしたいとします。
println "num=$num, list=$list, range=$range, string=$string"と頑張って書けば、
num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=fooと出力できますが、ちょっとタイピング量が多すぎる感じがありますね。
新しく追加された
NV
マクロメソッドを使ってみます。println NV(num, list, range, string)これを実行すると、次のように出力されます。
num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo変数名だけ列挙していけば良いので、たしかにちょっと手軽になった感じがします。
同じ系統で、
NVI
とNVD
という2つのバリエーションがあります。それぞれの違いは、値の文字列化の方式です。
NV
: 値の文字列化にtoString()
を使うNVI
: 値の文字列化にGroovy JDKのinspect()
を使うNVD
: 値の文字列化にGroovy JDKのdump()
を使うさきほどの
range
変数を出力して違いをみてみると、こんな感じです。println NV(range) //=> range=[0, 1, 2, 3, 4, 5] println NVI(range) //=> range=0..5 println NVD(range) //=> range=<groovy.lang.IntRange@14 from=0 to=5 reverse=false inclusive=true modCount=0>これは、オプションのgroovy-macro-libraryモジュールの機能として提供されます。
新機能: JavaShell (incubating)
Groovyのソースコードを動的にコンパイルして実行する
GroovyShell
というクラスが元々ありますが、これのJava版として、Javaのソースコードを動的にコンパイルして実行するJavaShell
が追加されます。たとえば、JDK 14で追加されたrecord機能を使った文字出力内容を検証するためのGroovyコードは次のようにかけます。import org.apache.groovy.util.JavaShell def opts = ['--enable-preview', '--release', '14'] def src = 'record Coord(int x, int y) {}' Class coordClass = new JavaShell().compile('Coord', opts, src) assert coordClass.newInstance(5, 10).toString() == 'Coord[x=5, y=10]'また、標準バンドルされているGroovy Consoleで、エディタ上のソースコードをJavaとして実行するための「Run as Java」メニューが追加されます。
新機能: POJO Annotation (incubating)
これは相当に野心的・実験的な機能です。
基本的に、GroovyでPOJOのようなクラスを書いたとしても、コンパイルしたクラスの実行時にはGroovyのJARファイルが必要になります。Groovyのクラスが持つ動的性質を実現するために、専用の下回りの機構が必要となるためです。これに対して、クラスに新しい
@POJO
アノテーション(と既存の@CompileStatic
)をつけることで、そのクラスの動的性質に関わるすべてをクラスファイルに組み込んでしまい、実行時にGroovyのJARファイルがなくてもピュアJavaの世界でそのまま実行できるようにしてしまおう、というのがこの新機能になります。もし、これが実用化できると、Groovyをプリプロセッサとして利用できる可能性がでてきます。Lombokに似ていますが、Groovyの言語パワーを活かせるため、より高度な応用が期待できそうです。
ただし、徐々に改善していけるという見込みはあるらしいものの、現状でGroovyのすべての動的性質をサポートできているわけではないので、まだまだincubating状態が続きそうな気配です。
新機能: recordライクなクラス (incubating)
Java 14で、プレビュー機能としてrecordが導入されました。これを受けて、Groovyでもrecordライクなクラスを書くためのサポートが追加されます。
元々Groovyでは、Groovy Beansとして、Java Beansを強力に拡張する機能が用意されています。たとえば、こんな感じです。
class Book { String title String author } // Mapを受け取るコンストラクタが自動生成されている def book = new Book(title: "プログラミングGROOVY", author: "..., 中野") // getterが自動生成されている assert book.getTitle() == "プログラミングGROOVY" // setterが自動生成されている book.setTitle("JavaからGroovyへ") assert book.getTitle() == "JavaからGroovyへ" // インスタンス変数への直接参照のようにみえるがgetterを経由している assert book.title == "プログラミングGROOVY" // インスタンス変数への直接代入のようにみえるがsetterを経由している book.title = "JavaからGroovyへ" assert book.title == "JavaからGroovyへ"また、Groovyには複数のAST変換アノテーションを集約して1つのアノテーションとして再利用可能にするメタアノテーションという仕組みがあります。たとえば、クラスの不変性を実現するための
@Immutable
アノテーションを1つ指定するだけで、次に列挙するサブアノテーションが持っている各機能がAST変換によって付与されます。// @Immutableとしてのベース @ImmutableBase // クラスを不変(immutable)にするための各種サブアノテーション @ImmutableOptions @PropertyOptions(propertyHandler = ImmutablePropertyHandler) @KnownImmutable // toString()を自動生成する @ToString(cache = true, includeSuperProperties = true) // equals()とhashCode()を自動生成する @EqualsAndHashCode(cache = true) // インスタンス変数の定義順で受け取るコンストラクタを自動生成する @TupleConstructor(defaults = false) // Mapを受け取るコンストラクタを自動生成する @MapConstructorさて、Java 14のrecordクラスは、このGroovyの
@Immutable
を付与したクラスとある程度類似しているのですが、完全には一致していません。とはいえ、Groovyのメタアノテーションを使えば、recordとの差分を調整した新しい@RecordType
アノテーションを追加するのは比較的簡単です。これには、以下のようなサブアノテーションが集約されます。// @RecordTypeとしてのベース @RecordBase // ピュアJavaな世界で実行可能なクラスファイルを生成する(前項で説明) @POJO // クラスを不変(immutable)にするための各種サブアノテーション(@Immutableと共通) @ImmutableOptions @PropertyOptions(propertyHandler = ImmutablePropertyHandler) @KnownImmutable // toString()を自動生成する(@Immutableと共通) @ToString(cache = true, includeNames = true) // equals()とhashCode()を自動生成する(@Immutableと共通) @EqualsAndHashCode(cache = true, useCanEqual = false) // インスタンス変数の定義順で受け取るコンストラクタを自動生成する(@Immutableと共通) @TupleConstructor(defaults = false) // Mapを受け取るコンストラクタを自動生成する(@Immutableと共通) @MapConstructorこれを使った例を見てみましょう。
import groovy.test.GroovyAssert @groovy.transform.RecordType class Book { String title String author } // インスタンス変数の定義順で受け取るコンストラクタが自動生成されている def book = new Book("プログラミングGROOVY", "..., 中野") // インスタンス変数名と同名のgetterが自動生成されている assert book.title() == "プログラミングGROOVY" // Groovy Beansとしてのgetter/setterは生成されない // (最後まで一通り実行できるように、期待する例外がスローされたかどうかをチェック // するGroovyのアサーション機能を使っている) GroovyAssert.shouldFail(MissingMethodException) { book.getTitle() //=> Caught: groovy.lang.MissingMethodException: No signature of method: Book.getTitle() ... } GroovyAssert.shouldFail(MissingMethodException) { book.setTitle("JavaからGroovyへ") //=> Caught: groovy.lang.MissingMethodException: No signature of method: Book.setTitle() } // インスタンス変数への代入は禁止されている // (Groovy BeansとしてのsetTitle()は生成されていないため、これはインスタンス変数への直接代入を意味する) GroovyAssert.shouldFail(ReadOnlyPropertyException) { book.title = "JavaからGroovyへ" //=> Caught: groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: title for class: Book } // toString()が自動生成されている assert book.toString() == "Book(title:プログラミングGROOVY, author:..., 中野)" // equals()が自動生成されている assert book == new Book("プログラミングGROOVY", "..., 中野") assert book != new Book("プログラミングGROOVY", "..., なかの")将来のリリースでは、Javaのrecordと同等の記述ができるように、次のようなシンタックシュガーを提供する可能性があります。
// 注意: この構文はGroovy 4.0ではサポートされない record Book(String title, String author){}ひとまず当面は、
@RecordType
を提供してみてユーザからのフィードバックを求める、という方針のようです。新機能: Groovy Contracts (incubating)
契約による設計(Design by Contract)のために、クラス不変条件/前提条件/事後条件の指定をサポートするアノテーションを提供します2。付与されたアノテーションに従って、AST変換によって必要に応じてコンストラクタ/メソッドにチェックロジックが挿入されて、以下の3点を確認します。
- メソッドが実行される前に前提条件が満たされていること
- メソッドが実行された後に事後条件が保持されていること
- メソッドが呼び出される前後にクラス不変条件が真であること
たとえば、ロケットを表現するクラスで試してみるとこうなります。
import groovy.contracts.Ensures import groovy.contracts.Invariant import groovy.contracts.Requires import groovy.test.GroovyAssert import org.apache.groovy.contracts.ClassInvariantViolation import org.apache.groovy.contracts.PostconditionViolation import org.apache.groovy.contracts.PreconditionViolation @Invariant({ speed() >= 0 }) // --[1] class Rocket { boolean started = false int speed = 0 @Requires({ isStarted() }) // --[2] @Ensures({ old.speed < speed }) // --[3] def accelerate(inc) { speed += inc } def isStarted() { started } def speed() { speed } } // 初期速度が負の場合、[1]のクラス不変条件違反が検出されて例外がスローされる // (最後まで一通り実行できるように、期待する例外がスローされたかどうかをチェック // するGroovyのアサーション機能を使っている) GroovyAssert.shouldFail(ClassInvariantViolation) { new Rocket(speed: -1) //=> // Caught: org.apache.groovy.contracts.ClassInvariantViolation: <groovy.contracts.Invariant> Rocket // // speed() >= 0 // | | // -1 false } def r = new Rocket() assert !r.started assert r.speed == 0 // start=falseの場合、[2]によって事前条件違反が検出されて例外がスローされる GroovyAssert.shouldFail(PreconditionViolation) { r.accelerate(5) //=> // Caught: org.apache.groovy.contracts.PreconditionViolation: <groovy.contracts.Requires> Rocket.java.lang.Object accelerate(java.lang.Object) // // isStarted() // | // false } // 事前条件違反なので、内部状態自体は更新されていない assert r.speed == 0 // start=trueの場合、[2]に違反しないため、速度を加速できる r.started = true r.accelerate(5) assert r.speed == 5 // 減速(負の加速)は、[3]によって事後条件違反が検出されて例外がスローされる GroovyAssert.shouldFail(PostconditionViolation) { r.accelerate(-1) //=> // Caught: org.apache.groovy.contracts.PostconditionViolation: <groovy.contracts.Ensures> Rocket.java.lang.Object accelerate(java.lang.Object) // // old.speed < speed // | | | | // | 5 | 4 // | false // ['speed':5] } // 事後条件違反なので、内部状態自体は更新済み assert r.speed == 4なかなか使い勝手が良さそうに見えますね。
新機能: GINQ (under investigation)
Parrotパーサーの立役者であるDaniel Sun氏が今まさに精力的に開発中の、.NETの統合言語クエリ(LINQ: Language-Integrated Query)という機能のGroovy版となるGINQです。
統合クエリ言語といってもSQLやDBとは直接は関係ありません。ざっくりいうと、様々なコレクション的な入力3に対する操作を専用のシンタックスで記述できるというものです。Groovyでは元々
each
,map
,findAll
などのコレクション操作がかなり充実しているので「既存のそれと何が違うの?」というのは自然な疑問だと思います。かく言う自分もまだあまりよくわかっていないのですが、GINQユーザガイド(2020/11/28版)などをみると、既存のGroovyのコレクション操作がLINQ的に言う「メソッド構文」であるのだとしたら、今回のGINQはLINQ的に言う「クエリ構文」を実現したものなのかな、という感じがします。ここでいう、クエリ構文というのはSQLに似たLINQ固有のDSLです。ちなみに、筆者はまだLINQよくわかってない勢なので、GINQのシンタックスが本家や他のLINQ実装のシンタックスと共通なのか、なにか違いがあるのか、などは不明です。
さて、GINQを利用したごく簡単なサンプルコードをいくつか紹介するとこんな感じです。
assert [2, 4, 6] == GQ { from n in [1, 2, 3] select n * 2 }.toList() // Groovyの既存コレクション操作で書くと... assert [2, 4, 6] == [1, 2, 3].collect { it * 2 }assert [[1, 1], [2, 4], [3, 9]] == GQ { from v in ( from n in [1, 2, 3] select n, Math.pow(n, 2) as powerOFN ) select v.n, v.powerOFN }.toList() // Groovyの既存コレクション操作で書くと... assert [[1, 1], [2, 4], [3, 9]] == [1, 2, 3].collect { [it, Math.pow(it, 2) as int] }どうなんでしょう。上の例だと、既存のコレクション操作の方が圧倒的に簡潔でわかりやすいですが、もっと複雑なことをしようとしたときに何かメリットがでてくるのでしょうか。
入力の多様性という観点からすれば、入力となるデータソースが、コレクション(
java.lang.Iterable
)、ストリーム(java.util.stream.Stream
)、配列、JSON、RDBMS、先行するGINQの出力結果のいずれであっても、変わらずに同じシンタックスでクエリがかける、というのはデータサイエンティスト的にはうれしい場面がありそうな気もします。というわけで、まだ全然わかっていないので、今後も注目していきたいと思います。
ちなみに、GINQは、現在公開されているGroovy 4.0のアルファ版(4.0.0-alpha-1)には含まれていないようで、上記のサンプルは実行できませんでした。GINQユーザガイドも高頻度で更新されつつある状況なので、手元で試せるのはもうちょっと先になりそうな雰囲気です。
なお、すでにPHPにもGinqというLINQクローンがあって大変紛らわしいのですが、GrailsによるORマッパーの「GORM」に対して、Go言語によるORマッパーの「gorm」が後発で登場するなど、昨今は名前カブりに対してあまり気にしない世の中なんでしょうかね。ググラビリティが低くなるので、ユーザとしては困り物ですが。
おわりに
さて、だいぶ長くなってしまいましたが、Groovyの最新動向はいかがでしたでしょうか。
明日は、j-yamaによるElastic Stackに関する記事です。引き続き、NTTテクノクロス Advent Calendar 2020をお楽しみください。
参考URL
- [InfoQ] 新しく改良されたパーサを備えたGroovy 3.0への長い道
- Release notes for Groovy 3.0
- Release notes for Groovy 4.0
- GINQユーザガイド
Codehausはすでにサービス終了しています。 ↩
かなり昔にあったGContractsの標準版という感じです。なお、GContractsはだいぶ前からメンテナンスが停止していて、アーカイブ化されています。 ↩
コレクション(
java.lang.Iterable
)、ストリーム(java.util.stream.Stream
)、配列、JSON、RDBMS、先行するGINQの出力結果、などが入力のデータソースとして使用できます。 ↩
- 投稿日:2020-11-29T22:25:49+09:00
Java 継承(inheritance)
1,継承(inheritance)とは
あるクラスが他のクラスから属性(attributes)やメソッド(methods)を取得するプロセスです
2,ソースコード
Main.javapublic class Main { public static void main(String[] args) { // inheritance = the process where one class acquires, // the attrivutes and methods of another. Car car = new Car(); car.go(); Bicycle bike = new Bicycle(); bike.stop(); System.out.println(car.speed); System.out.println(bike.speed); System.out.println(car.doors); System.out.println(bike.pedals); } }継承元?
Vehicle.javapublic class Vehicle { double speed; void go() { System.out.println("This vehicle is moming"); } void stop() { System.out.println("This vehicle is stopped"); } }クラスからクラスへ継承する場合、”子クラス extends 親クラス” と表記します?
Car.javapublic class Car extends Vehicle { int wheels = 4; int doors = 4; }Bicycle.javapublic class Bicycle extends Vehicle { int wheels = 2; int pedals = 2; }
- 投稿日:2020-11-29T21:47:18+09:00
Java static修飾子
1,static修飾子とは
クラスのフィールド変数やメソッドをそのクラス内で共有します。
staticがついているものはそのクラスが所有することになります。2,ソースコード
Main.javapublic class Main { public static void main(String[] args) { // static = modifier. A single copy of a variabole/method is created and shared. // the class "owns" the static member Friend friend1 = new Friend("hiro"); Friend friend2 = new Friend("tato"); Friend friend3 = new Friend("yoshi"); Friend friend4 = new Friend("taka"); System.out.println(Friend.numberOfFriends); Friend.displayFriends(); } }static がついているフィールド変数やメソッドはそのクラスで共有します。
Friend.javapublic class Friend { String name; static int numberOfFriends; public Friend(String name) { this.name = name; numberOfFriends++; } static void displayFriends() { System.out.println("You have " + numberOfFriends + " friends"); } }
- 投稿日:2020-11-29T21:39:00+09:00
Transformation Advisor Localをインストールした時の手順メモ
1. はじめに
「TomcatのJavaアプリ(VM環境)をTransformation Advisorを使ってモダナイズする 」という投稿で、利用する Transformation Advisor Localのインストールをこの投稿で記述します。
このツールの使用例については、上記ページも参照ください。
2. Transformation Advisor Localとは
まず、Transformation Advisorはオンプレミス環境で動作しているJavaのミドルウェア・アプリケーションを分析し、生成されたマイグレーションバンドルを使って、 OpenShift の Libertyコンテナ上にJavaアプリをデプロイすることができるJavaアプリケーションのモダナイズするための移行支援ツールです。 (IBMの製品です。)
そして、Transformation AdvisorはOpenShift上にインストールしますが、
今回インストールするTransformation Advisor Localは、Windows・Linux・MacOSと、ローカルPCでもインストールして、Transformation Advisorを利用することができるツールです。詳細はこちら:IBM Cloud Transformation Advisor
仕組みとしては、ローカルPCに、DockerとDocker Composeをインストールして、Transformation Advisorのコンテナイメージを起動して、Transformation Advisorを利用します。
ローカルPCにインストールすることができるで気軽に試すことができますが、ローカルPCの性能によっては、Dockerを動かす時点で、もっさりと遅いという事が起こるかもしれません。
3. Transformation Advisor Localのインストール
今回は、Transformation Advisor Localの90日評価版をLinux ( CentOS7 )の環境にインストールする手順を確認しました。
3.1 前提
- 導入環境:
- VMWare ESXi 6.7 の仮想マシン( スペックは適当に設定CPU4コア, メモリ8GB, HDD 32GB)
- OS: CentOS Linux release 7.8.2003 (Core)の Minimam Install
- インストール手順書: Installing IBM Cloud Transformation Advisor locally
3.2 手順
DockerとDocker Composeのインストール
- 作業環境: CentOS7.8
Dockerの導入手順 https://docs.docker.com/get-docker
に沿って下記の手順でDockerをインストールします。$ sudo yum install yum-utils # docker のyum リポジトリ追加 $ sudo yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce.repo # Dockerのインストール $sudo yum install docker-ce docker-ce-cli containerd.io #Dockerの起動 $sudo systemctl start docker # Dockerの動作確認 $ sudo docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 0e03bdcc26d7: Pull complete Digest: sha256:e7c70bb24b462baa86c102610182e3efcb12a04854e8c582838d92970a09f323 Status: Downloaded newer image for hello-world:latestDocker Composeのインストール
- 作業環境: CentOS7.8
Docker Composeの導入手順 https://docs.docker.com/compose/install/
に沿って下記の手順でDockerをインストールします。#docker-composeコマンドのダウンロード&設置 $ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose #docker-composeコマンドに実行権限追加 $ sudo chmod +x /usr/local/bin/docker-compose $ ls -l /usr/local/bin/docker-compose -rwxr-xr-x. 1 root root 12218968 Nov 29 04:34 /usr/local/bin/docker-composeTransformation Advisor Localのダウンロード
- 作業環境: ローカルPC
以下のURLよりTransformation Advisor Localの90日評価版をダウンロードします。(IBMアカウントが必要になります。)
https://www.ibm.com/account/reg/signup?formid=urx-38642ダウンロードページにて、「Transformation Advisor Localのインストールスクリプト」をダウンロードします。
Transformation Advisor Local の CentOS7.8へのインストール
- 作業環境: CentOS7.8
事前にローカルPCから CentOS7.8の環境に、先ほどダウンロードしたTransformation Advisor Localのバイナリ「 transformationAdvisor.zip」をアップロードしてください。
そして、以下の手順でインストールします。
#必要なツールのインストール(unzipとnetstatを導入) $ yum install -y unzip net-tools # Transformation Advisor Localの解凍 $ unzip transformationAdvisor.zip $ ls launchTransformationAdvisor.sh LICENSE LICENSES scripts transformationAdvisor.zipインストールのために「 ./launchTransformationAdvisor.sh 」を実行します。
$ sudo ./launchTransformationAdvisor.sh [sudo] password for dai: Docker Compose not installed. Please install docker-compose and re-run the script. https://docs.docker.com/compose/install/しかし、docker-composeがインストールされていないとメッセージが表示されます。これは、sudoのsecure_pathに docker-composeのインストールされているパスが登録されていないために、発生する事象です。
下記の様にvisudoを実行して、secure_pathに「 /usr/local/bin 」を追加してください。
$ sudo visudo (変更前)88 Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin (変更後)88 Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin再度、「launchTransformationAdvisor.sh」を実行して、インストールプログラムを起動します。 ライセンス情報が表示され、最後に、同意するか同意しないかの選択を 同意する 1 の選択をします。
sudo ./launchTransformationAdvisor.sh LICENSE INFORMATION The Programs listed below are licensed under the following License Information terms and conditions in addition to the Program license terms previously agreed to by Client and IBM. If Client does not have previously agreed to license terms in effect for the Program, the International License Agreement for Evaluation of Programs (Z125-5543-05) applies. Program Name (Program Number): IBM Cloud Transformation Advisor local 2.3.0 (Evaluation) The following standard terms apply to Licensee's use of the Program. ' ----- 省略 ----- ' 1) I have read and agreed to the license agreements 2) Don't accept the license agreementsその後、下記のようなオペレーションメニューが表示されるので、「1) Install Transformation Advisor」の 1 を入力してEnterを押してインストールを開始します。
Select the operation....... 1) Install Transformation Advisor 2) Uninstall Transformation Advisor (keep database data) 3) Uninstall Transformation Advisor (remove database data) 4) Stop Transformation Advisor 5) Start Transformation Advisor 6) Check for latest Transformation Advisor 7) Working in an Air Gapped Environment 8) Quitその後、正常にインストールが環境すると、下記の様にTransformation AdvisorのURLが表示されます。
Installing Transformation Advisor.............. Pulling db ... done Pulling server ... done Pulling ui ... done Creating network "scripts_default" with the default driver Creating scripts_db_1 ... done Creating scripts_server_1 ... done Creating scripts_ui_1 ... done Configuring Transformation Advisor............................................... Status ------------------------------------------------------------------------------------------------------ Transformation Advisor 2.3.0 is available for us at the following URL> http://192.168.26.253:3000Transformation Advisor への接続
- 作業環境: ローカルPC
ローカルPCのブラウザにて、前手順のインストール完了時に出力されるURLに接続し、Transformation Advisorの画面が表示されることを確認します。
4. 最後に
これで Transformation Advisor を利用することができるようになりました。
今回は、 Linux環境の手順を説明しましたが、Windows・MacOSの環境でも Docker・Docker Composeが導入済みの環境ならばインストールすることができます。Transformation Advisor の具体的な使用例は、「TomcatのJavaアプリ(VM環境)をTransformation Advisorを使ってモダナイズする 」の投稿をぜひ、ご覧ください。
- 投稿日:2020-11-29T21:23:39+09:00
GCP: Google Cloud Functions から Google Cloud Storage のファイルを読み込む
参考
公式ドキュメントだとStorageにファイル作成する例しかなかったので、こちらのQiita記事を参考にさせていただきました。
ありがとうございます!
Cloud Storage Client Libraryを使ってGCS上のファイルをReadする - Qiita概要
Google Cloud Functions (Java11) から Google Cloud Storage 上のファイルを読み込む。
- ファイルの内容
- テキストファイル
- 1行1データ
- 改行コードはLF
- 日本語含む
- UTF-8
やったこと
Storageにバケットを作って、ファイルを置く
バケットはStorageの管理単位のイメージです。
GCPコンソール > Storage メニューから「+バケットを作成」します。
以下を設定していきます。
- バケット名
- バケット名は一般公開されます
- そのため機密情報や既に使われている値は利用できません
- 命名ガイドライン
- データの保存場所
- データのデフォルトのストレージ クラス
- オブジェクトへのアクセスを制御する方法
作成したバケットの詳細を開き、「ファイルをアップロード」からファイルをアップします。
公開アクセスは「非公開」になっています。
Functionsを作る
GCPコンソール > Cloud Functions メニューから「+関数を作成」します。
関数名、リージョンは適宜設定します。検証しやすいので、トリガーは「HTTP」にしました。
認証も「未認証の呼び出しを許可」にしておきます。構成を決めたらコードを用意します。
ランタイムは「Java11」を選択して、サンプルソースを下記のように書き換えます。Example.javapackage com.example; import com.google.cloud.functions.HttpFunction; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; import com.google.cloud.storage.*; import java.io.UnsupportedEncodingException; import java.io.BufferedWriter; public class Example implements HttpFunction { @Override public void service(HttpRequest request, HttpResponse response) throws Exception { response.setContentType("Content-Type: text/plain; charset=UTF-8"); BufferedWriter writer = response.getWriter(); String name = ""; if (!request.getFirstQueryParameter("name").isEmpty()) { name = request.getFirstQueryParameter("name").get(); } if(name.isEmpty()){ writer.write("Query parameter 'name' is empty"); return; } String[] datas = getBlobData("XXXXXX_projectId", "XXXXXX_bucketName", name); for(String data : datas){ writer.write(String.format("'%s'%n", data)); } } public String[] getBlobData(String projectId, String bucketName, String blobName) { Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService(); BlobId blobId = BlobId.of(bucketName, blobName); Blob blob = storage.get(blobId); byte[] content = blob.getContent(); try { return new String(content, "UTF-8").split("\n", 0); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return new String[0]; } }pom.xmlは下記を追加します。
pom.xml<dependency> <groupId>com.google.cloud</groupId> <artifactId>google-cloud-storage</artifactId> <version>1.113.4</version> </dependency>最後に「デプロイ」してエラーがなければOK!
挙動確認
FunctionsのトリガーURLを呼び出します。
Chromeであればそのままbodyの内容が表示されるので、Storageに置いたファイルの内容が表示されれば成功です。感想
同じGCPプロジェクトの Functions から Storage なら、認証なしで参照できて便利でした。
ただ Functions で、しかもJavaでやる処理じゃない気がしています(とても遅い)。
そのうちGAEに乗せ換えるか、Javaから他の言語にする方がいいかなと感じてはいます。
- 投稿日:2020-11-29T20:42:37+09:00
【Java】SimpleDateFormat (日時の文字列操作)
【タイトル】SimpleDateFormat
日時フォーマットの変換をする。
【説明】
SimpleDateFormatクラスは、日付と時刻のフォーマット(書式)を扱うクラスです。
【使用場面】
- 年や月といったデータを処理中で使用したい場合
- ライブラリを使用する際、所定の形式へ変換する必要がある場合
- 日付操作の表示を任意で決めたいとき
Calendar 、Date、Time の日付や数値に代入された、「 日付・時刻形式データを、任意の日付・時刻フォーマットに変換する 」クラスとして重宝します。
【書き方】
まずはこちらをJavaファイルの上部のパッケージ宣言の後に記述してインポートします。
import java.text.SimpleDateFormat;宣言後、下記の形式でフォーマットを指定します。(書式とパターンは後述)
SimpleDateFormat オブジェクト名 = new SimpleDateFormat(”フォーマットパターン”)こちらは
SimpleDateFormat
で使用する 書式 と パターン をまとめたものになります。
こちらのアルファベットを用いてフォーマットを作成していきます。
書式 概要 パターン 表示 G 紀元 G AD y year, 年 yyyy
yyy2020
20M Month, 月 MM
MMM
MMMM03
Mar
Marchd day, 日 d
dd1
01H 時(0-23) H
HH0
00K 午前/午後の時(0-11) K
KK0
00h 午前/午後の時(1-12) h
hh1
01m 分 m
mm0
00s 秒 s
ss0
00S ミリ秒 S
SSS1
001z タイムゾーン z Pacific Standard Time, PDT, GMT-08:00 Z タイムゾーン Z -0800 java.text.SimpleDateFormatクラスのメソッド
SimpleDateFormat を使用する時に扱うメソッドの紹介です。
【説明】
SimpleDateFormat のメソッドの一例です。
- setTimeZone (タイムゾーンを設定)
- format (指定されたDateを日付文字列にフォーマットする)
- parse (文字列から解析してDateオブジェクトを生成)
その中でも特に重要な
formatメソッド
について少し触れます。formatメソッド
指定されたDateを日付文字列にフォーマットするメソッド。
【説明】
SimpleDateFormat を扱う上で最もメインとなる、「日時を設定したフォーマットの文字列へ変換する」メソッドです。
【書き方】
SimpleDateFormatのオブジェクト名.format(日時を格納した変数名)出力するときなどはこのような使い方です、
System.out.println(sdf.format(date));【サンプルコード】
// ① ここで SimpleDateFormat と Dateをインポート import java.text.SimpleDateFormat; import java.util.Date; public class MainSimpleDateFormat { public static void main(String[] args) { // ② 現在時刻を取得してくる値を 変数 date に格納 Date date = new Date(); // SimpleDateFormat をオブジェクト化し、任意のフォーマットを設定 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年 MM月 dd日"); // フォーマット指定なし System.out.println(date); // フォーマット指定あり System.out.println(sdf.format(date)); } }【実行結果】
Sun Nov 29 20:11:31 JST 2020 2020年 11月 29日【サンプルコード】 現在日付をyyyyMMdd形式の文字列で取得する
現在日付をyyyyMMdd形式で取得するサンプルです。
// ① ここでSimpleDateFormat と Calendarをインポート import java.text.SimpleDateFormat; import java.util.Calendar; public class MainSimpleDateFormat { public static void main(String[] args) { // ② Calendarをオブジェクト化 Calendar cl = Calendar.getInstance(); // ③ SimpleDateFormat のパターンを任意で設定する。 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年 MM月 dd日"); // ④ 文字列 str に SimpleDateFormat で整形した 日時 を格納する。 String str = sdf.format(cl.getTime()); // ⑤strを出力する System.out.println(str); } }【実行結果】
2020年 11月 29日【まとめ】
参考文献・記事
- 投稿日:2020-11-29T19:41:15+09:00
【Java】String型に変換するためのvalueOfメソッドとtoStringメソッドについて
プログラミング勉強日記
2020年11月29日
前にtoString()メソッドの使い方について扱ったが、今回valueO()メソッドでもString型に変換することができることを知った。それを踏まえてJavaでString型に変換するために使われる2種類のメソッドの違いとそれぞれの使い方についてまとめる。valueOfメソッドとは
引数に指定した様々な型の形をString型の文字列として返すことができる。
基本的な書き方String.valueOf(変換する値);valueOfメソッドのサンプルコード
short型、int型、long型、float型、double型をそれぞれvalueOfメソッドを使用してString型に変換する。
public class Main { public static void main(String[] args) throws Exception { // short型からString型に変換する short sh = 100; String strsh = String.valueOf(sh); System.out.println("short型からString型 : " + strsh); // int型からString型に変換する int num = 100; String strnum = String.valueOf(num); System.out.println("int型からString型 : " + strnum); // long型からString型に変換する long lon = 100; String strlon = String.valueOf(lon); System.out.println("long型からString型 : " + strlon); // float型からString型に変換する float fl = 100; String strfl = String.valueOf(fl); System.out.println("float型からString型 : " + strfl); // double型からString型に変換する double db = 100; String strdb = String.valueOf(db); System.out.println("double型からString型 : " + strdb); } }実行結果short型からString型 : 100 int型からString型 : 100 long型からString型 : 100 float型からString型 : 100.0 double型からString型 : 100.0toStringメソッドとは
数値型などをString型に変換するために使う。引数にString型の文字列に変換したい引数を指定し、戻り値はString型の文字列を返す。
toStringメソッドのサンプルコード
public class Main { pubic static void main(String[] args) { int num1 = 1234; int num2 = 5678; System.out.println(num1 + num2); // 数値を文字列に変換する String str1 = Integer.toString(num1); String str2 = Integer.toString(num2); System.out.println(str1 + str2); } }実行結果6912 12345678valueOfメソッドとtoStringメソッドの違い
基本的にはメソッドの目的は同じであるが、変換する対象の値がnullの場合の動作が異なる。
Integer型の値にnullを設定してString型に変換する場合、valueOfメソッドの場合はString型のnullを返すが、toStringメソッドの場合はnull参照時の例外が発生する。なので、toStringメソッドを使用する場合は変換する値がnullでどうか確かめる必要がある。参考文献
【Java入門】数値を文字列へ変換する方法(valueOf/toString)
【Java】toString()メソッドの使い方
- 投稿日:2020-11-29T19:41:15+09:00
【Java】String型に変換するvalueOfメソッドとtoStringメソッドについて
プログラミング勉強日記
2020年11月29日
前にtoString()メソッドの使い方について扱ったが、今回valueO()メソッドでもString型に変換することができることを知った。それを踏まえてJavaでString型に変換するために使われる2種類のメソッドの違いとそれぞれの使い方についてまとめる。valueOfメソッドとは
引数に指定した様々な型の形をString型の文字列として返すことができる。
基本的な書き方String.valueOf(変換する値);valueOfメソッドのサンプルコード
short型、int型、long型、float型、double型をそれぞれvalueOfメソッドを使用してString型に変換する。
public class Main { public static void main(String[] args) throws Exception { // short型からString型に変換する short sh = 100; String strsh = String.valueOf(sh); System.out.println("short型からString型 : " + strsh); // int型からString型に変換する int num = 100; String strnum = String.valueOf(num); System.out.println("int型からString型 : " + strnum); // long型からString型に変換する long lon = 100; String strlon = String.valueOf(lon); System.out.println("long型からString型 : " + strlon); // float型からString型に変換する float fl = 100; String strfl = String.valueOf(fl); System.out.println("float型からString型 : " + strfl); // double型からString型に変換する double db = 100; String strdb = String.valueOf(db); System.out.println("double型からString型 : " + strdb); } }実行結果short型からString型 : 100 int型からString型 : 100 long型からString型 : 100 float型からString型 : 100.0 double型からString型 : 100.0toStringメソッドとは
数値型などをString型に変換するために使う。引数にString型の文字列に変換したい引数を指定し、戻り値はString型の文字列を返す。
toStringメソッドのサンプルコード
public class Main { pubic static void main(String[] args) { int num1 = 1234; int num2 = 5678; System.out.println(num1 + num2); // 数値を文字列に変換する String str1 = Integer.toString(num1); String str2 = Integer.toString(num2); System.out.println(str1 + str2); } }実行結果6912 12345678valueOfメソッドとtoStringメソッドの違い
基本的にはメソッドの目的は同じであるが、変換する対象の値がnullの場合の動作が異なる。
Integer型の値にnullを設定してString型に変換する場合、valueOfメソッドの場合はString型のnullを返すが、toStringメソッドの場合はnull参照時の例外が発生する。なので、toStringメソッドを使用する場合は変換する値がnullでどうか確かめる必要がある。参考文献
【Java入門】数値を文字列へ変換する方法(valueOf/toString)
【Java】toString()メソッドの使い方
- 投稿日:2020-11-29T18:09:08+09:00
Spring Cloudを使ったマイクロサービスシステム構築(多重構成編)
概要
前回投稿したSpring Cloudを使ったマイクロサービスシステム構築(シングル構成編)の構成もとに、多重構成化する。
マイクロサービスの多重化
シングル構成だと、一つのマイクロサービスが停止した場合、そのサービスが利用できなくなる。またある特定のサービスへのアクセスが多い場合、多重化することでサービスを負荷分散できるようになる。
マイクロサービスのポートが同じなので、異なるサーバにマイクロサービスを構築する。
ms01
とms02
のサーバを用意し、それにマイクロサービスを配置する。各サーバで動作するマイクロサービスと受信ポートのイメージは以下のようなもの。
Service1-#3
はポートを変えるので後で設定する。Eurekaの多重化
Spring Cloudの本家の記事を参考にEurekaを
Peer Awareness
構成にする。
やっていることは、profilesを2つ用意して、それぞれがお互いのEurekaをサーバとして認識することっぽい。application.ymlserver: port: 8761 eureka: client: registerWithEureka: false fetchRegistry: false spring: cloud: inetutils: preferredNetworks: - 10.10.10 --- spring: profiles: peer1 eureka: instance: hostname: ms01 client: serviceUrl: defaultZone: http://ms02:8761/eureka/ --- spring: profiles: peer2 eureka: instance: hostname: ms02 client: serviceUrl: defaultZone: http://ms01:8761/eureka/Javaのソースコードは前回から変更なしのため必要なら昔の記事を参照。これで
ms01
とms02
サーバにそれぞれ配置する。起動の際はどのような条件で起動するか、peerを指定して動作条件を決める。ms01サーバで実行[root@ms01 ~]# java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1ms02サーバで実行[root@ms02 ~]# java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2正しく動作しているか確認。ms01のEureka管理コンソールを見ると、レプリカとしてms02のEurekaが登録されていて正常に登録されているようだ。
Service-1/Service-2のマイクロサービス多重化
Eurekaとほぼ同じ構成になる。サーバ毎に条件を分けるpeer指定がないので記載してないが必要ならpeer指定してサーバ毎に動きを変えることも可能。
サンプルとしてService-1のapplication.ymlの設定を載せるがService-2も読み替えて設定。application.ymlspring: application: name: app1-service server: port: 8081 eureka: client: serviceUrl: defaultZone: http://ms01:8761/eureka/, http://ms02:8761/eureka/Gatewayの多重化
Gatewayも一緒。
application.ymlserver: port: 8080 eureka: client: serviceUrl: defaultZone: http://ms01:8761/eureka/, http://ms02:8761/eureka/ spring: application: name: gateway-service cloud: gateway: routes: - id: app1Module # uri: http://ms01:8081/ #単体で動かす場合はこれでもOK uri: lb://APP1-SERVICE # application:name を指定する predicates: - Path=/hello/** - id: app2Module # uri: http://ms01:8082/ uri: lb://APP2-SERVICE predicates: - Path=/goodbye/**サービスの登録と起動
Service-1/Service-2とGatewayをそれぞれms01,ms02両方のサーバで起動してみる。Eurekaに二重登録されているっぽい。
シングル構成の時と同様、マイクロサービスのURLにアクセスすると正常応答が返ってくる。
今回は二重起動しているので、下記それぞれで応答が返ってくる。http://ms01:8080/hello http://ms02:8080/hello http://ms01:8080/goodbye http://ms02:8080/goodbye2台のサーバで3多重サービスをする
サーバがms01,ms02の2台でサービスを3多重にしたい場合、ポートを分けて起動する。
例えばService-1をms01で2つ起動する場合、既に8081ポートが利用済みなので、8083ポートで起動できるようにする。application.xmlspring: application: name: app1-service server: port: 8081 # デフォルトだと8081ポートで起動する eureka: client: serviceUrl: defaultZone: http://ms01:8761/eureka/, http://ms02:8761/eureka/ --- spring: profiles: peer1 # peer1のプロファイルを指定して起動すると、8083ポートで起動する server: port: 8083あとは、これを起動するとよい。
[root@ms01 ~]# java -jar app1-service-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1Eurekaの管理コンソールで見てみると、APP1-SERVICEのサービスがms01サーバの8081と8083ポートで動いているのがわかる。他にもms02サーバの8081ポートで動いているので、合計3多重で動いているのがわかる。
確認のため、ms01,ms02サーバの8081で動いているAPP1-SERVICEを停止し、ms01サーバの8083ポートでサービスが状態にしても、従来通りのアクセス方法で(つまりポートを意識することなく)アクセス可能。
この状態でも従来同様、http://ms01:8080/hello にアクセスすることでAPP1-SERVICEを呼び出せる。
他にも、ms01のサーバ自体を停止させてもシングル構成で動く。
- 投稿日:2020-11-29T17:37:07+09:00
[springで例外処理] 空文字入力時・同名のTODOが存在する時の例外処理
こんにちは。
今回は残るバリデーション2つの実装を行っていこうと思います。
TODOアプリ作成リンク集
1: [超基礎の理解] MVCの簡単な説明
2: [雛形を用意する] Spring Initializrで雛形を作ってHello worldしたい
3: [MySQLとの接続・設定・データの表示] MySQLに仮のデータを保存 -> 全取得 -> topに表示する
4: [POST機能] 投稿機能の実装
5: [PATCH機能] TODOの表示を切り替える
6: [JpaRepositoryの簡単な使い方] 検索機能の実装
7: [Thymeleaf テンプレートフラグメントで共通化] Headerの作成
8: [PUT機能] 編集機能の実装
9: [微調整]TODOの表示を作成日時が新しい順にソートする + 期日のデフォルトを今日の日付にする
10: [springで例外処理] 例外処理についての簡単なまとめ
11: [springで例外処理] 存在しないIDのTODOにアクセスした時の例外処理
12: [springで例外処理] 使われていないHttpMethodでリクエストが来た時の処理・サーバー内でエラーが起きた時の処理
13: [springで例外処理] TODOフォームのバリデーション1: 文字数制限・@Validatedを使うためのGradleの更新
14: [springで例外処理] 空文字入力時・同名のTODOが存在する時の例外処理(今ここ)TODO登録時のバリデーションチェックを追加する。
今回追加する例外処理は
・TODO登録時にから文字が入力された時
・入力されたTODOタイトルがすでにデータベースに存在する時の2点です。
から文字とは?
から文字とはスペース1つで登録しようとするケースの事です。
前回のバリデーションの処理では、TODO登録文字数が1~30字以内かどうかチェックするものを追加しましたが
仮にスペース一つが入力されるとこれは一文字とみなされてしまうので1~30字以内のチェックに引っかかりません!
スペース一つのタイトルは認めたくないので空文字入力時の処理を追加します。
コントローラへの追記
java/com/example/todo/TodoController.java@PostMapping("/register") public String register(@Validated @ModelAttribute TodoForm formData, BindingResult error, RedirectAttributes attributes) { if(error.hasErrors()) { attributes.addFlashAttribute("errorMessages", error); return "redirect:/top"; } //今回追加するもの// if(formData.getTitle().isBlank()) { error.addError(new ObjectError("noSpace", "TODOを入力してください。")); attributes.addFlashAttribute("errorMessages", error); return "redirect:/top"; } //ここまで// todoService.setTodo(formData); return "redirect:/top"; }こんな感じで追記します。
入力されたタイトルに対して
isBlank()
というメソッドを使ってから文字かどうかを判定しています。
似たようなメソッドでisEmpty()
もありますが、こちらはスペースに対してはfalseが返ってくるので今回はisBlank()
を使用しています。空文字があった場合は、新しく
ObjectError
を作りこれをフロントに送っています。同名タイトルが存在する場合のバリデーション処理
今回のTODOでは同じ名前のTODOを追加できない仕様を想定しています。
やり方としては、入力されたタイトルを使ってデータベースに同名のタイトルのTODOがあるかをチェックして判定します。
Repositoryに関数追加
まずはDBとアプリケーションをつなぐ役目を持つ
TodoRepository
に検索機能を追加します。java/com/example/todo/dao/TodoRepository.java@Repository public interface TodoRepository extends JpaRepository<TodoEntity, Long> { //この1行を追加 List<TodoEntity> findByTitle(String searchWord); List<TodoEntity> findByTitleContaining(String searchWord); List<TodoEntity> findAllByOrderByCreateTimeDesc(); }この1行を追加する事で渡ってきた
String
をもとにタイトル検索することができるようになりました。
findByTitleContaining
との違いですが、Containing
は含むという意味ですので、部分一致・完全一致
の両方に対応しています。かたや
findByTitle
は入力値が完全に一致する場合のみ対応しています。ServiceにRepositoryに追加した関数を呼び出す処理をかく
続いてServiceクラスに先ほどの関数を呼び出す関数を追加します。
java/com/example/todo/TodoService.javapublic boolean ifExistByTitle(String title) { return !todoRepository.findByTitle(title).isEmpty(); }ここのreturnについて解説します。
findByTitle()
は
タイトルが完全一致するTODOがあればそのTODOをリストとして返します。その結果に対して
.isEmpty()
するので
中身がある(つまり同名TODOがある) -> false
中身がない(つまり同名TODOがない) -> true
を返すわけです。そして
ifExistByTitle
は結果が
true -> 同名タイトルが存在する
false -> 同名タイトルが存在しない
ということを想定しています。ですので
isEmpty()
の結果を!
によってひっくり返す事でifExistByTitle
に相応しい結果を得る事できます。ちなみにKotlinには
isNotEmpty()
という関数があり、こんなややこしいことをせずに済みますwもしこの書き方が好み出ない場合は
java/com/example/todo/TodoService.javapublic boolean a(String title) { if(todoRepository.findByTitle(title).isEmpty()) { return false; } return true; }でも良いと思います。(InteliJだとシンプルにできるぜ!と先に書いた方を推奨してくることもありますが・・・)
コントローラに追記。
java/com/example/todo/TodoController.java@PostMapping("/register") public String register(@Validated @ModelAttribute TodoForm formData, BindingResult error, RedirectAttributes attributes) { if(error.hasErrors()) { attributes.addFlashAttribute("errorMessages", error); return "redirect:/top"; } if(formData.getTitle().isBlank()) { error.addError(new ObjectError("noSpace", "TODOを入力してください。")); attributes.addFlashAttribute("errorMessages", error); return "redirect:/top"; } //今回ついかする// if(todoService.ifExistByTitle(formData.getTitle())) { error.addError(new ObjectError("sameTitle", "同名のTODOが既に存在します。")); attributes.addFlashAttribute("errorMessages", error); return "redirect:/top"; } //ここまで// todoService.setTodo(formData); return "redirect:/top"; }このように先ほどついかしたService・Repositoryの処理を追加してやりましょう。
これにより2つのバリデーションチェックを追加する事ができました!
- 投稿日:2020-11-29T17:09:38+09:00
【Java】Time (日付操作)
Timeクラス
【説明】
DateクラスとCalendarクラスの問題を解決するために新しく出来たクラスです。
Timeは DateクラスとCalendarクラスの両方の特徴があり、
- 日付データ保持
- 日付操作
これら両方とも Timeクラス1つで賄うことが出来てしまいます。
Timeクラスには、
- LocalDate
- Localtimeのインスタンスを持つLocalDateTime
- LocalDateTimeに加え
- オフセット(標準時との時差)を含むOffsetDateTime
- OffsetDateTimeに加え
- タイムゾーンを含むZonedDateTimeがあります。
クラス名 機能と役割 例 Instant 世界における、ある「瞬間」の時刻を、ナノ秒単位で厳密に指し示し、保持する ZoneDateTime 世界における、ある「瞬間」の時刻を、ナノ秒単位で厳密に指し示し、保持する 2020-11-30T23:30:59.999+09:00[Asia/Tokyo] LocalDateTime 内部にLocalDateとLocalTimeのインスタンスを持っている。
日常的に使われる「曖昧な日時」を保持する
タイムゾーンのない日時2020-11-30T23:30:59.999 Duration 2つの異なる時刻や日付の期間を保持する Period 2つの異なる時刻や日付の期間を保持する OffsetDateTime オフセット付きの日時。 2020-11-30T23:30:59.999+09:00 また、月の値の対応もCalendarクラスと異なっており、月の値が1場合 1月 として出力されます。
【使用場面】
「 時間 」を扱う場面全般で使用できます。
※ 現場の中には、「伝統的なDateとCalendarを使用するので、Time Apiは積極活用しない」という場所もあるので、実務では現場のルールに従うこと。
【サンプルコード】
// LocalDateTime のインポート import java.time.LocalDateTime; // OffsetDateTime のインポート import java.time.OffsetDateTime; // ZonedDateTime のインポート import java.time.ZonedDateTime; public class AboutTimeMain { public static void main(String[] args) { // LocalDateTime で現在時刻を取得 LocalDateTime ldtNow = LocalDateTime.now(); // OffsetDateTime で現在時刻を取得 OffsetDateTime odtNow = OffsetDateTime.now(); // ZonedDateTime で現在時刻を取得 ZonedDateTime zdtNow = ZonedDateTime.now(); // それぞれのTimeクラスを出力する。 System.out.println("LocalDateTime は" + ldtNow.toString()); System.out.println("OffsetDateTime は" + odtNow.toString()); System.out.println("ZonedDateTime は" + zdtNow.toString()); } }【実行結果】
LocalDateTime は2020-11-29T16:48:21.834565 OffsetDateTime は2020-11-29T16:48:21.835357+09:00 ZonedDateTime は2020-11-29T16:48:21.836312+09:00[Asia/Tokyo]【サンプルコード】
TimeクラスはDateクラスやCalendarクラスと異なり、「直接値を変更することができない」ため、「 日時などの値を変更する場合はインスタンスを生成しなおす」 必要があります。
// LocalDateTime のインポート import java.time.LocalDateTime; public class AboutTimeMain { public static void main(String[] args) { // LocalDateTime で現在時刻を取得 LocalDateTime ldtNow = LocalDateTime.now(); System.out.println(ldtNow.getMonthValue() + "月です。"); // インスタンス の再生成 ldtNow = ldtNow.withMonth(5); System.out.println(ldtNow.getMonthValue() + "月に変更しました。"); } }【実行結果】
11月です。 5月に変更しました。
- 投稿日:2020-11-29T17:09:38+09:00
【Java】Time ( 日付操作 )
Timeクラス
【説明】
DateクラスとCalendarクラスの問題を解決するために新しく出来たクラスです。
Timeは DateクラスとCalendarクラスの両方の特徴があり、
- 日付データ保持
- 日付操作
これら両方とも Timeクラス1つで賄うことが出来てしまいます。
Timeクラスには、
- LocalDate
- Localtimeのインスタンスを持つLocalDateTime
- LocalDateTimeに加え
- オフセット(標準時との時差)を含むOffsetDateTime
- OffsetDateTimeに加え
- タイムゾーンを含むZonedDateTimeがあります。
クラス名 機能と役割 例 Instant 世界における、ある「瞬間」の時刻を、ナノ秒単位で厳密に指し示し、保持する ZoneDateTime 世界における、ある「瞬間」の時刻を、ナノ秒単位で厳密に指し示し、保持する 2020-11-30T23:30:59.999+09:00[Asia/Tokyo] LocalDateTime 内部にLocalDateとLocalTimeのインスタンスを持っている。
日常的に使われる「曖昧な日時」を保持する
タイムゾーンのない日時2020-11-30T23:30:59.999 Duration 2つの異なる時刻や日付の期間を保持する Period 2つの異なる時刻や日付の期間を保持する OffsetDateTime オフセット付きの日時。 2020-11-30T23:30:59.999+09:00 また、月の値の対応もCalendarクラスと異なっており、月の値が1場合 1月 として出力されます。
【使用場面】
「 時間 」を扱う場面全般で使用できます。
※ 現場の中には、「伝統的なDateとCalendarを使用するので、Time Apiは積極活用しない」という場所もあるので、実務では現場のルールに従うこと。
【サンプルコード】
// LocalDateTime のインポート import java.time.LocalDateTime; // OffsetDateTime のインポート import java.time.OffsetDateTime; // ZonedDateTime のインポート import java.time.ZonedDateTime; public class AboutTimeMain { public static void main(String[] args) { // LocalDateTime で現在時刻を取得 LocalDateTime ldtNow = LocalDateTime.now(); // OffsetDateTime で現在時刻を取得 OffsetDateTime odtNow = OffsetDateTime.now(); // ZonedDateTime で現在時刻を取得 ZonedDateTime zdtNow = ZonedDateTime.now(); // それぞれのTimeクラスを出力する。 System.out.println("LocalDateTime は" + ldtNow.toString()); System.out.println("OffsetDateTime は" + odtNow.toString()); System.out.println("ZonedDateTime は" + zdtNow.toString()); } }【実行結果】
LocalDateTime は2020-11-29T16:48:21.834565 OffsetDateTime は2020-11-29T16:48:21.835357+09:00 ZonedDateTime は2020-11-29T16:48:21.836312+09:00[Asia/Tokyo]【サンプルコード】
TimeクラスはDateクラスやCalendarクラスと異なり、「直接値を変更することができない」ため、「 日時などの値を変更する場合はインスタンスを生成しなおす」 必要があります。
// LocalDateTime のインポート import java.time.LocalDateTime; public class AboutTimeMain { public static void main(String[] args) { // LocalDateTime で現在時刻を取得 LocalDateTime ldtNow = LocalDateTime.now(); System.out.println(ldtNow.getMonthValue() + "月です。"); // インスタンス の再生成 ldtNow = ldtNow.withMonth(5); System.out.println(ldtNow.getMonthValue() + "月に変更しました。"); } }【実行結果】
11月です。 5月に変更しました。
- 投稿日:2020-11-29T16:33:27+09:00
Spring BootでCSV出力
Java+SpringBootで検索結果をCSVダウンロードできる機能を実装できたので備忘録として残しておきます。
で、このCSVボタンを押すと、検索結果全てがCSVファイルとしてダウンロードされるようにします
1 - まずこんな感じで検索ボタンが押された後のレコード情報をhiddenの<form>に入れて保持しておきます。
<form id="csvform" method="post" th:action="@{/asset/csv}" th:object="${csvForm}"> <div th:each="asset:${assets}"> <input type="hidden" name="id" th:value="${asset.id}" /> <input type="hidden" name="categoryId" th:value="${asset.categoryId}" /> <input type="hidden" name="adminName" th:value="${asset.adminName}" /> <input type="hidden" name="assetName" th:value="${asset.assetName}" /> <input type="hidden" name="remarks" th:value="${asset.remarks}" /> </div> <button type="submit">CSV</button> </form>2 - で、CSボタンが押されたらこの情報全てをポスト送信し、コントローラー側で受け取ってCSV形式に整形し、ダウンロードしてくれます。この際に、
CSV records
ではなく、List<CSV> records
とすると、エラーが起きてしまうのはなぜだか分からずじまい... とりあえずList<Object>を受け取るにはList<Object>をフィールドにもっとクラスを用意しておかないといけないみたい。@PostMapping(value = "/csv", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8; Content-Disposition: attachment") @ResponseBody public Object csvDownload(@ModelAttribute("csvForm") CSV records) throws JsonProcessingException { List<CsvColumn> csvList = new ArrayList<>(); for (int i = 0; i < records.getId().size(); i++) { // レコードの数ぶんだけループ回して csvList.add(new CsvColumn(records.getId().get(i), records.getCategoryId().get(i), records.getAdminName().get(i), records.getAssetName().get(i), records.getRemarks().get(i))); } CsvMapper mapper = new CsvMapper(); CsvSchema schema = mapper.schemaFor(CsvColumn.class).withHeader(); return mapper.writer(schema).writeValueAsString(csvList); }@Data public class CSV { List<Integer> id; List<Integer> categoryId; List<String> adminName; List<String> assetName; List<String> remarks; }@JsonPropertyOrder({"資産ID", "資産種別", "管理者名", "資産名", "備考"}) @Data public class CsvColumn { @JsonProperty("資産ID") private Integer id; @JsonProperty("資産種別") private Integer categoryId; @JsonProperty("管理者名") private String adminName; @JsonProperty("資産名") private String assetName; @JsonProperty("備考") private String remarks; public CsvColumn () {} public CsvColumn (Integer id, Integer categoryId, String adminName, String assetName, String remarks) { this.id = id; this.categoryId = categoryId; this.adminName = adminName; this.assetName = assetName; this.remarks = remarks; }
- 投稿日:2020-11-29T15:45:39+09:00
Clojure + JavaFX + カスタムJREでGUIアプリを作成し、リリースを自動化する
まえがき
- ClojureでJavaFXを利用したGUIアプリケーションを実装します
- ユーザ環境にJavaランタイムをインストールしなくて良いように、カスタムJREを使ってアプリケーションを起動できるようにする方法を書きます
- リリース作業をGitHubActionsで自動化します
開発環境
Key Value OS Ubuntu 18.04 Clojure 1.10.1 Leiningen 2.9.4 Java OpenJDK 14 JavaFXアプリケーションの配布について
以下の記事でより詳細に書かれているのですが、JavaFXアプリを最新のJavaで起動する形で配布するには準備が必要です。
OpenJFX時代のJDK選び - もしくはOpenJFX時代のアプリケーション配布Clojureで作成した実行可能JARも同じ問題を抱えています。
ユーザにJavaのランタイム、JavaFX SDKを入れてもらう必要なく、JavaFXアプリケーションを配布するには、カスタムJREを作成して実行可能JARと一緒に配布する必要があります。各プラットフォーム向けに配布する流れ
カスタムJREを作成してアプリを起動できるようにするのにはいくつか準備が必要で、手順もやや複雑です。なので、段階を追って以下の流れで説明します。
- Clojure+JavaFXのアプリケーションを実装する
- jlinkでカスタムJREを作成し、カスタムJREでアプリケーションを起動できるようにする
- 各プラットフォーム向けに配布するために、CIと連携して自動リリースできるようにする
0. ソースコード
CIでの自動リリースまですべて構築したソースコードは以下です。
今回の記事はこのリポジトリのソースコードをベースに説明します。https://github.com/jiro4989/iconisor
1. Clojure+JavaFXのアプリケーションを実装する
まずClojureでJavaFXアプリケーションを起動できるところまで実装します。
この時点ではカスタムJREなどは作らず、lein経由でアプリが起動できればOKです。プロジェクトの管理にはleiningenを使います。
ClojureでJavaFXを扱うためのライブラリとしてcljfx
を使います。
dependenciesに[cljfx "1.7.10"]
を追加します。pluginsにフォーマッタや静的解析を入れていますが、
こちらは今回の記事とは関係ないので入れなくても動作すると思います。コードとしては以下です。
project.clj(defproject iconisor "0.1.0-SNAPSHOT" :description "FIXME: write description" :url "http://github.com/jiro4989/iconisor" :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0" :url "https://www.eclipse.org/legal/epl-2.0/"} :dependencies [[org.clojure/clojure "1.10.1"] [cljfx "1.7.10"]] :main iconisor.core ;:main ^:skip-aot iconisor.core :target-path "target/%s" :plugins [[lein-cloverage "1.2.0"] [lein-cljfmt "0.7.0"] [jonase/eastwood "0.3.10"] [lein-kibit "0.1.8"]] :profiles {:uberjar {:aot :all :jvm-opts ["-Dclojure.compiler.direct-linking=true"] :prep-tasks ["compile"] :uberjar-name "iconisor.jar" :injections [(javafx.application.Platform/exit)]}})最初ここで躓いたのが
:injections
の設定です。アプリケーションの配布用に実行可能JARを作成する必要があるので
lein uberjar
を実行するとuberjarが永遠に終了しないという事象に遭遇しました。
以下のissuesを見つけて解決したのですが:injections [(javafx.application.Platform/exit)]
という記述が必須です。
https://github.com/cljfx/cljfx/issues/17次にmain関数を実装します。
とりあえずGUIが出れば良いので、cljfxのサンプルコードだけでOKです。src/iconisor/core.clj(ns iconisor.core (:require [cljfx.api :as fx]) (:gen-class)) (defn -main [& args] (fx/on-fx-thread (fx/create-component {:fx/type :stage :showing true :always-on-top true :style :transparent :scene {:fx/type :scene :fill :transparent :stylesheets #{"styles.css"} :root {:fx/type :v-box :children [{:fx/type :label :effect {:fx/type :drop-shadow :radius 1 :offset-y 2} :tooltip {:fx/type :tooltip :text "I am a tooltip!"} :text "Hi! What's your name?"} {:fx/type :text-field}]}}})))これで
lein run
を実行すると以下のようなテキストフィールドだけのGUIが起動するようになります。2. jlinkでカスタムJREを作成し、カスタムJREでアプリケーションを起動できるようにする
カスタムJREを作成するためにjlinkを実行します。
jlinkを実行するには依存ライブラリのモジュールが必要になります。
そのためOpenJFXのサイトからjmodsをダウンロードする必要があります。
僕は以下のシェルを作成し、コマンドラインで取得するようにしています。install_jmods.sh#!/bin/bash set -eux os_name=linux version=14.0.1 rm -rf jmods || true mkdir -p jmods curl -o jmods/jmods.zip -sSL https://download2.gluonhq.com/openjfx/${version}/openjfx-${version}_${os_name}-x64_bin-jmods.zip cd jmods unzip jmods.zipこのシェルを実行するとカレントディレクトリに
jmods
ディレクトリが作成されます。次に、以下のシェルスクリプトを作成します。
jlink.sh#!/bin/bash set -eux outdir=$1 jlink --module-path ./jmods/javafx-jmods-14.0.1/ \ --add-modules javafx.base,javafx.controls,javafx.swing,javafx.graphics,javafx.fxml \ --compress=2 \ --output "$outdir"このシェルを使ってカスタムJREを作成します。
以下のように呼び出します。./jlink.sh jreこれでjreディレクトリにカスタムJREが作成されます。
このカスタムJREを利用して実行可能JARが起動できることを確認します。
以下のように実行します。# 実行可能JARの生成 lein uberjar cd jre ./bin/java -jar ../target/uberjar/iconisor.jarこれでGUIが表示されれば、カスタムJREで起動できるようになっています。
3. CIと連携して自動リリースできるようにする
通常の開発ではjmodsを落としてくる必要はありませんし、カスタムJREの作成も不要です。
リリース作業のためだけにローカル環境を整えるのも手間なのと、リリース作業をコード化して再利用できるようにするために、CI環境(GitHubActions)だけでリリースを完了できるようにします。
具体的には、以下のコマンドを実行して、タグをpushするだけでリリースを終えられるようにするのを目指します。
git tag v0.1.0 git push origin --tags
追加の準備として、起動スクリプトを作成します。
前述のとおり、カスタムJREを使ってアプリケーションを起動するのであれば、コマンドラインからカスタムJREのJavaを指定する必要があります。
一般配布を目指すのでしたら、コマンドライン入力を求めるようにはしたくありません。
なので、前述のコマンドをラップしたスクリプトを作成し、配布時にカスタムJREと一緒に配布する形とします。
以下のスクリプトを作成します。Windows用。文字コードはSJIS、改行コードはCRLFにしましょう。
github/dist/iconisor.bat@echo off .\jre\bin\java.exe -jar .\iconisor.jarMac, Linux用。文字コードはUTF-8、改行コードはLFにしましょう。
#!/bin/bash # .github/dist/iconisor ./jre/bin/java -jar iconisor.jarこれらのスクリプトと一緒に、以下のディレクトリ構成となるようにリリース物を収集して圧縮するようなワークフローにしていきます。
iconisor_windows.zip/ iconisor_windows/ jre/ # カスタムJRE LICENSE README.txt iconisor.bat # 起動スクリプト iconisor # 起動スクリプト iconisor.jar # 実行可能JARということで、CI環境のみでWindows、Linux、Mac向けにリリース物を生成してアップロードするワークフローを書きました。
以下が全体です。name: test on: push: paths-ignore: - 'LICENSE' - 'README.*' - 'docs/*' pull_request: paths-ignore: - 'LICENSE' - 'README.*' - 'docs/*' env: app-name: iconisor # TODO: アプリ名なのでアプリ名に合わせて変更が必要 javafx-version: '14.0.1' defaults: run: shell: bash jobs: # リリース自体には不要。テストを書いているなら使う test: runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: java-version: '14' java-package: jdk architecture: x64 - name: Install xvfb run: sudo apt install -y xvfb - run: xvfb-run lein test - run: xvfb-run lein cloverage --codecov - uses: codecov/codecov-action@v1 # リリース自体には不要。lintにかけるなら必要。project.cljへのplugin記述も必要 linter: runs-on: ubuntu-latest strategy: matrix: cmd: - 'cljfmt check' - 'kibit' steps: - uses: actions/checkout@v2 - run: lein ${{ matrix.cmd }} # リリースのメイン処理 build: runs-on: ${{ matrix.runs-on }} strategy: matrix: include: - runs-on: windows-latest os: windows - runs-on: macOS-latest os: osx - runs-on: ubuntu-latest os: linux steps: - uses: actions/checkout@v2 - uses: actions/setup-java@v1 with: java-version: '14' java-package: jdk architecture: x64 - name: Install lein (unix) uses: DeLaGuardo/setup-clojure@master with: cli: '1.10.1.469' lein: 2.9.4 boot: latest if: matrix.os != 'windows' - name: Install lein (windows) run: | from urllib import request import os lists = [ ("https://raw.githubusercontent.com/technomancy/leiningen/stable/bin/%s", "lein.bat"), ] for elem in lists: url = elem[0] % elem[1] file = elem[1] request.urlretrieve(url, file) os.system("lein.bat self-install") shell: python if: matrix.os == 'windows' - name: Install openjfx jmods run: | mkdir -p jmods curl -o jmods/jmods.zip -sSL https://download2.gluonhq.com/openjfx/${{ env.javafx-version }}/openjfx-${{ env.javafx-version }}_${{ matrix.os }}-x64_bin-jmods.zip ( cd jmods unzip jmods.zip ) - name: Initialize custom JRE run: ./jlink.sh jre - name: Build (unix) run: lein uberjar if: matrix.os != 'windows' - name: Build (windows) run: lein.bat uberjar if: matrix.os == 'windows' shell: cmd - name: Create artifact run: | mkdir -p ${{ env.app-name }}_${{ matrix.os }}/ mv jre .github/dist/* LICENSE target/uberjar/${{ env.app-name }}.jar ${{ env.app-name }}_${{ matrix.os }}/ - name: Compress artifact (unix) run: | tar czf ${{ env.app-name }}_${{ matrix.os }}.tar.gz ${{ env.app-name }}_${{ matrix.os }} if: matrix.os != 'windows' - name: Compress artifact (windows) run: | 7z a ${{ env.app-name }}_${{ matrix.os }}.zip ${{ env.app-name }}_${{ matrix.os }} if: matrix.os == 'windows' - uses: actions/upload-artifact@v2 with: name: artifact-${{ matrix.os }} path: | ${{ env.app-name }}_${{ matrix.os }}.tar.gz ${{ env.app-name }}_${{ matrix.os }}.zip if: startsWith(github.ref, 'refs/tags/') create-release: if: startsWith(github.ref, 'refs/tags/') runs-on: ubuntu-latest needs: - build steps: - uses: actions/checkout@v1 - name: Create Release id: create-release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} release_name: ${{ github.ref }} body: ${{ github.ref }} draft: false prerelease: false - name: Write upload_url to file run: echo '${{ steps.create-release.outputs.upload_url }}' > upload_url.txt - uses: actions/upload-artifact@v2 with: name: create-release path: upload_url.txt upload-release: runs-on: ubuntu-latest needs: create-release strategy: matrix: include: - os: windows asset_name_suffix: windows.zip asset_content_type: application/zip - os: osx asset_name_suffix: osx.tar.gz asset_content_type: application/gzip - os: linux asset_name_suffix: linux.tar.gz asset_content_type: application/gzip steps: - uses: actions/download-artifact@v2 with: name: artifact-${{ matrix.os }} - uses: actions/download-artifact@v2 with: name: create-release - id: vars run: | echo "::set-output name=upload_url::$(cat upload_url.txt)" - name: Upload Release Asset id: upload-release-asset uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.vars.outputs.upload_url }} asset_path: ${{ env.app-name }}_${{ matrix.asset_name_suffix }} asset_name: ${{ env.app-name }}_${{ matrix.asset_name_suffix }} asset_content_type: ${{ matrix.asset_content_type }}解説すると
- WindowsVM, MacVM, LinuxVMを並列に起動
- それぞれのVMで実行可能JARを生成
- jmodsのダウンロード
- カスタムJREを作成
- カスタムJRE、起動スクリプト類(
.github/dist/*
)、実行可能JARを収集して圧縮- artifactとしてアップロード
- GitHub Releaseの作成
- artifactをダウンロードし、Releasesにartifactをアップロード
という感じです。
結果として、以下のようにダウンロード可能になります。
https://github.com/jiro4989/iconisor/releases
まとめ
以下の話をしました。
- Clojure+JavaFXアプリの実装
- カスタムJREの作成方法
- リリースワークフローの実装
ClojureでのJavaFXアプリ開発の一助となれば幸いです。
以上
- 投稿日:2020-11-29T15:25:23+09:00
AWSのJava SDK V2を使用して、Route53にAレコードを登録する
Main.javapackage com.example; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.route53.Route53Client; import software.amazon.awssdk.services.route53.model.Change; import software.amazon.awssdk.services.route53.model.ChangeAction; import software.amazon.awssdk.services.route53.model.ChangeBatch; import software.amazon.awssdk.services.route53.model.ChangeResourceRecordSetsRequest; import software.amazon.awssdk.services.route53.model.ChangeResourceRecordSetsResponse; import software.amazon.awssdk.services.route53.model.RRType; import software.amazon.awssdk.services.route53.model.ResourceRecord; import software.amazon.awssdk.services.route53.model.ResourceRecordSet; public class Main { public static void main(String[] args) throws IOException { Region region = Region.AWS_GLOBAL; Route53Client route53Client = Route53Client.builder() .region(region) .build(); upsertArecord("XXXXXXXXXXXXXXXXXXXX", "18.177.121.151", "test.example.com", route53Client); } public static void upsertArecord(String hostedZoneId, String ipAddress, String host, Route53Client route53Client) { ResourceRecord resourceRecord = ResourceRecord.builder().value(ipAddress).build(); List<ResourceRecord> resourceRecords = new ArrayList<ResourceRecord>(); resourceRecords.add(resourceRecord); // https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/route53/model/ResourceRecordSet.html // weightを入れるとSimpleタイプにならないので、weightは入れない。 // ホスト名は、testではなく、test.example.com のような文字列にする必要がある。 ResourceRecordSet resourceRecordSet = ResourceRecordSet.builder() .name(host) .type(RRType.A) .ttl(new Long(300)) .resourceRecords(resourceRecords) .build(); // https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/route53/model/Change.html // UPSERTにすることで、新規も更新もできる。 Change change = Change.builder() .action(ChangeAction.UPSERT) .resourceRecordSet(resourceRecordSet).build(); List<Change> changes = new ArrayList<Change>(); changes.add(change); ChangeBatch changeBatch = ChangeBatch.builder() .changes(changes) .build(); // https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/route53/model/ChangeResourceRecordSetsRequest.html ChangeResourceRecordSetsRequest request = ChangeResourceRecordSetsRequest.builder() .hostedZoneId(hostedZoneId) .changeBatch(changeBatch) .build(); // 例外がthrowされなければ、要求の送信は完了(戻り値の状態値はPENDINGとなるが、それでOK) ChangeResourceRecordSetsResponse response = route53Client.changeResourceRecordSets(request); // Print the result // response.toString(); } }pom.xml<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>aws-sdk-java-route53</artifactId> <version>1.0</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <!-- https://github.com/aws/aws-sdk-java-v2/#using-the-sdk --> <!-- https://search.maven.org/search?q=g:software.amazon.awssdk%20AND%20a:bom --> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>2.15.32</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- https://docs.aws.amazon.com/code-samples/latest/catalog/javav2-route53-pom.xml.html --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>route53</artifactId> </dependency> <!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <!-- http://www.slf4j.org/manual.html --> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.30</version> </dependency> <!-- logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.3</version> </dependency> </dependencies> <build> <finalName>my-route53</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.3</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.4</version> <configuration> <archive> <manifest> <mainClass>com.example.Main</mainClass> </manifest> </archive> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
- 投稿日:2020-11-29T14:21:00+09:00
【Java】Calendar ( 日時の計算をするクラス )
Calendarクラス
【説明】
「 日付や 指定した日時の計算 」 などを行う場合に使用します。
【使用場面】
- 入会してから90日後
- 今日から1ヶ月と2週間後
といった、少々ややこしい計算が必要な時に便利です。
【基本構文】
java.util.Calendar の インポート
まずは
import java.util.Calendar;
で通常のようにインポートを行います。// Calendarクラスのインポート import java.util.Calendar;getInstanceメソッド で オブジェクト の生成(重要)
「 Calendarクラスでは演算子newではなく、getInstanceメソッドを呼びオブジェクトを生成」します。
getInstanceメソッド
getInstanceメソッド は呼び出すごとに
「現在の日付と時間に初期化された状態でオブジェクトを返す」メソッドです。以上を踏まえて、Calendarクラスのインスタンス化( オブジェクト化 )のしかたはこちらです。
//Calendarクラスの オブジェクト の生成 Calendar calendar = Calendar.getInstance();calendarクラスの「主要フィールド」のチートシート
フィールド 用途 YEAR 年 MONTH 月(0 ~ 11) DATE,DAY_OF_MONTH 現在の 月 の 何日目 か WEEK_OF_MONTH 現在の 月 の 何周目 か DAY_OF_WEEK 現在の 週 の 何日目 か AM_PM 午前 か 午後 か HOUR 午前 / 午後 の 何時 か HOUR_OF_DAY 24時間制 で 何時 か MINUTE 分 SECOND 秒 Calendarクラスに使うメソッド
Calendarクラスで特に使えそうなメソッドである、getTimeメソッド, setメソッド, addメソッド, formatメソッド のまとめです。
【使用場面】
- getTimeメソッド (時刻を出力)
- setメソッド (日時を設定する)
- add (日付の加減算を行う)
- format (文字列で日付のフォーマットを指定)
getTime()メソッド
【説明】
getTimeメソッドを利用することで、カレンダーから「 現在の時刻をjava.utilパッケージ の Dateオブジェクト 」 として取得できます。
【使用場面】
- 現在時刻を表示したい時
- setされた時刻を表示したい時
【基本構文】
基本的には カレンダーの
オブジェクト名
にドットでgetTime()
繋げて呼び出します。// getTime() Calendarクラスのオブジェクト名.getTime()【サンプルコード】 getTime()メソッドで現在時刻を出力
現在時刻を出力する場合以下のように記述します。
System.out.println(Calendarクラスのオブジェクト名.getTime());getTime() メソッドを用いた一連の流れがこちらです。
package about_java_dateClass; //Calendarクラスのインポート import java.util.Calendar; public class DateMain { public static void main(String[] args) { // Calendarクラスの オブジェクト の生成 Calendar calendar = Calendar.getInstance(); // 現在時刻を出力 System.out.println(calendar.getTime()); } }【実行結果】
Sun Nov 29 12:58:32 JST 2020setメソッド (日時を設定する)
【説明】
setメソッドは日時を設定するために使用します。
【使用場面】
「2020年を2030年に設定」といったような、日付を処理するようなプログラムによく使われます。
【基本構文】
calendar.set(年, 月, 日, 時, 分, 秒);年月日時分をまとめて指定するとき
Calendar calendar = Calendar.getInstance(); calendar.set(2021, 1, 30, 22, 01, 55); // 現在時刻を出力 System.out.println("現在時刻は" + date); // setした時刻を出力 System.out.println("setした日時は" + calendar.getTime());出力結果
現在時刻はSun Nov 29 12:38:43 JST 2020 setした日時はTue Mar 02 22:01:55 JST 2021特定の日付/時刻要素(たとえば年だけ)を指定するとき
Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, 2040); // 現在時刻を出力 System.out.println("現在時刻は" + date); // setした時刻を出力 System.out.println("setした日時は" + calendar.getTime());出力結果
現在時刻はSun Nov 29 12:40:19 JST 2020 setした日時はThu Nov 29 12:40:19 JST 2040addメソッド (日付の加減算を行う)
【説明】
Calendarクラスで
「 現在時刻を基準に日付の加減算を行うとき 」にはaddメソッド
を使用する
。addメソッドでは、指定された日付/時刻要素(getメソッドの表を参照)に対して、
- 引数の値だけ加算
- 引数が負数の場合には減算
を行います。
【使用場面】
現在時刻から何年後、何年前、何日後、何日前といった日にちを計算して表示するなど。
【基本構文】
Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.演算する対象, 値);【サンプルコード】 現在時刻から3ヶ月前の日付を表示するプログラム
現在
日本時間で 2020年 11月 29日
ですが、そこから addメソッドを使って日付操作を行ってみます。//Calendarクラスのインポート import java.util.Calendar; public class DateMain { public static void main(String[] args) { Calendar calendar = Calendar.getInstance(); // addメソッドで 5年前にする calendar.add(Calendar.YEAR, -5); // addメソッドで 1ヶ月後にする calendar.add(Calendar.MONTH, 1); // addメソッドで 10日前にする calendar.add(Calendar.DATE, -10); // 年を表示 System.out.println(calendar.get(Calendar.YEAR) + "年"); // 月を表示 System.out.println(calendar.get(Calendar.MONTH) + "月"); // 日を表示 System.out.println(calendar.get(Calendar.DATE) + "日"); } }【実行結果】
以上のプログラムで現在時刻
日本時間で 2020年 11月 29日
を操作し実行結果が以下になります。2015年 11月 19日formatメソッド
【説明】
文字列で日付のフォーマットを指定することができるメソッドです。
【使用場面】
"〇〇〇〇年〇〇月〇〇日"といったような、馴染みのあるフォーマットに変えたい時に使用する。
日時の書式について
こちらが日時のフォーマットの書式をまとめたものになります。
こちらのアルファベットを用いてフォーマットを作成していきます。
書式 概要 パターン 表示 G 紀元 G AD y year, 年 yyyy
yyy2020
20M Month, 月 MM
MMM
MMMM03
Mar
Marchd day, 日 d
dd1
01H 時(0-23) H
HH0
00K 午前/午後の時(0-11) K
KK0
00h 午前/午後の時(1-12) h
hh1
01m 分 m
mm0
00s 秒 s
ss0
00S ミリ秒 S
SSS1
001【基本構文】
まずはパッケージ指定後、Calendarクラスといっしょに
java.text.SimpleDateFormat;
をインポートします。// java.text.SimpleDateFormat をインポート。 import java.text.SimpleDateFormat; import java.util.Calendar;インポート後インスタンス化を行います。SimpleDateFormatの略で「sdf」とする場合が多いかもしれません。
そして、時刻表示する場合のフォーマットを作成します。
以下の例場合の実行結果も添えます。// Calendarクラスの オブジェクト の生成 Calendar calendar = Calendar.getInstance(); //日付のフォーマットを設定 SimpleDateFormat sdf = new SimpleDateFormat("yyyy年 MM月 dd日 E曜日 HH時(hh時) mm分 ss秒 SSSミリ秒");// フォーマットを適用し現在時刻を表示 System.out.println(sdf.format(calendar.getTime()));実行結果
2020年 11月 29日 日曜日 15時(03時) 04分 36秒 845ミリ秒あとがき
最近だとjava.utilでの日付操作はモダンではないようですが、今の所現場で現役だそうなので、習得しておきたい。
参考資料
- 投稿日:2020-11-29T13:50:08+09:00
Spring Cloudを使ったマイクロサービスシステム構築(シングル構成)
概要
Gateway、EurekaといったSpring Cloudを使ってマイクロサービスを構築する。ここではシングル構成での構成で実現する。
前提
以下のURLから雛形を作成してマイクロサービスを作成する。
シングル構築でのマイクロサービス構築
まずはシングル構成で動くことを確認する。
サーバは1台でその上に全てのマイクロサービスを実装する。ホスト名は
localhost
にした方が汎用的だが、将来クラスタ構成とするためホスト名であるms01
を明示的に指定して構築する。作成手順
最初にEurekaのマイクロサービスを作成し起動する。他のマイクロサービスはEurekaのクライアントとして動作するので、Eurekaサーバが動作している必要がある。Eurekaのクライアントが起動する際、クライアントの情報がEurekaサーバに登録される。
Eureka
Eurekaのコードはこんな感じ。
EurekaServerApplication.javapackage comm.example.eurekaserver; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; //追加 @EnableEurekaServer //追加 @SpringBootApplication public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }application.ymlspring: cloud: inetutils: preferredNetworks: - 10.10.10 server: port: 8761 eureka: instance: hostname: ms01 client: registerWithEureka: false fetchRegistry: false serviceUrl: defaultZone: http://ms01:8761/eureka/Service-1/Service-2の作成
続きてユーザアプリケーションとしてのマイクロサービス、Service-1/Service-2を作成する。
Service-1のソース
App1ServiceApplication.javapackage com.example.app1service; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; //追加 @SpringBootApplication @EnableEurekaClient //追加 public class App1ServiceApplication { public static void main(String[] args) { SpringApplication.run(App1ServiceApplication.class, args); } }App1Controller.javapackage com.example.app1service.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class App1Controller { @RequestMapping("/hello") public String hello() { return "Hello from Server-A"; } }application.ymlspring: application: name: app1-service # Eurekaの管理画面で表示されるサービス名 server: port: 8081 eureka: client: serviceUrl: defaultZone: http://ms01:8761/eureka/Service-2もほぼ同様の作り。
App2ServiceApplication.javapackage com.example.app2service; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; //追加 @SpringBootApplication @EnableEurekaClient //追加 public class App2ServiceApplication { public static void main(String[] args) { SpringApplication.run(App2ServiceApplication.class, args); } }App2Controller.javapackage com.example.app2service.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class App2Controller { @RequestMapping("/goodbye") public String hello() { return "GoodBye from EurekaClient App2"; } }application.ymlspring: application: name: app2-service server: port: 8082 eureka: client: serviceUrl: defaultZone: http://ms01:8761/eureka/Gatewayの作成
Gatewayの作り方として、ルーティングをJavaで実装するパターンもあるようだが、ここでは
application.yml
ファイルにルーティング情報を書くやり方で実現する。GatewayApplication.javapackage com.example.gateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class GatewayApplication { public static void main(String[] args) { SpringApplication.run(GatewayApplication.class, args); } }application.ymlserver: port: 8080 eureka: client: serviceUrl: defaultZone: http://ms01:8761/eureka/ spring: application: name: gateway-service cloud: gateway: routes: - id: app1Module # uri: http://ms01:8081/ #単体で動かす場合はURL直指定でもOK。ただしクラスタに対応できない。 uri: lb://APP1-SERVICE # application:name を指定する predicates: - Path=/hello/** # app1で利用できるURLを指定 - id: app2Module # app2についてもapp1と同様の設定を行う uri: lb://APP2-SERVICE predicates: - Path=/goodbye/**デプロイ・起動
起動方法は全てのマイクロサービスで同じ手順なので代表例としてEurekaの手順を示すが、他のマイクロサービスも同様に起動できる。
Eclipse等でソースコードを作成後Mavenでeureka-server-0.0.1-SNAPSHOT.jar
というjarファイルを作成する。で作成したjarファイルをサーバに転送して起動する。なお起動順序は最初に言っているよう、Service-1/2およびGatewayはEurekaのクライアントとなるので、サーバであるEurekaを最初に起動し、その後EurekaクライアントのService-1/2およびGatewayを起動すること。
この例ではベタで起動しているが、systemctlで起動できるようサービス化してもいい。[root@ms01 ~]# java -jar eureka-server-0.0.1-SNAPSHOT.jar全てのマイクロサービスが起動したら、Eurekaの管理コンソール(っていうの?)には各マイクロサービスがEurekaのクライアントとして登録されている。
単体で動くかチェックする
Service-1およびService-2については単独でも動作する。
ブラウザから下記URLを叩いて、正常に応答が帰ってくるようならSerivce-1/2は正しく動いている。http://ms01:8081/hello
http://ms01:8082/goodbyeGatewayを経由して動くかチェックする
Gatewayは8080ポートで待ち受けていて、'/hello'がService-1の
/hello
に、goodbye
がService-2の/goodbye
に振り分けられる。
http://ms01:8080/hello
http://ms01:8080/goodbyeここまで動作すると、シングル構成でGatewayを経由して各マイクロサービスにルーティングできるようになる。
ユーザとしては、それぞれのマイクロサービスが異なるサーバで動作していても、サーバが複数あることを気にすることなくGatewayのURLとマイクロサービスのパスを知っているとサービスにアクセス可能となる。
ただし、この構成はシングル構成のため、例えばService-1が落ちた場合、サービスの提供ができなくなる。
- 投稿日:2020-11-29T11:56:29+09:00
【Java・SpringBoot】Spring JDBC でユーザー詳細画面(SpringBootアプリケーション実践編12)
ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して、Spring JDBCの使い方について学びます⭐️
前回はユーザー一覧画面を作ったので、今回はユーザー詳細画面を作ります^^
構成は前回の記事を参考にしてください⭐️前回の記事
【Java・SpringBoot】Spring JDBC でユーザー一覧画面(SpringBootアプリケーション実践編11)リポジトリークラスに1件検索するメソッドを実装
queryForMapメソッドで1件取得
- 戻り値はMap型
- 第1引数:SQL文、第2引数以降にPreparedStatementを指定
Map<String, Object> map = jdbc.queryForMap("SELECT * FROM m_user" + " WHERE user_id = ?", userId);
- 戻り値のMapのgetメソッドにカラム名を指定することで、値を取得する
UserDaoJdbcImpl.javapackage com.example.demo.login.domain.repository.jdbc; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Repository("UserDaoJdbcImpl") public class UserDaoJdbcImpl implements UserDao { @Autowired JdbcTemplate jdbc; // Userテーブルの件数を取得. @Override public int count() throws DataAccessException { //全件取得してカウント int count = jdbc.queryForObject("SELECT COUNT(*) FROM m_user", Integer.class); return count; } // Userテーブルにデータを1件insert. @Override public int insertOne(User user) throws DataAccessException { //1件登録 int rowNumber = jdbc.update("INSERT INTO m_user(user_id," + " password," + " user_name," + " birthday," + " age," + " marriage," + " role)" + " VALUES(?, ?, ?, ?, ?, ?, ?)", user.getUserId(), user.getPassword(), user.getUserName(), user.getBirthday(), user.getAge(), user.isMarriage(), user.getRole()); return rowNumber; } // Userテーブルのデータを1件取得 @Override public User selectOne(String userId) throws DataAccessException { // 1件取得 Map<String, Object> map = jdbc.queryForMap("SELECT * FROM m_user" + " WHERE user_id = ?", userId); // 結果返却用の変数 User user = new User(); // 取得したデータを結果返却用の変数にセットしていく user.setUserId((String) map.get("user_id")); //ユーザーID user.setPassword((String) map.get("password")); //パスワード user.setUserName((String) map.get("user_name")); //ユーザー名 user.setBirthday((Date) map.get("birthday")); //誕生日 user.setAge((Integer) map.get("age")); //年齢 user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス user.setRole((String) map.get("role")); //ロール return user; } // Userテーブルの全データを取得. @Override public List<User> selectMany() throws DataAccessException { // M_USERテーブルのデータを全件取得 List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user"); // 結果返却用の変数 List<User> userList = new ArrayList<>(); // 取得したデータを結果返却用のListに格納していく for (Map<String, Object> map : getList) { //Userインスタンスの生成 User user = new User(); // Userインスタンスに取得したデータをセットする user.setUserId((String) map.get("user_id")); //ユーザーID user.setPassword((String) map.get("password")); //パスワード user.setUserName((String) map.get("user_name")); //ユーザー名 user.setBirthday((Date) map.get("birthday")); //誕生日 user.setAge((Integer) map.get("age")); //年齢 user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス user.setRole((String) map.get("role")); //ロール //結果返却用のListに追加 userList.add(user); } return userList; } // Userテーブルを1件更新. @Override public int updateOne(User user) throws DataAccessException { return 0; } // Userテーブルを1件削除. @Override public int deleteOne(String userId) throws DataAccessException { return 0; } //SQL取得結果をサーバーにCSVで保存する @Override public void userCsvOut() throws DataAccessException { } }サービスクラスに1件取得用のメソッド追加
UserService.javapackage com.example.demo.login.domain.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Service public class UserService { @Autowired UserDao dao; public boolean insert(User user) { // insert実行 int rowNumber = dao.insertOne(user); // 判定用変数 boolean result = false; if (rowNumber > 0) { // insert成功 result = true; } return result; } public int count() { return dao.count(); } public List<User> selectMany() { // 全件取得 return dao.selectMany(); } /** * 1件取得用メソッド. */ public User selectOne(String userId) { // selectOne実行 return dao.selectOne(userId); } }ホーム画面用のコントローラークラスを修正
動的なURLに対応したメソッドを作る
- @GetMappingや@PostMappingの値に
/{<変数名>}
を付ける
- ユーザーIDを受け取る場合は、
@GetMapping(/userDetail/{id})
- 通常は
/userDetail/{id}
とすればいいが。。。- ユーザーIDがメールアドレス形式の場合、
sample@xxx.co.jp
というユーザーIDが渡されてくると、sample@xxx.co
しか受け取れない
- →正規表現を使用して
@GetMapping("/userDetail/{id:.+}")
とする@PathVariable
- @PathVariableアノテーションを付けると、渡されてきたパス(URL)の値を引数の変数に入れることができる
- 例:
@PathVariable("id") String userId) {...}
http://localhost:8080/userDetail/sample@xxx.co.jp
というURLでリクエストが来た場合、sample@xxx.co.jp
という値が引数のuserIdという変数に入れられるHomeController.javapackage com.example.demo.login.controller; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import com.example.demo.login.domain.model.SignupForm; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.service.UserService; @Controller public class HomeController { @Autowired UserService userService; //結婚ステータスのラジオボタン用変数 private Map<String, String> radioMarriage; /** * ラジオボタンの初期化メソッド */ private Map<String, String> initRadioMarrige() { Map<String, String> radio = new LinkedHashMap<>(); // 既婚、未婚をMapに格納 radio.put("既婚", "true"); radio.put("未婚", "false"); return radio; } @GetMapping("/home") public String getHome(Model model) { //コンテンツ部分にユーザー詳細を表示するための文字列を登録 model.addAttribute("contents", "login/home :: home_contents"); return "login/homeLayout"; } @GetMapping("/userList") public String getUserList(Model model) { //コンテンツ部分にユーザー一覧を表示するための文字列を登録 model.addAttribute("contents", "login/userList :: userList_contents"); //ユーザー一覧の生成 List<User> userList = userService.selectMany(); //Modelにユーザーリストを登録 model.addAttribute("userList", userList); //データ件数を取得 int count = userService.count(); model.addAttribute("userListCount", count); return "login/homeLayout"; } /** * ユーザー詳細画面のGETメソッド用処理. */ @GetMapping("/userDetail/{id:.+}") public String getUserDetail(@ModelAttribute SignupForm form, Model model, @PathVariable("id") String userId) { // ユーザーID確認 System.out.println("userId = " + userId); // コンテンツ部分にユーザー詳細を表示するための文字列を登録 model.addAttribute("contents", "login/userDetail :: userDetail_contents"); // 結婚ステータス用ラジオボタンの初期化 radioMarriage = initRadioMarrige(); // ラジオボタン用のMapをModelに登録 model.addAttribute("radioMarriage", radioMarriage); // ユーザーIDのチェック if (userId != null && userId.length() > 0) { // ユーザー情報を取得 User user = userService.selectOne(userId); // Userクラスをフォームクラスに変換 form.setUserId(user.getUserId()); //ユーザーID form.setUserName(user.getUserName()); //ユーザー名 form.setBirthday(user.getBirthday()); //誕生日 form.setAge(user.getAge()); //年齢 form.setMarriage(user.isMarriage()); //結婚ステータス // Modelに登録 model.addAttribute("signupForm", form); } return "login/homeLayout"; } @PostMapping("/logout") public String postLogout() { return "redirect:/login"; } @GetMapping("/userList/csv") public String getUserListCsv(Model model) { return getUserList(model); } }ユーザー詳細画面作成
userDetail.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <meta charset="UTF-8"></meta> </head> <body> <div th:fragment="userDetail_contents"> <div class="row"> <div class="col-sm-5"> <div class="page-header"> <h1>ユーザー詳細</h1> </div> <form method="post" th:action="@{/userDetail}" th:object="${signupForm}"> <table class="table table-bordered table-hover"> <tr> <!-- ユーザーID(入力不可) --> <th class="active col-sm-2">ユーザID</th> <td class="col-sm-3"> <div class="form-group"> <input type="text" class="form-control" th:field="*{userId}" readonly="readonly" /> </div> </td> </tr> <tr> <!-- パスワード --> <th class="active">パスワード</th> <td> <div class="form-group" th:classappend="${#fields.hasErrors('password')} ? 'has-error'"> <input type="text" class="form-control" th:field="*{password}" /> <span class="text-danger" th:if="${#fields.hasErrors('password')}" th:errors="*{password}"> password error </span> </div> </td> </tr> <tr> <!-- ユーザー名 --> <th class="active">ユーザ名</th> <td> <div class="form-group" th:classappend="${#fields.hasErrors('userName')} ? 'has-error'"> <input type="text" class="form-control" th:field="*{userName}" /> <span class="text-danger" th:if="${#fields.hasErrors('userName')}" th:errors="*{userName}"> userName error </span> </div> </td> </tr> <tr> <!-- 誕生日 --> <th class="active">誕生日</th> <td> <div class="form-group" th:classappend="${#fields.hasErrors('birthday')} ? 'has-error'"> <input type="text" class="form-control" placeholder="yyyy/MM/dd" th:field="*{birthday}" /> <span class="text-danger" th:if="${#fields.hasErrors('birthday')}" th:errors="*{birthday}"> birthday error </span> </div> </td> </tr> <tr> <!-- 年齢 --> <th class="active">年齢</th> <td> <div class="form-group" th:classappend="${#fields.hasErrors('age')} ? 'has-error'"> <input type="text" class="form-control" th:field="*{age}" /> <span class="text-danger" th:if="${#fields.hasErrors('age')}" th:errors="*{age}"> age error </span> </div> </td> </tr> <tr> <!-- 結婚 --> <th class="active">結婚</th> <td> <div class="form-group"> <div th:each="item : ${radioMarriage}"> <input type="radio" name="radioMarrige" th:value="${item.value}" th:text="${item.key}" th:field="*{marriage}"> </input> </div> <span class="text-danger" th:if="${#fields.hasErrors('marriage')}" th:errors="*{marriage}"> marriage error </span> </div> </td> </tr> </table> <!-- 更新ボタン --> <button class="btn btn-primary btn-lg pull-right" type="submit" name="update"> 更新 </button> <!-- 削除ボタン --> <button class="btn btn-danger btn-lg" type="submit" name="delete"> 削除 </button> </form> </div> </div> </div> </body> </html>SpringBootを起動してユーザ一覧画面確認!
- http://localhost:8080/userList
- ユーザ一覧から、ユーザ詳細画面に遷移します
- パスワード以外の情報が反映されました〜^^
- 投稿日:2020-11-29T11:47:34+09:00
Java 二次元配列
はじめに
学習用のメモになります。
二次元配列とは?
二次元配列とは、2つのインデックスで要素を指定する配列のことです。
サンプルコード
Main.javapublic class Main { public static void main(String[] args) { String[][] teams = {{"勇者", "戦士", "魔法使い"}, {"盗賊", "忍者", "商人"}, {"スライム", "ドラゴン", "魔王"}}; System.out.println(teams[0][0]); //実行結果 :勇者 teams[0][0] = "剣士"; // 勇者 → 剣士 に更新 System.out.println(teams[0][0]); //実行結果:剣士 System.out.println(teams.length); //実行結果:2 //配列の長さ System.out.println(teams[0].length); //実行結果:3 //teams[0]の配列の長さ } }二次元配列の宣言
Main.javaString[][] teams = {{"勇者", "戦士", "魔法使い"}, {"盗賊", "忍者", "商人"}, {"スライム", "ドラゴン", "魔王"}};二次元配列変数の宣言は、配列の型と次元がふたつあることを宣言する。変数の型の後に[]をふたつ書けば二次元配列になる。
二次元配列をループ処理する
Main.java// 2次元配列をループで処理する public class Main { public static void main(String[] args) { String[][] teams = {{"勇者", "戦士", "魔法使い"}, {"盗賊", "忍者", "商人"}, {"スライム", "ドラゴン", "魔王"}}; for (int i = 0; i < teams.length; i++) { for(int j = 0; j < teams[i].length; j++) { System.out.print(teams[i][j] + " "); } System.out.println(""); } //出力結果 //勇者 戦士 魔法使い //盗賊 忍者 商人 //スライム ドラゴン 魔王 //拡張forでのループ処理 for (String[] team : teams) { for (String player : team) { System.out.print(player + " "); } } } }二次元配列をnewで作成する
Main.java// 2次元配列をnewで作成する public class Main { public static void main(String[] args) { int[][] numberA = new int[3][4]; for(int i =0;i<numberA.length; i++){ for(int j =0;j<numberA[i].length;j++){ numberA[i][j]=i*10+j; //初期値を指定している System.out.print(numberA[i][j]+" "); } System.out.println(""); System.out.println("---"); } } }出力結果
0 1 2 3 --- 10 11 12 13 --- 20 21 22 23 ---
- 投稿日:2020-11-29T11:21:25+09:00
【Java】Date ( 日付操作 )
Date
Dateクラスは、「日付を管理しているクラス」です。
【使用場面】
上記のように Dateクラスは
- 簡単に日付と時刻を取得できる
- 日付の計算には不向き
以上のような特性のため、「単純に時刻を表示したい時」につかうといいとおもいます。
【書き方】
まず、Javaで日付を操作するときには以下を インポート する必要がありますので、こちらをパッケージの下に記述してください。
// Dateのインポート import java.util.Date;// Dateのインスタンス化 Date date = new Date();【サンプルコード】 現在時刻を表示
こちらは単純に現在時刻を表示するプログラムです。
System.out.println(インスタンス名);
で現在時刻を表示します。package about_java_dateClass; //Dateのインポート import java.util.Date; public class DateMain { public static void main(String[] args) { // Dateのインスタンス化 Date date = new Date(); // 現在時刻を出力 System.out.println(date); } }【実行結果】
Sun Nov 29 11:02:14 JST 2020【補足】
タイムゾーン
上記の実行結果の 「JST」 の部分です。
日本標準時刻 が世界標準+9時間 であることを示しています。つまり上記の実行結果は「 日本時間で○○ですよ 」といった意味合いなると思います。
GMT(グリニッジ標準時)
世界標準時刻 は、 GMT(グリニッジ標準時) と呼ばれ、
その GMT を基準として、
- 日本: JST
- アメリカ: PST
のように表記し、「 GMT ± n時間の差 」があることを明示的に示します。
- 投稿日:2020-11-29T11:10:53+09:00
Javaのマルチスレッドについての学習
Java8でマルチスレッドとマルチプロセスの勉強をする。
1.はじめに
1-1.イメージ
マルチプロセスとマルチスレッドの簡単なイメージは以下になる。
マルチプロセスの場合は、メモリ空間が別になるので、データのやり取りにはプロセス間通信が必要になる。
逆にマルチスレッドでは、メモリ空間が同じなため、複数スレッドが同時に同じデータにアクセスして、「レースコンディション」のバグがでないように、排他制御を行い、「スレッドセーフ」にする必要がある。
1-2.非同期処理と並列処理について
マルチスレッドのメリットを2つ記載する。
・「フリーズ」状態にならず、すぐに応答する
→非同期処理(並行)
・パフォーマンスが良くなる
→並列処理1-2.準備
- 投稿日:2020-11-29T10:34:27+09:00
【Java・SpringBoot】Spring JDBC でユーザー一覧画面(SpringBootアプリケーション実践編11)
ホーム画面からユーザー一覧画面に遷移し、ユーザーの詳細を表示するアプリケーションを作成して
Spring JDBCの使い方について学びます⭐️
今回はユーザーを登録(insert)した結果を確認できるように、ユーザー一覧画面を実装します!
構成は前回の記事を参考にしてください⭐️前回の記事
【Java・SpringBoot】Spring JDBC(SpringBootアプリケーション実践編9)
【Java・SpringBoot】Spring JDBC でユーザー登録(SpringBootアプリケーション実践編10)リポジトリークラス実装
queryForObjectメソッドでObjectの取得
- 第1引数にSQL文、第2引数に戻り値のオブジェクトのclassを指定
int count = jdbc.queryForObject("SELECT COUNT(*) FROM m_user", Integer.class);
queryForListメソッドで複数件のselect
- 戻り値の型には
List<Map<String,Object>>
を指定
- Listが行、Mapが列
List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user");
- Mapのgetメソッドでテーブルのカラム名を指定することで値を取得可能
- 引数を追加すれば、PreparedStatementを使える
- 以下では拡張for文を使って、
List<Map<String,Object>>
をList<User>
に変換している
for (Map<String, Object> map : getList) {...}
UserDaoJdbcImpl.javapackage com.example.demo.login.domain.repository.jdbc; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Repository("UserDaoJdbcImpl") public class UserDaoJdbcImpl implements UserDao { @Autowired JdbcTemplate jdbc; // Userテーブルの件数を取得. @Override public int count() throws DataAccessException { //全件取得してカウント int count = jdbc.queryForObject("SELECT COUNT(*) FROM m_user", Integer.class); return count; } // Userテーブルにデータを1件insert. @Override public int insertOne(User user) throws DataAccessException { //1件登録 int rowNumber = jdbc.update("INSERT INTO m_user(user_id," + " password," + " user_name," + " birthday," + " age," + " marriage," + " role)" + " VALUES(?, ?, ?, ?, ?, ?, ?)", user.getUserId(), user.getPassword(), user.getUserName(), user.getBirthday(), user.getAge(), user.isMarriage(), user.getRole()); return rowNumber; } // Userテーブルのデータを1件取得 @Override public User selectOne(String userId) throws DataAccessException { return null; } // Userテーブルの全データを取得. @Override public List<User> selectMany() throws DataAccessException { // M_USERテーブルのデータを全件取得 List<Map<String, Object>> getList = jdbc.queryForList("SELECT * FROM m_user"); // 結果返却用の変数 List<User> userList = new ArrayList<>(); // 取得したデータを結果返却用のListに格納していく for (Map<String, Object> map : getList) { //Userインスタンスの生成 User user = new User(); // Userインスタンスに取得したデータをセットする user.setUserId((String) map.get("user_id")); //ユーザーID user.setPassword((String) map.get("password")); //パスワード user.setUserName((String) map.get("user_name")); //ユーザー名 user.setBirthday((Date) map.get("birthday")); //誕生日 user.setAge((Integer) map.get("age")); //年齢 user.setMarriage((Boolean) map.get("marriage")); //結婚ステータス user.setRole((String) map.get("role")); //ロール //結果返却用のListに追加 userList.add(user); } return userList; } // Userテーブルを1件更新. @Override public int updateOne(User user) throws DataAccessException { return 0; } // Userテーブルを1件削除. @Override public int deleteOne(String userId) throws DataAccessException { return 0; } //SQL取得結果をサーバーにCSVで保存する @Override public void userCsvOut() throws DataAccessException { } }サービスクラス
- カウントと複数検索用のメソッドを用意
UserService.javapackage com.example.demo.login.domain.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.repository.UserDao; @Service public class UserService { @Autowired UserDao dao; public boolean insert(User user) { // insert実行 int rowNumber = dao.insertOne(user); // 判定用変数 boolean result = false; if (rowNumber > 0) { // insert成功 result = true; } return result; } //カウント用メソッド. public int count() { return dao.count(); } //全件取得用メソッド. public List<User> selectMany() { // 全件取得 return dao.selectMany(); } }ホーム画面用のコントローラークラスを修正
- ホーム画面のユーザー管理というリンクをクリックすると、データベースから全ユーザーの情報を取ってくる
- コントローラークラスでは、カウント結果と複数検索結果をModelクラスに登録(addAttribute)し、ユーザー一覧画面で、Modelから値を取得して表示する
HomeController.javapackage com.example.demo.login.controller; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import com.example.demo.login.domain.model.User; import com.example.demo.login.domain.service.UserService; @Controller public class HomeController { @Autowired UserService userService; @GetMapping("/home") public String getHome(Model model) { //コンテンツ部分にユーザー詳細を表示するための文字列を登録 model.addAttribute("contents", "login/home :: home_contents"); return "login/homeLayout"; } @GetMapping("/userList") public String getUserList(Model model) { //コンテンツ部分にユーザー一覧を表示するための文字列を登録 model.addAttribute("contents", "login/userList :: userList_contents"); //ユーザー一覧の生成 List<User> userList = userService.selectMany(); //Modelにユーザーリストを登録 model.addAttribute("userList", userList); //データ件数を取得 int count = userService.count(); model.addAttribute("userListCount", count); return "login/homeLayout"; } @PostMapping("/logout") public String postLogout() { //ログイン画面にリダイレクト return "redirect:/login"; } @GetMapping("/userList/csv") public String getUserListCsv(Model model) { return getUserList(model); } }ユーザー一覧画面を作成
日付型のフォーマット指定
#dates.format()
メソッド
- 第1引数:フォーマットする値
- 第2引数:フォーマットを指定
<td th:text="${#dates.format(user.birthday, 'YYYY/MM/dd')}"></td>
動的URL
- ユーザーを1件取得して表示するには、検索するためのユーザーIDをコントローラークラスに渡す必要がある
- →
th:href
の値の中にユーザーIDを入れる
<a class="btn btn-primary" th:href="@{'/userDetail/' + ${user.userId}}">
userList.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"> <head> <meta charset="UTF-8"></meta> </head> <body> <!-- コンテンツ部分 --> <div th:fragment="userList_contents"> <div class="page-header"> <h1>ユーザー一覧</h1> </div> <table class="table table-bordered table-hover table-striped"> <tr> <th class="info col-sm-2">ユーザID</th> <th class="info col-sm-2">ユーザ名</th> <th class="info col-sm-2">誕生日</th> <th class="info col-sm-2">年齢</th> <th class="info col-sm-2">結婚</th> <th class="info col-sm-2"></th> </tr> <tr th:each="user : ${userList}"> <td th:text="${user.userId}"></td> <td th:text="${user.userName}"></td> <td th:text="${#dates.format(user.birthday, 'YYYY/MM/dd')}"></td> <td th:text="${user.age}"></td> <td th:text="${user.marriage} ? '既婚' : '未婚'"></td> <td> <!-- ユーザー詳細画面へのリンク(動的URL) --> <a class="btn btn-primary" th:href="@{'/userDetail/' + ${user.userId}}"> 詳細 </a> </td> </tr> </table> <!-- ユーザー一覧の件数 --> <label th:text=" '合計:' + ${userListCount} + '件' "></label><br/> <!-- 更新・削除処理の結果表示用 --> <label class="text-info" th:text="${result}">結果表示</label><br/> <!-- CSV出力用のリンク --> <a class="btn btn-primary" th:href="@{'/userList/csv'}">CSV出力</a> </div> </body> </html>SpringBootを起動して画面を確認!
- ログイン後のホーム画面から、ユーザー管理のリンクをクリックすると、ユーザー一覧画面が表示されます
- ユーザー登録画面で登録をすると、この画面で見れるようになりました〜^^
- 投稿日:2020-11-29T09:27:24+09:00
Java 標準入力
はじめに
学習用のメモになります。
標準入力で1行データを読み込む
//標準入力 import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String data = sc.nextLine(); } }1行に複数要素がある場合
split
メソッドで,
区切りで要素を配列(array)に代入するimport java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); String data = sc.nextLine(); String[] array = data.split("、"); System.out.println(array[0]); } }複数行の要素を出力する場合
import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); while (sc.hasNextLine()) { String data = sc.nextLine(); System.out.println(data); } } }
hasNextLine
は入力する行がまだあるかみているメソッドwhile (sc.hasNextLine())
- 投稿日:2020-11-29T05:39:20+09:00
Java オブジェクト(Object Oriented Programming)とコンストラクタ、スコープ(local,global)
1,オブジェクトとは
Object is an instance of a class that may contain attributes and methods.
車、人間、サイコロ、ピザのインスタンスを作りながら様々なルールを覚えていきましょう!2,ソースコード
ルール1,mainメソッドとは別のファイルに記述する
mainクラスを記述しているクラスをアプリケーションクラス、それ以外を、一般クラスと言う。
Main.javapublic class Main { public static void main(String[] args) { // Object Oriented Programming // object = an instance of a class that may contain attributes and methods. Car myCar1 = new Car(); Car myCar2 = new Car(); System.out.println(myCar1.make); System.out.println(myCar1.model); System.out.println(); System.out.println(myCar2.make); System.out.println(myCar2.model); myCar1.drive(); myCar1.brake(); } }※一般クラスには、mainメソッドは記述しない?
Car.javapublic class Car { String make = "Chevrolet"; String model = "Corvette"; int year = 2020; String color = "blue"; double price = 50000.00; void drive() { System.out.println("You drive the car."); } void brake() { System.out.println("You step on the brakes."); } }ルール2,コンストラクターを作る時は、クラス名と同じメソッド名にし先頭は大文字
Main.javapublic class Main { public static void main(String[] args) { // constructor = special method that is called when an object is instantiated(created). Human human1 = new Human("hiro", 25, 70); Human human2 = new Human("taro", 20, 65); System.out.println(human1.name); human1.eat(); human1.drink(); System.out.println(human2.name); human2.eat(); human2.drink(); } }※public Huan()がコンストラクタになります?
Human.javapublic class Human { String name; int age; double weight; public Human(String name, int age, double weight) { this.name = name; this.age = age; this.weight = weight; } public void eat() { System.out.println(this.name + "is eating."); } public void drink() { System.out.println(this.name + "is drinking"); } }ルール3,スコープにも注意
Main.javapublic class Main { public static void main(String[] args) { DiceRoller diceRoller = new DiceRoller(); } }※フィールドに書くと、global変数になる?
DiceRoller.javaimport java.util.Random; public class DiceRoller { Random random; int number = 0; public DiceRoller() { random = new Random(); int number = 0; roll(); } void roll() { number = random.nextInt(6) + 1; System.out.println(number); } }ルール4,コンストラクタはデータ型、順番、パラメーターの数が重要
Main.javapublic class Main { public static void main(String[] args) { Pizza pizza = new Pizza("thicc crust", "tomato", "mozzerella", "pepperoni"); System.out.println("Here are the ingredients of your pizza"); System.out.println(pizza.bread); System.out.println(pizza.sauce); System.out.println(pizza.cheese); System.out.println(pizza.topping); } }※コンストラクタのデータ型、順番、パラメーターの数に注意?
DiceRoller.javapublic class Pizza { String bread; String sauce; String cheese; String topping; public Pizza() { } public Pizza(String bread) { this.bread = bread; } public Pizza(String bread, String sauce) { this.bread = bread; this.sauce = sauce; } public Pizza(String bread, String sauce, String cheese) { this.bread = bread; this.sauce = sauce; this.cheese = cheese; } public Pizza(String bread, String sauce, String cheese, String topping) { this.bread = bread; this.sauce = sauce; this.cheese = cheese; this.topping = topping; }Java チュートリアル
Java テキストファイル(.txt .csv)への出力 java.io.FileWriter : here
Java テキストファイル(.txt .csv)からの入力 java.io.FileReader : here
Java モニターからの入力 java.util.Scanner : here
Java GUIからの入力 javax.swing.JOptionPane : here
Java Mathクラス Math.sqrt : here
Java 乱数の生成 java.util.Random : here
Java Stringクラス : here
Java Wrapperクラス : here
Java ArrayListクラス java.util.ArrayList : here
Java メソッド(method)の作り方とオーバーロード : here
Java printf()メソッド : here
Java オブジェクト(Object Oriented Programming)とコンストラクタ、スコープ(local,global) : here
- 投稿日:2020-11-29T04:35:51+09:00
Java printf()メソッド
1,printf() メソッドは、フォーマットに用いるメソッドです。
2,ソースコード
Main.javapublic class Main { public static void main(String[] args) { System.out.printf("%d This is a format String ", 123); boolean myBoolean = true; char myChar = '@'; String myString = "hiro"; int myInt = 50; double myDouble = 1000; System.out.printf("%b", myBoolean); System.out.printf("%c", myChar); System.out.printf("%s", myString); System.out.printf("%d", myInt); System.out.printf("%f", myDouble); System.out.printf("Hello %10s", myString); // System.out.printf("Hello %-10s", myString); // System.out.printf("You have this much money %.2f", myDouble); System.out.printf("You have this much money %.1f", myDouble); System.out.printf("You have this much money %f", myDouble); System.out.printf("You have this much money %+f", myDouble); System.out.printf("You have this much money %020f", myDouble); System.out.printf("You have this much money %,f", myDouble); } }Java チュートリアル
Java テキストファイル(.txt .csv)への出力 java.io.FileWriter : here
Java テキストファイル(.txt .csv)からの入力 java.io.FileReader : here
Java モニターからの入力 java.util.Scanner : here
Java GUIからの入力 javax.swing.JOptionPane : here
Java Mathクラス Math.sqrt : here
Java 乱数の生成 java.util.Random : here
Java Stringクラス : here
Java Wrapperクラス : here
Java ArrayListクラス java.util.ArrayList : here
Java メソッド(method)の作り方とオーバーロード : here
Java printf()メソッド : here
Java オブジェクト(Object Oriented Programming)とコンストラクタ、スコープ(local,global) : here
- 投稿日:2020-11-29T04:18:27+09:00
Java メソッド(method)の作り方とオーバーロード
1,Javaではクラスメソッドをオリジナルで作ることができます。
※一つ一つルールをしっかり覚える必要があります。
2,ソースコード
ルール1,static を必ずつける
Main.javapublic class Main { public static void main(String[] args) { String name = "hiro"; int age = 21; hello(name, age); int x = 3; int y = 4; int ans = add(x, y); System.out.println(ans); } public static void hello(String name, int age) { System.out.println("Hello " + name); System.out.println("You are " + age + " years old."); } public static int add(int x, int y) { int ans = x + y; return ans; } }ルール2,オーバーロードをする場合は同じメソッド名にし、異なるパラメーターを書く
Main.javapublic class Main { public static void main(String[] args) { int a = add(1, 2); System.out.println(a); int b = add(1, 2, 3); System.out.println(b); } public static int add(int a, int b) { System.out.println("This is overloaded method #1 "); return a + b; } public static int add(int a, int b, int c) { System.out.println("This is overloaded method #2 "); return a + b + c; } }Java チュートリアル
Java テキストファイル(.txt .csv)への出力 java.io.FileWriter : here
Java テキストファイル(.txt .csv)からの入力 java.io.FileReader : here
Java モニターからの入力 java.util.Scanner : here
Java GUIからの入力 javax.swing.JOptionPane : here
Java Mathクラス Math.sqrt : here
Java 乱数の生成 java.util.Random : here
Java Stringクラス : here
Java Wrapperクラス : here
Java ArrayListクラス java.util.ArrayList : here
Java メソッド(method)の作り方とオーバーロード : here
Java printf()メソッド : here
Java オブジェクト(Object Oriented Programming)とコンストラクタ、スコープ(local,global) : here
- 投稿日:2020-11-29T03:43:27+09:00
Java ArrayListクラス java.util.ArrayList
1,ArrayListの作成方法
➊ArrayList food = new ArrayList();
2,ソースコード
1次元配列
Main.javaimport java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<String> food = new ArrayList<String>(); food.add("pizza"); food.add("hambueger"); food.add("hotdog"); food.set(0, "sushi"); food.remove(2); food.clear(); for (int i = 0; i < food.size(); i++) { System.out.println(food.get(i)); } } }2次元配列
Main.javaimport java.util.ArrayList; public class Main { public static void main(String[] args) { ArrayList<ArrayList<String>> groceryList = new ArrayList<>(); ArrayList<String> bakeryList = new ArrayList<>(); bakeryList.add("pasta"); bakeryList.add("garlic bread"); bakeryList.add("donuts"); ArrayList<String> produceList = new ArrayList<>(); produceList.add("tomatoes"); produceList.add("zucchini"); produceList.add("peppers"); ArrayList<String> drinksList = new ArrayList<>(); drinksList.add("soda"); drinksList.add("coffee"); groceryList.add(bakeryList); groceryList.add(produceList); groceryList.add(drinksList); for (int i = 0; i < groceryList.size(); i++) { System.out.println(groceryList.get(i)); } } }2次元配列とfor-each roop
Main.javaimport java.util.ArrayList; public class Main { public static void main(String[] args) { String[] animals = { "cat", "dog", "rat", "bird" }; for (String animal : animals) { System.out.println(animal); } ArrayList<String> newAnimals = new ArrayList<>(); newAnimals.add("cat"); newAnimals.add("dog"); newAnimals.add("rat"); newAnimals.add("bird"); for (String animal : newAnimals) { System.out.println(animal); } } }Java チュートリアル
Java テキストファイル(.txt .csv)への出力 java.io.FileWriter : here
Java テキストファイル(.txt .csv)からの入力 java.io.FileReader : here
Java モニターからの入力 java.util.Scanner : here
Java GUIからの入力 javax.swing.JOptionPane : here
Java Mathクラス Math.sqrt : here
Java 乱数の生成 java.util.Random : here
Java Stringクラス : here
Java Wrapperクラス : here
Java ArrayListクラス java.util.ArrayList : here
Java メソッド(method)の作り方とオーバーロード : here
Java printf()メソッド : here
Java オブジェクト(Object Oriented Programming)とコンストラクタ、スコープ(local,global) : here
- 投稿日:2020-11-29T03:18:50+09:00
Java Wrapperクラス
1,Wrapperクラスの紹介です。
Wrapperクラスは、プリミティブ型のデータを参照型と同じような扱いにできるクラスです。
これによって、参照型に含まれているメソッドが使えるようになります。2,ソースコード
Main.javapublic class Main { public static void main(String[] args) { // primitive // Wrapper // --------- // ------- // boolean // Boolean // char // Character // int // Integer // double // Double Boolean a = true; Character b = '@'; Integer c = 123; Double d = 3.14; String e = "hiro"; if (a == true) { System.out.println("This is true"); } } }Java チュートリアル
Java テキストファイル(.txt .csv)への出力 java.io.FileWriter : here
Java テキストファイル(.txt .csv)からの入力 java.io.FileReader : here
Java モニターからの入力 java.util.Scanner : here
Java GUIからの入力 javax.swing.JOptionPane : here
Java Mathクラス Math.sqrt : here
Java 乱数の生成 java.util.Random : here
Java Stringクラス : here
Java Wrapperクラス : here
Java ArrayListクラス java.util.ArrayList : here
Java メソッド(method)の作り方とオーバーロード : here
Java printf()メソッド : here
Java オブジェクト(Object Oriented Programming)とコンストラクタ、スコープ(local,global) : here