Springのチュートリアルをやっていくよ!〜第0回事前準備
JJUGナイトセミナーで「チュートリアルが豊富なので試してみてね」ということなので一つ一つ試していこうと思います。
とりあえず今日は環境設定。
インストールするもの
- Java SE7(Java8対応しているけど、今のところ7でやります)
- http://spring.io/tools:Spring Tools
- gradle
基本的な流れ
「サンプルダウンロード→eclipseに取り込み→コード追加→実行」の繰り返しです。
サンプルは空のJavaファイルが入っています。
空のファイルに対して解説のページのコードを埋め込んでいけば、動くものが完成するわけです。
編集するのはeclipseでやりたいのですが、そのままだとインポートできません。
そこで、gradleでeclipseプロジェクトに変換してインポートします。
具体的には
- ダウンロードしたzipファイルを解凍する
解凍してできたinitialディレクトリに移動
cd /Users/sety/Downloads/gs-serving-web-content-master/initial
gradle eclipseコマンドを実行
うまくいけば.classpathやら.projectといったeclipseがプロジェクトとして認識するためのファイルが出来上がります。
あとはeclipse上からプロジェクトをインポートすればチュートリアルを実行する準備は完了です。
JJUG ナイトセミナ 「Spring Framework 特集」のまとめ
色々とサイトが出てきたのでリンク集。
Springプロジェクト
スライド
再入門!RESTとSpringMVC
Spring4とSpring Bootで作る次世代Springアプリケーション #jjug #jsug
デモ
http://spring-boot-demo.herokuapp.com/
Twelve-Factor App
SaaSを提供するときの方法論。
Java 7 Update 51でJavaDBが起動しなくなった場合の対応
Java SEをアップデートすると以下の例外が出てJavaDBが起動しなくなる。
java.security.AccessControlException: access denied ("java.net.SocketPermission" "localhost:1527" "listen,resolve")
これを何とかするためには、java.policyを修正をする必要がある。
Macの場合、以下の場所にある。
編集前に必ずバックアップしておく。
/Library/Java/JavaVirtualMachines/jdk1.7.0_51.jdk/Contents/Home/jre/lib/security/java.policy
Windowsの場合はC:¥Program Files¥Javaにインストールされてるはず。
java.policyに対して、以下の定義を追加します。
grant codeBase "file:/Applications/NetBeans/glassfish-4.0/javadb/lib/*" {
permission java.net.SocketPermission "localhost:1527", "listen,resolve";
};
自分の場合はNetBeansからGlassFishを起動する場合でしか使用しないため、NetBeansの下にあるlibを指定しています。
Java SE付属のJavaDBを使用する場合は、file://の後を変更すること。
元ネタはstackoverflow先生です。
http://stackoverflow.com/questions/21154400/unable-to-start-derby-database-from-netbeans-7-4
チェック例外はコンパイルをすり抜ければOK
チェック例外でもコンパイルさえ誤魔化せばなんとでもなる。
その証明をしてみます。
こんなメソッドがあって
public class Employee {
public String getMessage(){
throw new RuntimeException();
}
}
こんな呼び出しメソッド(別クラス)があります。
public class EmpMain {
public static void main(String[] args) {
Employee employee = new Employee();
employee.getMessage();
}
}
コンパイルエラーは当然ありません。
実行したら正常に終了します。
ここで、最初のEmployeeクラスを以下のように修正します。
public class Employee {
public String getMessage() throws Exception {
throw new Exception();
}
}
当然、呼び出し側のメソッドはコンパイルエラーになります。
でもEmployeeクラス自体はコンパイル可能です。
じゃあ新しいEmployeeクラスを古いバイトコードから呼び出したらどうなるか。
> java EmpMain
Exception in thread "main" java.lang.Exception
at byteread.Employee.getMessage(Employee.java:9)
at byteread.EmpMain.main(EmpMain.java:8)
正常に実行できました。
ソースではコンパイルエラーになりますが、バイトコードでは問題がないことがわかりますね。
ちなみに逆のパターンだとどうでしょう。
employee.getMessage();がIOExceptionをスローしてくるのであれば、コンパイルは正常に終了します。
public class EmpMain {
public static void main(String[] args) {
Employee employee = new Employee();
try {
employee.getMessage();
System.out.println("正常終了");
} catch (IOException e) {
System.out.println("IOEcxeption発生!");
}
}
}
しかし、Employeeクラスが修正され、getMessageメソッドがスローしなくなります。 そうするとソースコードはコンパイルエラーですが、バイトコードだけなら正常に実行できます。
Lombokの生成したバイトコードを見てみる
2013年にブレークしたLombok。 詳しくは
http://d.hatena.ne.jp/nowokay/20130730
を参照で。
まずはgetter/setter。
import lombok.Data;
@Data
public class Employee {
private String empCd;
private String name;
private int age;
}
上のようなソースから生成されたバイトコードは
public void setEmpCd(java.lang.String empCd);
0 aload_0 [this]
1 aload_1 [empCd]
2 putfield byteread.Employee.empCd : java.lang.String [13]
5 return
のようにsetterがきちんと生成されてる。
すばらし~。
下手に人間が書くよりミスが少なくていいと思います。
あとはチェック例外でもスローできちゃうSneakyThrows。
発生することがない上に、キャッチしてもあまり意味が無いチェック例外を綺麗に無視できるのでとても便利です。
@SneakyThrows
public String getMessage(){
throw new Exception();
}
このソースのバイトコードはこんな感じ。
例外のインスタンスを生成し、スローしているだけの処理に見えます。
public java.lang.String getMessage();
0 new java.lang.Exception [13]
3 dup
4 invokespecial java.lang.Exception() [15]
7 athrow
8 astore_1 [$ex]
9 aload_1 [$ex]
10 athrow
比較のために以下のソースのバイトコードも並べてみます。
public String getMessage(){
throw new RuntimeException();
}
バイトコードは例外のクラス名が違う以外はほぼ同じでした。
public java.lang.String getMessage();
0 new java.lang.RuntimeException [13]
3 dup
4 invokespecial java.lang.RuntimeException() [15]
7 athrow
これを見るとチェック例外もランタイム例外も実行においては関係ないっていうことです。たぶん。
要はコンパイラのチェックさえ通してしまえばいいんです。
文字列結合とバイトコード
今日もバイトコードのお勉強。
文字列の+演算子の結合パターンを色々と。
まずはリテラルオンリー
public static void main(String[] args) {
System.out.println("Hello " + " World" + "!!");
String s = "寿限無 寿限無 "
+ "五劫の摺り切れ"
+ "海砂利水魚の"
+ "水行末 雲来末 風来末"
+ "食う寝る所に住む所"
+ "藪柑子 ブラコウジ"
+ "パイポ パイポ パイポの シューリンガン"
+ "シューリンガンのグーリンダイ"
+ "グーリンダイのポンポコピーの"
+ "ポンポコナーの"
+ "長久命の長助";
System.out.println(s);
}
上のソースのように、リテラルの文字列を+で結合した場合はコンパイル時に結合してくれる。
だからSQLなどの長い文字列を書く場合は遠慮せずに結合しちゃっていい。
間違えてもStringBuilderとかはやっちゃダメ。
ちなみにバイトコードはこんな感じ。
0 getstatic java.lang.System.out : java.io.PrintStream [16]
3 ldc <String "Hello World!!"> [22] // ←結合済みの文字列
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
8 ldc <String "寿限無 寿限無 五劫の摺り切れ海砂利水魚の水行末 雲来末 風来末食う寝る所に住む所藪柑子 ブラコウジパイポ パイポ パイポの シューリンガンシューリンガンのグーリンダイグーリンダイのポンポコピーのポンポコナーの長久命の長助"> [30] // ←長くても結合してくれる
10 astore_1 [s]
11 getstatic java.lang.System.out : java.io.PrintStream [16]
14 aload_1 [s]
15 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
18 return
Stringだけではなく、intなどの数値が入っていた場合でも大丈夫。
public static void main(String[] args) {
System.out.println(2014 + "Year!" + "Hello World" + "!!");
System.out.println("Hello " + 2014.1 + " World" + "!!");
System.out.println("Hello " + " World" + 11);
}
上のようなソースでも、バイトコードでは結合した文字列になる。
intの部分がどこで出てきても大丈夫。
0 getstatic java.lang.System.out : java.io.PrintStream [22]
3 ldc <String "2014Year!Hello World!!"> [28]
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [30]
8 getstatic java.lang.System.out : java.io.PrintStream [22]
11 ldc <String "Hello 2014.1 World!!"> [36]
13 invokevirtual java.io.PrintStream.println(java.lang.String) : void [30]
16 getstatic java.lang.System.out : java.io.PrintStream [22]
19 ldc <String "Hello World11"> [38]
21 invokevirtual java.io.PrintStream.println(java.lang.String) : void [30]
24 return
足し算が発生する場合でも大丈夫。
public static void main(String[] args) {
System.out.println(20 + 14 + "Year!" + "Hello World" + "!!");
System.out.println("Hello World" + (20 + 14) + "!!");
}
バイトコードになった時は計算した後の値を使用したリテラルになっている。
0 getstatic java.lang.System.out : java.io.PrintStream [22]
3 ldc <String "34Year!Hello World!!"> [28] // 計算されてる!
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [30]
8 getstatic java.lang.System.out : java.io.PrintStream [22]
11 ldc <String "Hello World34!!"> [36] // 計算されてる!
13 invokevirtual java.io.PrintStream.println(java.lang.String) : void [30]
16 return
例えば、int mega = 1024 * 1024;という式はコンパイル時に計算した結果が代入される。
変数を含む場合
以下のように文字列結合に変数を含む場合は状況が異なる。
public static void main(String[] args) {
int i = 2014;
System.out.println(i + "Year!" + "Hello World" + "!!");
}
ソースを読めば、iが2014以外になることはありえない。
でもコンパイルしたバイトコードでは、StringBuilderを利用して文字列を結合している。
0 sipush 2014
3 istore_1 [i]
4 getstatic java.lang.System.out : java.io.PrintStream [22]
7 new java.lang.StringBuilder [28]
10 dup
11 iload_1 [i]
12 invokestatic java.lang.String.valueOf(int) : java.lang.String [30]
15 invokespecial java.lang.StringBuilder(java.lang.String) [36]
18 ldc <String "Year!"> [39]
20 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [41]
23 ldc <String "Hello World"> [45]
25 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [41]
28 ldc <String "!!"> [47]
30 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [41]
33 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [49]
36 invokevirtual java.io.PrintStream.println(java.lang.String) : void [53]
39 return
バイトコードの処理の通りにソースコードを直すと以下のようになる。
public static void main(String[] args) {
int i = 2013;
System.out.println(
new StringBuilder(String.valueOf(i))
.append("Year!")
.append("Hello World")
.append("!!").toString());
}
「文字列結合すると中間的なStringオブジェクトが生成されて効率が云々」みたいなことを言う人がいる。
しかし、コンパイルされたバイトコードを見るとStringBuilderを使って効率的な文字列結合が行われている。
ループが絡むと話が違ってくるけど、コンパイラがいい感じに処理してくれるので何が何でもStringBuilderを使う必要はない。
変数の出現位置によっては、事前に結合してくれる。
public static void main(String[] args) throws Exception {
System.out.println("SELECT" + " * " + "FROM" +
" PIYO " + "WHERE" + " ID " + "=" + args[0] +
" ORDER" + " BY " + "NAME");
}
上の例の場合、args[0]までの文字列は結合しているが、それ以降の文字列は1つずつappendしている。
0 getstatic java.lang.System.out : java.io.PrintStream [29]
3 new java.lang.StringBuilder [35]
6 dup
7 ldc <String "SELECT * FROM PIYO WHERE ID ="> [37]
9 invokespecial java.lang.StringBuilder(java.lang.String) [39]
12 aload_0 [args]
13 iconst_0
14 aaload
15 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [42]
18 ldc <String " ORDER"> [46]
20 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [42]
23 ldc <String " BY "> [48]
25 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [42]
28 ldc <String "NAME"> [50]
30 invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [42]
33 invokevirtual java.lang.StringBuilder.toString() : java.lang.String [52]
36 invokevirtual java.io.PrintStream.println(java.lang.String) : void [56]
39 return
定数を使った場合
変数が絡む場合でも、staticかつfinalで宣言された『定数』の場合はリテラルと同じ扱いになる。
private static final int YEAR = 2014;
public static void main(String[] args) {
System.out.println(YEAR + "Year!" + "Hello World" + "!!");
}
変数の場合と異なりコンパイル時に文字列を結合している。
0 getstatic java.lang.System.out : java.io.PrintStream [26]
3 ldc <String "2014Year!Hello World!!"> [32]
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [34]
8 return
ローカル変数をfinalで宣言した場合でも結果は同じになる。
効率を追求するならローカル変数すべてをfinalにするといいかもしれない。(大人しく定数にしろと言われそうだけど・・・)
今日のまとめ
コンパイラが割りといい感じに処理してくれるので、文字列結合は全部StringBuilderにするという置換は不要。
やってもやらなくてもあまり変わりないので、ソースが読みやすい方を選択した方がいい。
static finalを付けない定数もどきは意味が無い。
今日は思わぬ寄り道。
Lambdaに辿り着くまで道は長そう。
バイトコードの読み方その1 とりあえず基本
今年の目標その1「バイトコードの読み方をマスターする」
1.命令の種類
WikiPediaが妙に詳しい。
http://ja.wikipedia.org/wiki/Java%E4%BB%AE%E6%83%B3%E3%83%9E%E3%82%B7%E3%83%B3
本当はJava言語仕様を読むべきだけど、ここだけでもなんとかなる感じ。
大体の命令は、スタック領域とローカル変数領域を操作する。
2.スタック領域とローカル変数領域
スタック領域にはインスタンスやプリミティブの値を格納する。
メソッド呼び出しや演算などの命令の実行は、すべてスタック領域に格納されているオブジェクトから行う。
命令を実行をすると使用したスタックの値が消費される。
例えば 1+1の演算の場合(後ろの[]はスタック領域の状態)
iconst_1 // スタックに1を追加 [1]
iconst_1 // スタックに1を追加 [1,1]
iadd // スタック領域の先頭と次の値を足してスタック領域に格納する [2]
というようになる。
3.インスタンスメソッドの呼び出し
System.out.println("Hello World!")をeclipseのクラスファイルエディターで見ると以下のようになる。
0 getstatic java.lang.System.out : java.io.PrintStream [16]
3 ldc <String "Hello World!!"> [22]
5 invokevirtual java.io.PrintStream.println(java.lang.String) : void [24]
・0の部分の命令であるgetstaticは、java.lang.Systemのoutフィールドを取得してスタック領域に格納する。 [PrintStreamインスタンス]←スタック領域の状態
・3の命令は"Hello World!"という文字列をスタック領域に格納する。 [PrintStreamインスタンス,"Hello World!"]←スタック領域の状態
・5の命令はinvokevirtual命令でインスタンスメソッドを実行する。引数はスタック領域の先頭の値、実行するメソッドを持つインスタンスはスタック領域の次の値。これを実行するとスタック領域は空になる。
invokevirtualで実行したメソッドが戻り値を持っている場合、スタック領域には戻り値が格納される。
例えば戻り値がintだとしたら、メソッド実行後のスタック領域は[1]のようになる。
invokevirtual命令を実行すると、スタック領域に格納したインスタンスは取り出されてなくなってしまう。
そのため、メソッドを連続で呼び出す場合などは、命令の実行前にdup命令でスタック領域の先頭にあるインスタンスを複製したり、ローカル変数領域から取得したりなどを行う。