- 投稿日:2020-11-08T18:56:48+09:00
JavaからRustをJNA(Java Native Access)で実行する
はじめに
動機
システムプログラミング言語(?)であるRustの話を聞いていると、FFI(Foreign function interface)の話が少なからず出てきます。
ということで普段書いているJavaから呼び出します。対象読者
JavaとRustが多少読め、MavenというJava用のプロジェクト管理用ツールが存在していることを知っている方。
難しいことはしておらず、プロジェクトの雛形を作成したレベルの話です。環境
開発環境はVSCodeを使用します。
RustとJavaの実行環境はDockerで構築します。
DockerFiile
ベースはMicrosoft提供のJavaの開発環境のサンプル(Debean 10)を、フォークして自分用に弄ったものを使用しています。変更箇所としてはJavaのversionを14→11に変更した程度です。
弄ったものそこにRustのコンパイラをインストールします。以下を追記。
DockerFiileENV RUSTUP_HOME=/usr/local/rustup \ CARGO_HOME=/usr/local/cargo \ PATH=/usr/local/cargo/bin:$PATH RUN set -eux; \ \ url="https://static.rust-lang.org/rustup/dist/x86_64-unknown-linux-gnu/rustup-init"; \ wget "$url"; \ chmod +x rustup-init; \ ./rustup-init -y --no-modify-path --default-toolchain nightly; \ rm rustup-init; \ chmod -R a+w $RUSTUP_HOME $CARGO_HOME; \ rustup --version; \ cargo --version; \ rustc --version; RUN apt-get update && apt-get install -y lldb python3-minimal libpython3.7 python3-dev gcc \ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts内容としては
- 環境変数の追加
- 必要なrustのコンポーネントをインストール
- Rustで必要になるデバッガ、python、gccのインストール
を行っていますインストールするRustをここでnightlyとしている理由は後述します。
Rust
今回Rust側で作成したファイルは以下です
treeworkspace │ Cargo.toml │ ├─sample-jna │ │ Cargo.toml │ │ │ └─src │ lib.rs │ └─scripts cargo-build.shworkspaceのトップレベルでcargoのコマンドが使いたかったため、このような構成となりました。
以下解説
Cargo.toml
Cargo.toml[workspace] members = ["sample-jna"] [profile.release] lto = true上2行でworkspace内のsample-jnaディレクトリをプロジェクトとして認識しています。
lto = trueはbuild時のファイルサイズ削減用のオプションです。sample-jna/Cargo.toml
Cargo.toml[package] name = "sample-jna" version = "0.1.0" authors = ["uesugi6111 <59960488+aburaya6111@users.noreply.github.com>"] edition = "2018" [lib] crate-type = ["cdylib"][package]はcargo new で作成されるもので問題ありません。
[lib]のcrate-typeがコンパイル後のタイプとなります。別言語から呼び出す想定のダイナミックライブラリは、cdylibを指定するようリファレンスに書かれているので、それに従います。lib.rs
ここがライブラリ本体となります。今回は以前書いて残していたエラトステネスの篩に似たアルゴリズムで引数までの素数を列挙し、その個数を返すだけのプログラムを用意しました。
lib.rs#[no_mangle] pub extern fn sieve_liner(n: usize) -> usize{ let mut primes = vec![]; let mut d = vec![0usize; n + 1]; for i in 2..n + 1 { if d[i] == 0 { primes.push(i); d[i] = i; } for p in &primes { if p * i > n { break; } d[*p * i] = *p; } } primes.len() }通常のコンパイルでは関数名は他の名称に変換されてしまい、ほかプログラムなどから呼び出す際に、名前がわからなくなってしまいます。それを防ぐために#[no_mangle](直訳:切り刻み無し)を関数に付与します。
cargo-build.sh
cargo-build.sh#!/bin/bash cargo build --release -Z unstable-options --out-dir ./src/main/resourcesライブラリのbuildスクリプトになります。
--release
releaseオプションでのbuildを指定します。
-Z unstable-options --out-dir ./src/main/resources
build後に出力するディレクトリを指定するオプションとなっています。しかしこのオプションが使えるのはnightlyのみとなっています。
そのためDockerで構築する環境へのインストールは、nightly指定にしています。ディレクトリの指定先はJava側でコンパイルされたときにjarファイル内に配置される場所に設定しました。
Java
Java側で作成したファイルは以下になります。
workspace │ pom.xml └─src └─main ├─java │ └─com │ └─mycompany │ └─app │ App.java │ └─resourcesやけにディレクトリが深いですが、特に意味はありません。
pom.xml
以下を<dependencies>へ追記します
pom.xml<dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.6.0</version> </dependency>App.java
App.javapackage com.mycompany.app; import java.util.ArrayList; import java.util.List; import com.sun.jna.Library; import com.sun.jna.Native; public class App { private static final int N = 100000000; public interface SampleJna extends Library { SampleJna INSTANCE = Native.load("/libsample_jna.so", SampleJna.class); int sieve_liner(int value); }; public static void main(String[] args) { System.out.println("N = " + N); System.out.println("FFI :" + executeFFI(N) + "ms"); System.out.println("Java :" + executeJava(N) + "ms"); } public static long executeFFI(int n) { long startTime = System.currentTimeMillis(); SampleJna.INSTANCE.sieve_liner(n); return System.currentTimeMillis() - startTime; } public static long executeJava(int n) { long startTime = System.currentTimeMillis(); sieveLiner(n); return System.currentTimeMillis() - startTime; } public static int sieveLiner(int n) { List<Integer> primes = new ArrayList<>(); int d[] = new int[n + 1]; for (int i = 2; i < n + 1; ++i) { if (d[i] == 0) { primes.add(i); d[i] = i; } for (int p : primes) { if (p * i > n) { break; } d[p * i] = p; } } return primes.size(); } }Rust側に実装したロジックと同様のものを実装し、実行時間を比較します。
ライブラリの呼び出しは
SampleJna INSTANCE = Native.load("/libsample_jna.so", SampleJna.class);
で行っています。
Native.load(ライブラリのPath,ライブラリを定義したのinterface)という形で記述するようです。
今回ライブラリはmain/resources直下に配置する予定なので絶対パス(?)で表記しています。Maven
ここまでで本来動作確認はできるのですが、jarにすることを考えた際の設定もしてみました。
jarを作成するまでの流れ
- Rust をコンパイルしてJava側のresourcesディレクトリに配置
- Java側のコンパイルこれをMavenの機能を利用し、ワンアクションで行います。
maven-assembly-plugin
jarに依存ライブラリを含める
maven-assembly-plugin<plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>/</classpathPrefix> <mainClass>com.mycompany.app.App</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin>exec-maven-plugin
maven の処理中でシェルスクリプトを実行するために必要となります。
exec-maven-plugin<plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.3.2</version> <executions> <execution> <id>dependencies</id> <phase>generate-resources</phase> <goals> <goal>exec</goal> </goals> <configuration> <workingDirectory>${project.basedir}</workingDirectory> <executable>${project.basedir}/scripts/cargo-build.sh </executable> </configuration> </execution> </executions> </plugin>少し解説
phase
シェルスクリプトを実行するタイミングを設定します。Mavenではライフサイクルという概念が存在するので、実行したいタイミングに合ったものを指定します。リファレンス
executable
ここで実行したい対象を指定します。pom.xml
ここまで適応し終えたファイル
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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.mycompany.app</groupId> <artifactId>my-app</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>my-app</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>net.java.dev.jna</groupId> <artifactId>jna</artifactId> <version>5.6.0</version> </dependency> </dependencies> <properties> <jdk.version>11</jdk.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <build> <plugins> <plugin> <artifactId>maven-assembly-plugin</artifactId> <version>3.3.0</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <classpathPrefix>/</classpathPrefix> <mainClass>com.mycompany.app.App</mainClass> </manifest> </archive> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M3</version> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>1.3.2</version> <executions> <execution> <id>dependencies</id> <phase>generate-resources</phase> <goals> <goal>exec</goal> </goals> <configuration> <workingDirectory>${project.basedir}</workingDirectory> <executable>${project.basedir}/scripts/cargo-build.sh </executable> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>実行
workspaceのルートで以下を実行
mvn packageすると
[INFO] --- maven-assembly-plugin:3.3.0:single (make-assembly) @ my-app --- [INFO] Building jar: /workspace/target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------のようなログが出力されてコンパイルが完了します。
あとは表示されたパスに出力されているjarを実行してください。
例java -jar ./target/my-app-1.0-SNAPSHOT-jar-with-dependencies.jar
出力
N = 100000000 FFI :1668ms Java :3663ms10^8までの素数の数のカウントでかかった、JavaとFFI(Rust)の時間(ms)が出力されました。
Nを小さくするとJavaの方が早くなるため、大きなオーバーヘッドがあるのでしょうかわかりません。さいご
とりあえずは動いたということで一旦完了とします。
使ったソースになります
https://github.com/uesugi6111/java-rust普段触らない部分の話が多くまだわからないことが多いですがぼちぼち調べていきます。
謎
- JNA以外のJavaからの呼び出し方法
- Rustの関数での単純な数値以外の返し方
- 投稿日:2020-11-08T18:48:07+09:00
ゼロから始めるSpring Boot その2
目標
複数回に分けて実施予定です。
- SpringBootとは何かをざっくり知る
- 環境構築&Hello World!を表示させる
- パラメータやオブジェクトの受け渡しをする ←今回やること
- 他の機能にも触れてみる
前置き
前回はHello Worldを表示するために下の2つをやりました。
@RestController
で文字列を表示@Controller
でindex.htmlを表示プロジェクトは前回作ったものを引き続き使用します。
具体的には以下のようなことをやります。
これを実現するためには、主に2種類の方法があります。
まずは@RequestParam
から使っていきましょう。パラメータを受け渡そう(RequestParam編)
index.html
の<body>
部分を以下のように書き換えます。index.html<body> <form action="/hello" method="get"> 名前: <input type="text" name="username"> <input type="submit" value="送信"> </form> </body>
各種タグの説明についてはここをクリック。
タグ 要素名 内容 form action リクエストを送るURLを指定。 method 送信方法をGET、POSTから選択して指定。 input type="text" テキストフィールド。 name パラメータに付ける名前。java側で取得する際に使用。 type="submit" パラメータを送信するためのボタン。 value コンテンツ(テキストフィールドやボタンなど)の中身に表示させる文字列を指定。
続けて
DemoController.java
に以下のコードを追加します。DemoController.javapackage com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @Controller public class DemoController { //前回作成したhelloメソッドと同じです。(メソッド名が被るのでindexに変更) @RequestMapping(value="/",method=RequestMethod.GET) public ModelAndView index() { ModelAndView mav=new ModelAndView(); mav.setViewName("/index"); return mav; } //今回追加するメソッドです。 @GetMapping("/hello") public ModelAndView hello(@RequestParam String username,ModelAndView mav) { mav.addObject("username",username); mav.setViewName("/hello"); return mav; } }
@RequestParam
引数の先頭に記述。これを付けることにより、リクエストパラメータの値が自動的に変数に格納されます。また、書き方が色々あるのでまとめて紹介します。
例として、送られてくるパラメータの名前は「username
」とします。
@RequestParam
String username
今回使用している書き方です。変数名(赤字)と、受け取りたいパラメータの名前を同じにする必要があります。変数名と同じパラメータが存在しないとエラーになります。記述例@RequestParam String username //OK @RequestParam String name //NG
@RequestParam("username")
String username
一番無難な書き方です。アノテーション側で、受け取りたいパラメータの名前を指定します。存在しない名前を指定してしまうとエラーになります。この時、変数名は任意の名前でOKです。記述例@RequestParam("username") String username //OK @RequestParam("username") String name //OK @RequestParam("name") String username //NG
@RequestParam(value="username",required=false)
String username
あまり出てきません。value
でパラメータの名前を、required
でパラメータの存在を必須条件にするかどうかが選べます。デフォルトはtrueです。
falseにすると指定したパラメータが存在しなくてもエラーになりません(当然パラメータの受け渡しは出来ませんが・・・)。これも変数名は任意の名前でOKです。記述例@RequestParam(value="username",required=false) String username //OK @RequestParam(value="name",required=false) String name //OK でも受け渡しは出来ない @RequestParam(value="name",required=true) String name //NG
続いてパラメータを受け取るHTMLファイルを作成します。
index.html
と同じフォルダにhello.html
という名前でHTMLファイルを作成し、中身を以下のように書き換えてください。hello.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <span th:text="${username}"></span>さん、Hello World! </body> </html>
<html xmlns:th="http://www.thymeleaf.org">
この記述によってThymeleafの機能をth
から始まるタグ名で呼び出せるようになります。
Thymeleafってなんだ?については前回の記事や公式ドキュメントへ!
th:text="${パラメータ名}"
ModelAndView
に格納されたパラメータを取得してコンテンツに表示します。<div>
や<td>
、<p>
タグなどの時に使用します。th:value="${パラメータ名}"
th:text
と同様。こちらは<input type="text">
などで使用します。他のタグについては登場時に都度解説していきます。
それでは
http://localhost:8080
にアクセスしてみましょう。
適当に名前を入力して「送信」ボタンをクリック。
入力した名前が次のページで表示できました!
上記が@RequestParam
によるパラメータの受け渡しです。
画像でまとめると以下のようなイメージになります。
パラメータを受け渡そう(PathVariable編)
続いてはGET方法でのみ可能なパラメータの受け渡し方法です。
この方法では、URLに含まれている文字列をそのまま取得することができます。
(例えばhttp://localhost:8080/hello/xxx
にアクセスしたとき、xxx
をパラメータとして取得できます)
DemoController.java
のhello
メソッドを書き換えます。DemoController.java@GetMapping("/hello/{username}") public ModelAndView hello(@PathVariable String username,ModelAndView mav) { mav.addObject("username",username); mav.setViewName("/hello"); return mav; }
@GetMapping
これ自体は前回やっているのですが、問題は引数の方です。
URLから取得したい部分を{}
で囲み、後述する@PathVariable
で呼び出す用の名前を中に入れます。
@PathVariable
引数の先頭に記述。これも書き方が複数ありますが、
@RequestParam
と同じのため割愛します。また、パラメータは複数取得可能です。
たとえば「http://localhost:8080/hello/hatopo/24
にアクセスしたとき、hatopo
と24
を取得したいなあ」といった場合は以下のように記述します。記述例@GetMapping("/hello/{username}/{age}") public ModelAndView hello(@PathVariable String username,@PathVariable int age,ModelAndView mav) { // 中身は省略 return mav; }
それでは
http://localhost:8080/hello/xxx
にアクセスしてみましょう。
(xxxは任意の文字列を入れてください)
/hello
以降のパラメータを表示できました!
パラメータの受け渡し感が無いな・・・と思う人へ
完全に蛇足ではありますが、テキストフィールドの値をURLにくっ付けてページ遷移させるjavascriptを記述することでパラメータの受け渡しができます。
index.html
の<body>
内を以下のように書き換えます。index.html<body> 名前:<input type="text" id="name"> <input type="submit" value="送信" onclick="send()"> <script> function send(){ var name = document.getElementById("name").value; if(name == "")name = "default_name"; location.href = "http://localhost:8080/hello/" + name; } </script> </body>
http://localhost:8080/
にアクセスして名前を入れて「送信」ボタンを押すと入力値が表示できます!
以上がパラメータの受け渡しになります。続けてオブジェクトの受け渡しもやりましょう。
オブジェクトの受け渡し
オブジェクトについて知らない人は、とりあえず今は
パラメータの入った箱(クラス)
といった理解で大丈夫です。まず、
DemoController
と同じフォルダにUser
という名前でクラスを作成し、以下のコードを追加してください。User.javapackage com.example.demo; public class User { private String name; private int age; }続けて
getter
、setter
と呼ばれる、javaがパラメータの取得/設定をするために必要なメソッドを追加します。
手動で書いてもOKですが、自動で追加する場合はUserクラス内の適当な場所にカーソルを置いてソース
>>getterおよびsetterの生成
の順にクリック。
すべて選択
>>生成
の順にクリック。
コード全体が以下のようになればOKです。User.javapackage com.example.demo; public class User { private String name; private int age; // ↓自動生成によって追加されるコード public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } // ↑自動生成によって追加されるコード }※Lombokというプラグインを利用するとこのコードの記述が不要になりますが、この記事では扱いません。
続けて
index.html
を以下のように書き換えます。index.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <form action="/hello" method="get" th:object="${user}"> 名前:<input type="text" name="name"><br> 年齢:<input type="text" name="age"><br> <input type="submit" value="送信"> </form> </body> </html>
th:object="${オブジェクト名}"
これを記述したタグ内にあるパラメータをまとめて1つのオブジェクト名で参照できるようにします。この例だと、name
とage
をuser
という名前の箱に入れます。
DemoController.java
のhello
メソッドも書き換えましょう。DemoController.java@GetMapping("/hello") public ModelAndView hello(@ModelAttribute User user,ModelAndView mav) { mav.addObject("user",user); mav.setViewName("/hello"); return mav; }
@ModelAttribute
引数の先頭に記述。フォームから送られてきたオブジェクトを変数に格納します。
このとき、Springが裏で下のようなことをしてくれます。内部の処理User user = new User(); user.setName(name); user.setAge(age);そのため、
User
クラスにname
というフィールド変数が定義されていなかったり、setAge
メソッドが定義されていなかったりすると正しく動きません。また、このアノテーションはメソッドに書くこともできますが、使い道がだいぶ異なるためこの記事では割愛します...
最後に
hello.html
を以下のように書き換えましょう。hello.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <div th:object="${user}"> <span th:text="*{name}"></span>さんは <span th:text="*{age}"></span>歳です! </div> </body> </html>
th:text="*{パラメータ名}"
th:text="${オブジェクト名.パラメータ名}"
の省略形です。
th:object={}
を記述したタグ内においては上記のように省略して書くことができます。
それでは
http://localhost:8080
にアクセスしましょう。
適当に名前と年齢を入力して「送信」ボタンを押すと・・・
名前と年齢を表示できました!
おさらい
@RequestParam
でパラメータの受け渡し@PathVariable
でURLパラメータの取得@ModelAttribute
でオブジェクトの受け渡し今回はここまでです。お疲れ様でした。
次回からはより本格的な機能について触れていきます。
- 投稿日:2020-11-08T18:26:24+09:00
【Java】H2DB・JDBCの関係
H2 Database特徴
- H2のメリット
- Javaのプログラムに組み込めること
- Embedded and server modes; in-memory databases
The main features of H2 are:
- Very fast, open source, JDBC API
- Embedded and server modes; in-memory databases
- Browser based Console application
- Small footprint: around 2 MB jar file size
H2 Derby HSQLDB MySQL PostgreSQL Pure Java Yes Yes Yes No No Memory Mode Yes Yes Yes No No Encrypted Database Yes Yes Yes No No
- Pure java:Javaで作られてる
- ex: H2/Derby/HSQLDB
- 自分のアプリケーションに埋め込めるのでアプリ起動時に自動で立ち上がる
- Embedded / server modes / in-memory databasesの3つのモードが使える
server modes
- h2サーバ立ち上げてたら他のサーバからもアクセスできる
in-memory databases modes
- インメモリで動かす
- Java仮想マシン上のJavaプログラム内で
h2.start();
で起動- インメモリはDataをインサート文でSQL文でデータを吐き出した時に、HDDや外部にデータが保存されずメモリ上に保存される
- 再起動で消える
- メモリの中にしかないので他のサーバから見れない
- インメモリでは次回に起動した時にDBもクリアになっているので開発ではよく使う!
Embedded modes
- in-memory同様、Java仮想マシン上のJavaプログラム内で動く
- データは外部にHDD/SSD保存
- 外からアクセスできる
- クラッシュしてもデータ残ってる
- DBのユニットテストするときにDB更新かけてしまうと同じユニットテストできない
H2とJDBCの関係
- 従来はDBに対してApplicationからオラクルなどDB固有のライブラリを使って直接アクセスすることが多かった(C++でOracleLibを使うなど)
- この方法ではDBを自由に変更、製品を組み替えできない (Oracle版とかMySQL版とかにできない)
- →JavaではJDBCが解決!
DBアクセス基本
- JDBC:SQL抽象化メカニズム
- 直接Oracle叩くのではなくJDBCの
jdbc_url
を元に指定先のDB (Oracleなど)にアクセス- オラクルのライブラリをJDBCがアクセスしRDBにアクセスことで一段抽象化する
jdbc_url
を読んでh2と書いてる場合、JDBCはh2のライブラリを通してH2DBにアクセスアプリとRDBが直接結合(密結合)だったのが、JDBC(抽象化モジュール)を介することで、アプリとRDBが間接的になる!(疎結合になる)
- アプリはDBが何であるかに関与する必要なく、URLでどこにアクセスすればいいかのみ知っていればいい(JDBCがURLでDBにアクセス)
RDBが何であってもアプリ側で変更不要でJDBCがいい感じにやってくれるということ!
- 投稿日:2020-11-08T17:43:02+09:00
【Java】リクエストを投げて画面を表示②(GET/POST)
リクエストを投げて結果を画面を表示(thymeleaf)
(ゴール) htmlのフォームでPOSTされた値(姓名)を埋め込んで画面に表示
Project Root └─src └─ main └─ java └─ com.example └─ demo └─trySpring └─HelloController.javaProject Root └─src └─ main └─ resources └─templates └─hello.html └─helloResponse.htmlGETでhello.htmlに画面遷移する
- 入力フォーム作成
hello.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" lang="en"> <head> <meta charset="UTF-8"> <title>Hello World</title> </head> <body> <h1>Hello World</h1> <form method="post" action="/hello"> 苗字: <input type="text" name="text1" th:value="${text1_value}"/> <br> 名前: <input type="text" name="text2" th:value="${text2_value}"/> <br><br> <input type="submit" value="クリック"/> <br> </form> </body> </html>POSTするコントローラークラス(HelloController)作成
- POST先のplaceholderへは
model.addAttribute
で一個の文字列として繋げて渡してあげる- GET/POSTは同じエンドポイント(URL)でOK
- devtoolのネットワークで確認したらGET/POSTリクエストが投げられていることが確認できる
HelloController.javaimport lombok.Getter; 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 org.springframework.web.bind.annotation.RequestParam; @Controller public class HelloController { @GetMapping("/hello") private String getHello() { return "hello"; } @PostMapping("/hello") public String postRequest(@RequestParam("text1")String text1,@RequestParam("text2")String text2, Model model){ //HTMLの画面から受け取った文字列をModelに登録 model.addAttribute("userName","私は"+ text1 +" "+ text2+"です。"); return "helloResponse"; //helloResponse.htmlに画面遷移 } }POST先のhelloResponse.html作成
- テンプレートとモデルの値(入力された姓名)をマージした結果を返す
helloResponse.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF8"></meta> <title>Response Sample</title> </head> <body> <h1>Hello Response</h1> <!-- Modelからの値を受け取り受け取った文字を表示 プレースホルダに指定したvalue(userName)をいれる --> <p th:text="${userName}"></p><body> </html>URLにアクセスして確認!
- 投稿日:2020-11-08T16:28:06+09:00
いつもintな初心者のBigDecimal備忘録
はじめに
Java学習歴3ヶ月の者です。
研修でSpringをやっているのですが、データ型がNumberのものは
整数にしかならないものもintではなくBigDecimalを使いました。
数値といえばintかdoubleしか使ってこず、BigDecimalの処理にとまどいました。
BigDecimalについてきちんとまとめられている記事は多くありますが、
実際使ったものだけ備忘録としてまとめてみます。計算したい
四則演算は「+」、「-」、「*」、「/」ではありません。
演算 メソッド 例 加算(A + B) add A.add(B) 減算(A - B) subtract A.subtract(B) 乗算(A * B) multiply A.multiply(B) 徐算(A / B) divide A.divide(B) 徐算は使ったことないですが、少数になる場合は引数が3つ必要です。
A.divide(B, 小数点以下の桁数, 丸め方法(切り上げ、切り捨て、四捨五入など))
今回は使ってないので割愛します。比較したい
「compareTo」を使います。
比較演算子 戻り値 例 A < B AがBより小さい場合は-1を返す A.compareTo(B) < 0 A > B AがBより大きい場合は1を返す A.compareTo(B) > 0 A == B AとBが等しい場合は0を返す A.compareTo(B) == 0 研修で調べた時は「より小さい(<)」ではなく、「以下(<=)」にしたかったのでこんな感じで書きましたが
// 「より大きい」ではない(頭ぐちゃぐちゃになるわ) if (!(A.compareTo(B) == 1)) { } // これでよかったみたい if (A.compareTo(B) <= 0) { }参考
https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/math/BigDecimal.html
https://www.sejuku.net/blog/25564
- 投稿日:2020-11-08T15:13:17+09:00
Factory Method Pattern
Factory Method Pattern
packageが異なるケースなど、newでインスタンスを生成できないケースでは、Factory Method Patternを利用して、インスタンスを取得する
factory抽象クラスはインスタンス化する骨組みを定義し
factory抽象クラスのサブクラスが実際のインスタンスを生成する
userはfactory抽象クラスを通じてインスタンスを受け取る
Design Pattarm MENU
このページは下記の構成となってます
コードを確認するだけなら2番目に飛んでください
・new でインスタンスを生成できないケース
・Factory Method Pattern の確認new でインスタンスを生成できないケース
以下のクラス構成で確認します
パッケージ名 クラス アクセス修飾子 説明 Default user(Main.class) public 動作を確認する implement sam.class Default 特になし package.implement.sampackage implement; class sam{}user(Main.class)import implement.*; public static void main(String[] args){ sam s = new sam(); }結果
sam cannot be resolved to a typesam.classのアクセス修飾子はDefaultですから
所属パッケージに所属するクラスからしかアクセスできません従ってuserから、sam.classは完全に独立し、触れることができません
例えば、このようなケースでFactory Method Patternを使いますFactory Method Pattern の確認
以下の構成で確認します
パッケージ名 アクセス修飾子 クラス import 説明 framework public abstract samFramework.class なし userが利用するオブジェクトの抽象クラス framework public abstract factoryFramwork.class なし インスタンス生成を定義する抽象クラス implement Default sam.class import framework.samFramework userはsam()のインスタンスを利用する implement public factory.class import framework.samFramework
import framework.factoryFrameworksam.classのインスタンスを生成する Default public user(Main.class) import framework.samFramework
import framework.factoryFramework
import implement.factory
samFramework.classとfactoryFramework.classは
何もimportしていません
また user(Main.class) は sam( )をimportできませんので
new sam( )でsamインスタンスを取得できませんではクラスを作っていきます
samFramework.classpackage framework; public abstract class samFramework{ public abstract void show(); }factoryFramework.classpackage framework; public abstract class factoryFramework{ public abstract samFramework newInstance(); }sam.classpackage implement; import framework.samFramework; class sam extends samFramework { public void show(){System.out.println("sam");} }factory.classpackage implement; import framework.factoryFramework; import framework.samFramework; public class factory extends factoryFramework { public samFramework newInstance(){return new sam();} }user(Main.class)import framework.factoryFramework; import framework.samFramework; import implement.factory; class Main { public static void main(String[] args){ factoryFramework factory = new factory(); samFramework sam = factory.newInstance(); sam.show(); }}
- 投稿日:2020-11-08T14:38:12+09:00
CheckStyleの対応
Javaの開発環境メモ
で開発環境を構築しているが、CheckStyleでアラートが大量に出ているので、その対応方法を調べて記載していく。1.CheckStyle
1-1.pomの設定
pom.xml<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <version>3.1.1</version> <dependencies> <dependency> <groupId>com.puppycrawl.tools</groupId> <artifactId>checkstyle</artifactId> <version>8.37</version> </dependency> </dependencies> <configuration> <configLocation>google_checks.xml</configLocation> <failsOnError>true</failsOnError> <failOnViolation>true</failOnViolation> <violationSeverity>error</violationSeverity> <consoleOutput>true</consoleOutput> </configuration> <executions> <execution> <id>checkstyle</id> <phase>verify</phase> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin>1-2.設定ファイルのダウンロード
以下からダウンロードする?
https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
pom.xmlファイルは以下のサイトを選考に設定した。
https://maven.apache.org/plugins/maven-checkstyle-plugin/examples/custom-checker-config.htmlエラーになる場合は?
https://github.com/checkstyle/checkstyle/blob/master/src/main/resources/google_checks.xml
ルールのファイルとライブラリのバージョンに差があるのかもしれない。
2-1.インポート文が辞書式順序になっていません。
2-1-1.原因
自作のクラスのパッケージがorgよりもアルファベットの先頭にある場合(jpなど)に、警告になる。
1-2.Eclipseによる解決方法
- [ウィンドウ]-[設定]
- [Java]-[コード・スタイル]-[インポートの編成]
- 「新規」ボタンで自作のパッケージ(jp)を作成し、orgの上に移動する。
2.Package name 'パッケージ名' must match pattern '^[a-z]+(.[a-z][a-z0-9])$'.
原因 パッケージ名に「_」などの記号が含まれている。
2-1.CheckStyleによる解決方法
修正前<module name="PackageName"> <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> <message key="name.invalidPattern" value="Package name ''{0}'' must match pattern ''{1}''."/> </module>上記のルールを警告ではなくINFOに変更する。
指定できるのは ignore, info, warning, error のいずれか。修正後<module name="PackageName"> <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]*)*$"/> <message key="name.invalidPattern" value="Package name ''{0}'' must match pattern ''{1}''."/> <property name="severity" value="info" /> </module>参考URL
マルチモジュールでCheckStyleとかのルールを共有する
https://ssogabe.hatenadiary.org/entry/20091219/1261196056原因
Eclipseによる解決方法
CheckStyleによる解決方法
- 投稿日:2020-11-08T14:17:49+09:00
【Android/Java】Fragmentに戻るボタンを設置する
はじめに
フラグメントでの戻るボタンの設置において、
親Activity内で定義して呼び出す書き方にしていたのですが、
Activityに置かずに各Fragmentから直接中身を呼び出すほうがいい
と、ベテランエンジニアから指摘をいただいたので備忘録として両パターン残しておきます。①MainActivityから呼び出す方法
MainActivity.javapublic class MainActivity extends AppCompatActivity { // 戻るボタンを定義 public void setupBackButton(boolean enableBackButton) { ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(enableBackButton); } }Fragment1.java// onCreateView内で呼び出す MainActivity activity = (MainActivity) getActivity(); activity.setupBackButton(true); setHasOptionsMenu(true); // FragmentでActionBarを使うために必要な設定Fragment2.java// onCreateView内で呼び出す MainActivity activity = (MainActivity) getActivity(); activity.setupBackButton(false);②Fragmentから直接呼び出す方法
Fragment1.java// onCreateView内で呼び出す .setupBackButton() setHasOptionsMenu(true); // FragmentでActionBarを使うために必要な設定 // Fragmentクラス内で定義 public void setupBackButton() { AppCompatActivity activity = (AppCompatActivity) getActivity(); ActionBar actionBar = activity.getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); }※下記はFragment1から遷移したときに戻るボタンが表示されてしまうのを防ぐために記載してます。
必要可否は各自判断してください。Fragment2.java// onCreateView内で呼び出す .unSetupBackButton() // Fragmentクラス内で定義 public void unSetupBackButton() { AppCompatActivity activity = (AppCompatActivity) getActivity(); ActionBar actionBar = activity.getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(false); }違い
ぱっと見た感じ同じですが、Activityの取得先が違います!
①MainActivity activity = (MainActivity) getActivity();
②AppCompatActivity activity = (AppCompatActivity) getActivity();特定のActivity(今回だとMainActivity)が前提の書き方はやめたほうがいいとのことです。
前提となるクラス(自分で定義したクラス)が増えるほどコードは安全な変更がしづらくなるからです。取得先がAppCompatActivityであれば継承元なので変更されることはほぼないし安全性が高いということでしょうかね。
先を見据えた実装ができる強強エンジニアになりたいです。
- 投稿日:2020-11-08T13:54:54+09:00
Javaのインターフェースについて
Javaのインターフェースの特徴
インターフェースの特徴として以下の2つが挙げられる
- インターフェースはインスタンスフィールド(インスタンス変数)を持つことが出来ない
- 具象メソッド(抽象メソッドではないメソッド)を実装することができない
interface はインスタンスフィールドを持たない
インターフェースでは、フィールドを宣言しようとすると
public static final
のように定数を宣言したものとして扱われる。具象メソッドを実装することが出来ない
抽象メソッドしか定義することが出来ず、interfaceは実装された場合インスタンスが生成された場合には抽象メソッドは必ずオーバーライドされる必要がある。
おまけ
各クラスの共通化のために、インターフェースの代わりにクラスを作成するときに注意が必要。各クラスが何かしらのサブクラスである場合、共通化してクラスにしたものを継承することは出来ない。なぜなら、Javaでは、スーパークラスは1つしか持てない。
インタフェースであれば、複数実装することは可能。
- 投稿日:2020-11-08T10:50:10+09:00
Template Pattern
Template Pattern
templateで処理の大枠を決めてロジックを共有する
サブクラスではtemplateで決た枠に従い処理を行うTemplate Pattern 例
以下のクラス構成で確認します
クラス 説明 template.class 処理の大枠を記述する sam1.class 処理の詳細を肉づける sam2.class 処理の詳細を肉づける user(Main.class) Template Patternの動作を確認する template.classabstract class template{ abstract String temp1(); abstract String temp2(); abstract String temp3(); final void show(){ System.out.print(temp1()+temp2()+temp3()+"\n"); } }sam1.classclass sam1 extends template{ String temp1(){return "<<< ";} String temp2(){return "Template";} String temp3(){return " >>>";} }sam2.classclass sam2 extends template{ String temp1(){return "[[[ ";} String temp2(){return "Template";} String temp3(){return " ]]]";} }user(Main.class)public static void main(String[] args){ sam1 s1= new sam1(); sam2 s2= new sam2(); s1.show(); s2.show(); }}
- 投稿日:2020-11-08T10:50:10+09:00
Template Method Pattern
Template Method Pattern
templateで処理の大枠を決めてロジックを共有する
サブクラスではtemplateで決た枠に従い処理を行うTemplate Method Pattern 例
以下のクラス構成で確認します
クラス 説明 template.class 処理の大枠を記述する sam1.class 処理の詳細を肉づける sam2.class 処理の詳細を肉づける user(Main.class) Template Patternの動作を確認する template.classabstract class template{ abstract String temp1(); abstract String temp2(); abstract String temp3(); final void show(){ System.out.print(temp1()+temp2()+temp3()+"\n"); } }sam1.classclass sam1 extends template{ String temp1(){return "<<< ";} String temp2(){return "Template";} String temp3(){return " >>>";} }sam2.classclass sam2 extends template{ String temp1(){return "[[[ ";} String temp2(){return "Template";} String temp3(){return " ]]]";} }user(Main.class)public static void main(String[] args){ sam1 s1= new sam1(); sam2 s2= new sam2(); s1.show(); s2.show(); }}
- 投稿日:2020-11-08T09:15:51+09:00
Adapter Pattern
Adapter Pattern
あるクラスのメソッドを再利用する
再利用されるクラスを修正したりはしないAdapter Patternは以下の2つの方法があります
・再利用されるクラスをextendsしたクラスを利用する
・再利用されるクラスをインスタンス化して利用する再利用されるクラスをextends
以下のクラス構成で確認します
クラス 説明 sam.class 再利用されるクラス adapt.class sam.classのsam()を、adapt.classのexec()でラップして再利用する user(Main.class) Adapter Patternを使う sam.classclass sam { void sam(){System.out.print("base");} }adapt.classclass adapt extends sam{ void exec(){ System.out.print("<<< "); // A sam(); // sam.classのsam()メソッド System.out.println(" >>>");} // A } // Aでsam()をラップしてますuser(Main.class)public static void main(String[] args){ adapt ad= new adapt(); ad.exec(); }}再利用されるクラスをインスタンス化
構成は上記と同じで、adapt.classを変更します
adapt.classclass adapt extends sam{ sam s=new sam(); void exec(){ System.out.print("<<< "); s.sam(); System.out.println(" >>>");} }
- 投稿日:2020-11-08T09:09:57+09:00
Java Desige Pattern
デザインパターンまとめ
オブジェクト指向とはシステムの開発、保守、拡張時に、追跡困難なバグの発生を抑えるために、様々な工夫を施す開発指向です。
java デザインパターンは、クラスの再利用性を高めることで拡張しやすく、保守しやすいシステムづくりを指向します。
下記は未完成なMenuです
*はリンク切れしてますMenu
Iterator
Adapter
Template Method
Factory Method
Singleton
Prototype
*Builder
*Abstract Factory
*Bridge
*Strategy
*Composite
*Decorator
*Visitor
*Chain of Responsibility
*Facade
*Mediator
*Observer
*Memento
*State
Flyweight
Proxy
Command
*Interpreterオブジェクト指向のヒント
オブジェクト間の依存性を薄める
・抽象クラスやインターフェイスプログラミングを意識する
・正しく動くクラスは修正せず拡張して使う
・ The Liskov Substitution Principle(LSP)を意識する
Superクラス型の変数を利用して、そのサブクラスを一括して扱う
instanceofを使いたくなったら意識する(instanceofではなくLspでできないか?)