20200702のJavaに関する記事は12件です。

「実装」javaのProcessクラス注意事項

バージョン

java 8

API

先ずJava API DocsからProcessクラスを一緒に見ましょう

注意事項

  • API Docs中で下記部分はご注意ください、BUGを発生する可能性があります。

ネイティブなプラットフォームには標準入出力ストリームに使うバッファのサイズが限られるものもあるので、サブプロセスの入力ストリームの書き込みあるいはストリーム出力の読込みが失敗した場合、サブプロセスはブロックされるか、デッドロック状態になる可能性があります。

bad.java
Process p = null;
p = Runtime.getRuntime().exec("cmd /c dir");
p.waitFor(); // 標準出力ストリームに出力量が多い場合は、プロセスはブロックされる
  • API Docs中で下記部分を考えると、Processオブジェクトを使う後で、destroy()メソッドを呼出したほうがいいです。

Processオブジェクトへの参照がなくなった場合でも、サブプロセスは終了されず、非同期的に実行を続けます。

bad.java
Process p = null;
p = Runtime.getRuntime().exec("cmd /c dir");
// 終了しません

サンプルコード

Test.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.SequenceInputStream;

public class Test {

    public static void main(String[] args) throws IOException, InterruptedException {
        Process p = null;
        p = Runtime.getRuntime().exec("cmd /c dir");

        // SequenceInputStreamは、ほかの入力ストリームを論理的に連結したものを表します。
        SequenceInputStream sis = new SequenceInputStream(p.getInputStream(), p.getErrorStream());
        BufferedReader br = new BufferedReader(new InputStreamReader(sis));

        // サブプロセスの出力ストリームを文字列に作成
        String line = null;
        StringBuilder sb = new StringBuilder();
        while ((line = br.readLine()) != null) {
            sb.append(line + "\n");
        }
        br.close();

        p.waitFor();
        p.destroy();

        // データを出力
        System.out.print(sb);
    }

}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Android]自動スクロールで手を動かすことなく実機確認を行う

一覧など、無限スクロールが存在する機能を使用するときに、実機にかかる負担をAndroidStudioのMemory Profilerで監視をし重箱の隅をつつく品質をチェックしているのだが、手で動かすのがだるい。

なので、無限スクロール自動化をしてみた。

SampleCode

簡単にRecyclerViewのSampleコードを実装したので、こちらを使用する。
https://github.com/yamachita0109/RecyclerView

実際に自動scrollしてみた

下記コマンドをコマンドラインに貼り付ければOK。
もっと細かい値を指定したい場合は、公式ドキュメントを参考にすること。

$ while [ true ]; do
adb shell input swipe 521 1540 492 432
done;

デモ

demo.gif

まとめ

・RecyclerViewをスクラッチで実装したのは初めて、もっと美しく実装して、実サービスに耐えうる形にしたいなぁ。

・adbコマンドでなんでもことが発覚。
実機操作を記憶して、スクリプト化することもできるそうな。
ちょっと試してみようかな。

・他にもっと便利なadbコマンドの使い方がある方は是非教えてください!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

6行でJavaをセグフォらせる

初投稿です。お見苦しいところもあると思いますが、ご了承ください。

pythonから各言語をセグフォらせるのが流行ってきたので...

pythonを三行でセグフォらせる
pythonを2行でセグフォらせる
C言語で16文字でセグフォらせる
Pythonを33文字でセグフォらせる
Rustを5行でセグフォらせる
C 言語で 5 文字でセグフォらせる

実際のコード

無理に6行にした結果

a.java
import java.lang.reflect.*;
import sun.misc.Unsafe;
class A {public static void main(String[] a) throws Exception {Constructor<Unsafe> b=Unsafe.class.getDeclaredConstructor();
    b.setAccessible(true);
    b.newInstance().putLong(0, 0);} }

無理やり過ぎで可読性が皆無です
コードを整理すると

a.java
import java.lang.reflect.*;
import sun.misc.Unsafe;
class A {
    public static void main(String[] a) throws Exception{
        Constructor<Unsafe> b=Unsafe.class.getDeclaredConstructor();
        b.setAccessible(true);
        b.newInstance().putLong(0, 0);
    }
}

あまり変わりませんね...

実行結果

Linux(Ubuntu)の場合

Ubuntu
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  SIGSEGV (0xb) at pc=0x00007f8b7c08ba84, pid=1986, tid=1987
#
# JRE version: OpenJDK Runtime Environment (14.0.1+7) (build 14.0.1+7-Ubuntu-1ubuntu1)
# Java VM: OpenJDK 64-Bit Server VM (14.0.1+7-Ubuntu-1ubuntu1, mixed mode, sharing, tiered, compressed oops, g1 gc, linux-amd64)
# Problematic frame:
# V  [libjvm.so+0xe99a84]
#
# No core dump will be written. Core dumps have been disabled. To enable core dumping, try "ulimit -c unlimited" before starting Java again
#
# An error report file with more information is saved as:
# /home/user/hs_err_pid1986.log
#
# If you would like to submit a bug report, please visit:
#   Unknown
#
Aborted

(Ubuntu+OpenJDK Runtime Environment 14.0.1)

WSL2で実行したため実際の結果とは異なる場合があるかもしれません。

Windowsの場合

Windows
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x00007ffd4fa119b7, pid=18520, tid=8224
#
# JRE version: Java(TM) SE Runtime Environment (14.0.1+7) (build 14.0.1+7)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (14.0.1+7, mixed mode, sharing, tiered, compressed oops, g1 gc, windows-amd64)
# Problematic frame:
# V  [jvm.dll+0x7219b7]
#
# No core dump will be written. Minidumps are not enabled by default on client versions of Windows
#
# An error report file with more information is saved as:
# C:\Users\User\aa\hs_err_pid18520.log
#
# If you would like to submit a bug report, please visit:
#   https://bugreport.java.com/bugreport/crash.jsp
#

(Windows + Java(TM) SE Runtime Environment 14.0.1)

禍々しいエラー文と共にclassファイルと同じディレクトリにログファイルが生成されます。

Windowsのlogファイルの40行目:

has_err_pid{prosess_id}.log
40:#siginfo: EXCEPTION_ACCESS_VIOLATION (0xc0000005), writing address 0x0000000000000000

Ubuntuのlogファイルの44行目:

has_err_pid{posess_id}.log
49 siginfo: si_signo: 11 (SIGSEGV), si_code: 1 (SEGV_MAPERR), si_addr: 0x0000000000000000

コードにも書いたようにメモリの0番地に書き込もうとしてアクセス違反を起こしていますね。

なんだよ!Segmentation Fault書いてないやんけ!詐欺やん!って思ってる人もいると思いますが、

"wikipedia -セグメンテーション違反-"より

UNIXライクのオペレーティングシステム上では、不正なメモリにアクセスをするプロセスはSIGSEGVシグナルを受け取る。Microsoft Windows上では、不正なメモリにアクセスするプロセスはSTATUS_ACCESS_VIOLATION例外を受け取る

この競技(?)のレギュレーションが決まってないのですがこれはセグフォとして扱っていいですよね?

超大雑把な解説

sun.misc.UnsafeでJavaでもメモリをアクセスする能力を手に入れれます。

じゃあUnsafe.getUnsafe().putLong(0, 0)でもっと短くできんじゃないの?って思うかもしれませんが、

javaのunsafeは名前の通りものすごく安全ではないクラスです。
finalの値を変更したりメモリを確保したり、アクセスしたりやりたい放題できます(ある程度制限があるらしいが)
そのため、コンストラクタがprivateでさらにgetunsafe()getclassloder()がnullの場合しかインスタンスを生成できません。

その抜け道として、リフレクションAPIで無理やりインスタンス化、さらにsetAccessible(true)を使って普通はアクセスできないメソッドにアクセスできるようにします。

最後にputLong(address, x);addressにメモリ番地、xに適当な値を入れることでjavaでもセグフォを引き寄せることができました!!やったね!

参考にしたサイト等

sun.misc.Unsafe の魔力
何かの時にスッと使える力技 - Reflection 編
セグメンテーション違反-wikipedia

編集リクエストなど気軽に送りつけてください。
拙い文章ですが最後まで閲覧いただきありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

稼ぎたいなら食わず嫌いしない。上に立つ人こそ色々と試そう。

今時のリーダーは、率先して色々と試し、失敗し、部下を成功の最短ルートで動かすものだ。
これはIT業界に限ったことではない。

フリーランスで年収を上げるのも、専門技術の高さより、幅広い技術の方が年収を上げやすい。
また、副業で稼ぐにも、幅広い技術がいい。

私は旅行代理店のシステム開発で副業していたことがあるが、その時は、PHPとRubyだった。
フリーランスのメインの仕事では、Javaだった。

副業、クラウドソーシングでは、PHPばっかである。
ただ、フリーランスの仕事では、PHPはかなり減った。

PHPの将来性を検索すれば、残るという意見も無くなるという意見もあるが、
それは、副業で考えるか、フリーランスの仕事で考えるか、で変わってくる。

結局、自分で試して、自分で考えるしかない。
この試すという手間を検索して、人の意見を聞いて終わりにしないということである。

Ruby が出たばかりのころ、Perl と Python が消えると騒がれた。
私は全部試して、業務でも使用した。
Ruby の急成長は予想できなかったが、Perl が消える予想は当たった。
当時、Perl の将来性を検索したら、使用している企業が多いから、消えることはないという意見が多かった。

私が消えると言っているのは、稼げないという意味である。

Rust は良い言語だが、PHP はダメだ。 みたいなのは、思い込みである。
PHPの方が楽だし、稼げる。
言いたいことは、状況によるから決めつけるなということ。
そして、自分で試して、情報に流されないということ。

今時、開発言語の選定はかなり難しい。
検索して判断せずに、是非、試してから判断して欲しい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Spring Boot で Azure Cosmos DBにアクセスする

Spring Bootを利用して、Azure CosmosDB(MongoDBコア)にアクセスしたので、その記録です。

コード書くのが怠かったので、https://spring.pleiades.io/guides/gs/accessing-data-mongodb/ を参考にしました。

まずは、普通にサンプルコードをgit cloneします。

$ git clone https://github.com/spring-guides/gs-accessing-data-mongodb.git

チュートリアル通りにローカルにMongoDBを立てて、

$ sudo apt install mongodb
$ mkdir -p data/db
$ mongod --dbpath=./data/db

Gradleで実行します。

$ gradle bootRun

Welcome to Gradle 6.5.1!

Here are the highlights of this release:
 - Experimental file-system watching
 - Improved version ordering
 - New samples

For more details see https://docs.gradle.org/6.5.1/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

2020-07-02 03:12:00.912  INFO 32643 --- [           main] c.e.a.AccessingDataMongodbApplication    : Starting AccessingDataMongodbApplication on DESKTOP-N9T4CN3 with PID 32643 (XXX/gs-accessing-data-mongodb/complete/build/classes/java/main started by XXX in XXX/gs-accessing-data-mongodb/complete)
2020-07-02 03:12:00.917  INFO 32643 --- [           main] c.e.a.AccessingDataMongodbApplication    : No active profile set, falling back to default profiles: default
2020-07-02 03:12:01.274  INFO 32643 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-07-02 03:12:01.315  INFO 32643 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 36ms. Found 1 repository interfaces.
2020-07-02 03:12:01.589  INFO 32643 --- [           main] org.mongodb.driver.cluster               : Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
2020-07-02 03:12:01.674  INFO 32643 --- [localhost:27017] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:1, serverValue:1}] to localhost:27017
2020-07-02 03:12:01.678  INFO 32643 --- [localhost:27017] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 8]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=2881100}
2020-07-02 03:12:01.745  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:01.745  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:01.771  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:01.771  WARN 32643 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 03:12:02.134  INFO 32643 --- [           main] c.e.a.AccessingDataMongodbApplication    : Started AccessingDataMongodbApplication in 1.552 seconds (JVM running for 1.853)
2020-07-02 03:12:02.157  INFO 32643 --- [           main] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:2, serverValue:2}] to localhost:27017
Customers found with findAll():
-------------------------------
Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith']
Customer[id=5efcd1f2b15c223a7dc2a9cf, firstName='Bob', lastName='Smith']

Customer found with findByFirstName('Alice'):
--------------------------------
Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith']
Customers found with findByLastName('Smith'):
--------------------------------
Customer[id=5efcd1f2b15c223a7dc2a9ce, firstName='Alice', lastName='Smith']
Customer[id=5efcd1f2b15c223a7dc2a9cf, firstName='Bob', lastName='Smith']
2020-07-02 03:12:02.270  INFO 32643 --- [extShutdownHook] org.mongodb.driver.connection            : Closed connection [connectionId{localValue:2, serverValue:2}] to localhost:27017 because the pool has been closed.

BUILD SUCCESSFUL in 53s
2 actionable tasks: 2 executed

mongoコマンドで中を覗いてみます。

$ mongo
MongoDB shell version v3.6.8
connecting to: mongodb://127.0.0.1:27017
Implicit session: session { "id" : UUID("2648d101-f236-4f56-bc3d-7250d32ef9a2") }
MongoDB server version: 3.6.8
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings: 
2020-07-02T02:54:56.366+0900 I STORAGE  [initandlisten] 
2020-07-02T02:54:56.366+0900 I STORAGE  [initandlisten] ** WARNING: Using the XFS filesystem is strongly recommended with the WiredTiger storage engine
2020-07-02T02:54:56.366+0900 I STORAGE  [initandlisten] **          See http://dochub.mongodb.org/core/prodnotes-filesystem
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] ** WARNING: Access control is not enabled for the database.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          Read and write access to data and configuration is unrestricted.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] ** WARNING: This server is bound to localhost.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          Remote systems will be unable to connect to this server. 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          Start the server with --bind_ip <address> to specify which IP 
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          addresses it should serve responses from, or with --bind_ip_all to
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          bind to all interfaces. If this behavior is desired, start the
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] **          server with --bind_ip 127.0.0.1 to disable this warning.
2020-07-02T02:54:57.070+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] 
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2020-07-02T02:54:57.071+0900 I CONTROL  [initandlisten] 
> db.customer.find()
{ "_id" : ObjectId("5efcd1f2b15c223a7dc2a9ce"), "firstName" : "Alice", "lastName" : "Smith", "_class" : "com.example.accessingdatamongodb.Customer" }
{ "_id" : ObjectId("5efcd1f2b15c223a7dc2a9cf"), "firstName" : "Bob", "lastName" : "Smith", "_class" : "com.example.accessingdatamongodb.Customer" }
> 

入ってますね。

この時点で、チュートリアルでは接続先のMongoDBへのアクセスは行っていません。実装されているデータアクセスに関するものも、以下の2つのみです。

Customer.java
package com.example.accessingdatamongodb;

import org.springframework.data.annotation.Id;

public class Customer {
    @Id
    public String id;
    public String firstName;
    public String lastName;
    public Customer() {}
    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    @Override
    public String toString() {
        return String.format(
                "Customer[id=%s, firstName='%s', lastName='%s']",
                id, firstName, lastName);
    }
}
CustomerRepository.java
package com.example.accessingdatamongodb;

import java.util.List;
import org.springframework.data.mongodb.repository.MongoRepository;

public interface CustomerRepository extends MongoRepository<Customer, String> {
    public Customer findByFirstName(String firstName);
    public List<Customer> findByLastName(String lastName);
}

高レベルAPIであるRepositoryを使用しているので、実装は非常に簡単です。実際にはSpring Data MongoDBによってCustomerRepojitoryインターフェースを実装するクラスが自動生成されて、saveやfindなどのメソッドも利用できるようになっています。便利ですね。(低レベルAPIであるTemplateMongoを使用すると、もっと複雑なこともできます。)

今回は、MongoDBコアで作成したCosmosDBにアクセスするのが主目的なので、高レベルAPIを利用したこのサンプルを利用します。

Azure側に、CosmosDBを作成します。こちらは https://docs.microsoft.com/ja-jp/azure/cosmos-db/create-mongodb-java#create-a-database-account を参考にしてサクサクとPortalから作成します。コレクションの作成は不要です。

作成されたら、Portalから接続先のURIを確認します。今回はJavaですので、"クイックスタート"の"Java"タブに表示されているプライマリ接続文字列をコピーします。
image.png

サンプルコードに、main/resources/application.propertisを追加し、"spring.data.mongodb.uri"を追加して、先ほどコピーした値を貼り付け、Mongo DB Driverが3.6以降の場合は末尾に"&retrywrites=false"を追加します。Cosmos DBが現時点でRetryable Writesに対応していないためです。

application.properties
spring.data.mongodb.uri=<your_connection_string>&retrywrites=false

定義したURLを使用するため、Configurationを作成します。

CustomerRepogitoryConfig.java
package com.example.accessingdatamongodb;

import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class CustomerRepositoryConfig {
    @Value("${spring.data.mongodb.uri:mongodb://localhost:27017}")
    public String connectionString;
    public @Bean MongoClient mongoClient() {
        return MongoClients.create(this.connectionString);
    }
}

Gradleで実行します。

$ gradle bootRun -Pargs=--debug

> Task :bootRun

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.0.RELEASE)

2020-07-02 04:54:56.451  INFO 3822 --- [           main] c.e.a.AccessingDataMongodbApplication    : Starting AccessingDataMongodbApplication on DESKTOP-N9T4CN3 with PID 3822 (XXX/gs-accessing-data-mongodb/complete/build/classes/java/main started by XXX in XXX/gs-accessing-data-mongodb/complete)
2020-07-02 04:54:56.456  INFO 3822 --- [           main] c.e.a.AccessingDataMongodbApplication    : No active profile set, falling back to default profiles: default
2020-07-02 04:54:57.370  INFO 3822 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-07-02 04:54:57.497  INFO 3822 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 118ms. Found 1 repository interfaces.
2020-07-02 04:54:58.011  INFO 3822 --- [           main] org.mongodb.driver.cluster               : Cluster created with settings {hosts=[XXX:YYY], mode=MULTIPLE, requiredClusterType=REPLICA_SET, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500, requiredReplicaSetName='globaldb'}
2020-07-02 04:54:58.011  INFO 3822 --- [           main] org.mongodb.driver.cluster               : Adding discovered server XXX:YYY to client view of cluster
2020-07-02 04:54:58.244  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.245  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.316  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.LocalDateTime to class java.time.Instant as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.317  WARN 3822 --- [           main] o.s.data.convert.CustomConversions       : Registering converter from class java.time.Instant to class java.time.LocalDateTime as reading converter although it doesn't convert from a store-supported type! You might wanna check you annotation setup at the converter implementation.
2020-07-02 04:54:58.912  INFO 3822 --- [           main] c.e.a.AccessingDataMongodbApplication    : Started AccessingDataMongodbApplication in 3.129 seconds (JVM running for 3.84)
2020-07-02 04:54:58.947  INFO 3822 --- [           main] org.mongodb.driver.cluster               : No server chosen by com.mongodb.client.internal.MongoClientDelegate$1@210386e0 from cluster description ClusterDescription{type=REPLICA_SET, connectionMode=MULTIPLE, serverDescriptions=[ServerDescription{address=XXX:YYY, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
2020-07-02 04:54:59.082  INFO 3822 --- [azure.com:10255] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:1, serverValue:1162392059}] to XXX:YYY
2020-07-02 04:54:59.097  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=XXX:YYY, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 0]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=12402600, setName='globaldb', canonicalAddress=XXX:YYY, hosts=[XXX:YYY], passives=[], arbiters=[], primary='XXX:YYY', tagSet=TagSet{[Tag{name='region', value='Japan East'}]}, electionId=null, setVersion=1, lastWriteDate=null, lastUpdateTimeNanos=134393982328436}
2020-07-02 04:54:59.100  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Adding discovered server XXX:YYY to client view of cluster
2020-07-02 04:54:59.103  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Server XXX:YYY is no longer a member of the replica set.  Removing from client view of cluster.
2020-07-02 04:54:59.105  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Canonical address XXX:YYY does not match server address.  Removing XXX:YYY from client view of cluster
2020-07-02 04:54:59.338  INFO 3822 --- [azure.com:10255] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:2, serverValue:689958399}] to XXX:YYY
2020-07-02 04:54:59.348  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Monitor thread successfully connected to server with description ServerDescription{address=XXX:YYY, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 6, 0]}, minWireVersion=0, maxWireVersion=6, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=8755400, setName='globaldb', canonicalAddress=XXX:YYY, hosts=[XXX:YYY], passives=[], arbiters=[], primary='XXX:YYY', tagSet=TagSet{[Tag{name='region', value='Japan East'}]}, electionId=null, setVersion=1, lastWriteDate=null, lastUpdateTimeNanos=134394233832236}
2020-07-02 04:54:59.349  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Setting max set version to 1 from replica set primary XXX:YYY
2020-07-02 04:54:59.349  INFO 3822 --- [azure.com:10255] org.mongodb.driver.cluster               : Discovered replica set primary XXX:YYY
2020-07-02 04:54:59.603  INFO 3822 --- [           main] org.mongodb.driver.connection            : Opened connection [connectionId{localValue:3, serverValue:1139365834}] to XXX:YYY
Customers found with findAll():
-------------------------------
Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith']
Customer[id=5efcea15cac85c79009dba56, firstName='Bob', lastName='Smith']

Customer found with findByFirstName('Alice'):
--------------------------------
Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith']
Customers found with findByLastName('Smith'):
--------------------------------
Customer[id=5efcea13cac85c79009dba55, firstName='Alice', lastName='Smith']
Customer[id=5efcea15cac85c79009dba56, firstName='Bob', lastName='Smith']
2020-07-02 04:55:01.231  INFO 3822 --- [extShutdownHook] org.mongodb.driver.connection            : Closed connection [connectionId{localValue:3, serverValue:1139365834}] to XXX:YYY because the pool has been closed.

BUILD SUCCESSFUL in 8s
3 actionable tasks: 2 executed, 1 up-to-date

成功しました。Azureポータルのデータエクスプローラーでcustomerコレクションが追加されていることを確認しましょう。
image.png

データが作成されていることが確認できますね。

Cosmos DBは最近Free Tierが提供されたので、手軽に機能を試すことができます。
是非、触ってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Apache JMeter で大きいファイルをダウンロードする

やりたいこと

JMeter で大容量のファイルをダウンロードするような負荷テストをしたい。
(Azure CDN を使ったファイルダウンロードの負荷テストを行いたいという要望があり調査した内容です)

準備すべきこと

以下の条件を整えることが重要

  • 64bit 版の Java をインストールする
  • JVM の実行時オプションでヒープサイズを拡張する
  • ヒープサイズを十分に拡張可能な実メモリを搭載する
  • 64bit 版の JVM を使う
  • CUI モードで実行する

それぞれやること

64bit 版の Java をインストールする

Java の公式サイトから、64bit 版の Java のインストーラーをダウンロードしてインストール。
手順は下記のサイトのドキュメントを参考にして、マニュアルインストールです。

https://java.com/ja/download/faq/java_win64bit.xml

JVM の実行時オプションでヒープサイズを拡張する & 64bit 版の JVM を使う

JMeter インストール先のディレクトリにある jmeter.bat をテキストエディタで開いて、以下の部分を適宜変更。
下記の設定では、ヒープサイズを 16GB に拡張したうえ、64bit JVM を利用する設定。

before
set HEAP=-Xms1g -Xmx1g -XX:MaxMetaspaceSize=256m
after
set HEAP=-Xms1g -Xmx16g -XX:MaxMetaspaceSize=256m -d64

ヒープサイズを十分に拡張可能な実メモリを搭載する

ここは、物理マシンなら財力任せですし、仮想マシンならヒープサイズよりも大きいメモリを搭載したものを選ぶのが良いでしょう。

例えば、Azure の VM だと E シリーズなどが搭載メモリ量多めで使いやすいサイズになるかな…と思います。
https://docs.microsoft.com/ja-jp/azure/virtual-machines/ev3-esv3-series

CUI モードで実行する

JMeter の起動時に、GUI で起動すると下記のメッセージが出るはずです。

Don't use GUI mode for load testing !, only for Test creation and Test debugging.
For load testing, use CLI Mode (was NON GUI):
   jmeter -n -t [jmx file] -l [results file] -e -o [Path to web report folder]

GUI で jmx ファイルを生成した後、改めてコンソールから JMeter を起動しましょう。

まとめ

上記の条件を整えれば、でかいファイルをダウンロードする類の負荷テストも JMeter で実行しやすくなると思います。(とはいえ、ヒープに確保できるメモリの量次第なので、ダメだったらごめんなさい。)

条件が整っていないときに出るエラーたち(検索用)

JVM のヒープサイズが足りない

java.lang.OutOfMemoryError: Java heap space

64bit 版の Java がセットアップされていない

Error: This Java instance does not support a 64-bit JVM.
Please install the desired version.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Spring Boot 2.3.1×Thymeleafでエコーアプリケーション作ったった

はじめに

こんにちは!
macでSTSの環境構築を行って、Spring BootでHelloWorldしていた者です。
今回は簡単なエコーアプリケーションを作成してみました。

もょもと.gif

画面遷移図っぽくすると、以下のようになります。

スクリーンショット 2020-06-30 20.30.29.png

↓「送信」押下   ↑「お返事bot TOP」押下

スクリーンショット 2020-06-30 20.30.38.png

前回、前々回同様、Spring徹底入門を参考に進めました。
本にはSpring MVCでのエコーアプリケーションの作り方と、SpringBootでのHelloWorldのコードは載っているのですが、Spring Bootでのエコーアプリケーションの方法はなかったので、なんとか混ぜ合わせながら作りました。

本当はバリデートとかテストコードとかもチャカチャカ実装するつもりだったのですが…Viewで使っているThymeleafに馴染みがなく(今までJSPを使っていましたが、非推奨らしい…!)、概念と書き方に慣れるのにちょっと時間がかかりました!ので、バリデートとか一切ない、ただのピュアなエコーアプリケーションです!

Thymeleaf?テンプレートエンジン?何それ?な人が読むとちょっと得するかも!な内容となっています。

使用環境とバージョン

  • macOS Catalina
  • jdk14.0.1
  • JUnit5
  • Maven 3.6.3_1
  • STS 4.6.1
  • Spring Boot 2.3.1
  • spring-boot-starter-thymeleaf-2.3.1

Spring Initializrでプロジェクトの雛形を作る

前回同様Spring Initializrでプロジェクトの雛形を作成します。
使い方自体は前回と変わらないので、相違点だけ紹介します。

スクリーンショット 2020-06-28 20.17.05.png

(左上から)

  • Spring Bootのバージョン
    前回は2.3.0があったのですが、今回なくなっていたので2.3.1を使用しました1

  • Project Metadata
    前回はデフォルトのdemoのままで行いましたが、同名ファイルだと混ざるのでformに変更しました2

  • Dependencies【重要!】
    前回は何も指定しませんでしたが、今回はThymeleafを使用するため、ADD DEPENDENCIESからThymeleafを選択しました。(もちろん後からpom.xmlを書き換えればなんとかなりますが…)

ファイル構成

前回はメインメソッドとコントローラーを同じクラスDemoApplication.javaにしていましたが、今回はメインメソッドはEchoApplication.javaに、コントローラーはEchoController.javaに分けて作成しました。ただ、2つのHTMLそれぞれにコントローラーを作るほどでもなかったので、コントローラー1つで2つのページを担当しています。
そのほか、EchoFormクラスは画面に入力された値(今回はお返事botなので入力する値は名前)を受け取るクラスです。index.htmlがお返事bot TOPページ、echo.htmlは名前「もょもと」が送信されたあとで「Hello もょもと」とお返事していたページです3

スクリーンショット 2020-06-28 21.43.45.png

①コントローラーの作成

EchoApplication.javaはテンプレートそのままですので、割愛します。
EchoController.javaは以下のようになりました。

EchoController.java
package com.example.form;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping
public class EchoController {
    @ModelAttribute
    public EchoForm setUpEchoForm() {
        EchoForm form = new EchoForm();
        return form;
    }

    @RequestMapping
    public String index(Model model) {
        return "index";
    }

    @RequestMapping(value = "echo", method = RequestMethod.POST)
    public String echo(EchoForm form, Model model) {
        model.addAttribute("name", form.getName());
        return "echo";
    }
}

(早くもformというパッケージ名を後悔し始めている、echoだったな)

setUpEchoFormメソッドについている@ModelAttributeは、そのメソッドの戻り値を自動でModelに追加するためのアノテーションです。Modelの属性名はクラス名(EchoForm)の先頭を小文字にしたものになります。この場合はechoFormです4

@RequestMappingアノテーションは、URLリクエストのマッピングをしています。
自分のざっくりした理解では、@RequestMapping単体だと、ディレクトリパスなしのURL直下にアクセスした時の挙動と思っています。valueを指定するとルートページからの相対パスが指定でき、methodは見た通り、受け付けるHTTPリクエストの種類を指定できます。現在の実装だと、以下の挙動になります。

  • indexメソッドはhttp://{サーバー名}というURLへのリクエストで起動し、”index”というview名をreturnします。
  • echoメソッドはhttp://{サーバー名}/echoというURLへのリクエストで起動し、”echo”というview名をreturnします。 POSTしか受け付けていないため、サーバー起動後直にhttp://{サーバー名}/echoでGETリクエストをすると、以下のエラーが発生します。

スクリーンショット 2020-06-28 23.03.34.png

②フォームクラスの作成

コントローラーで既に出てきましたが、テキストフィールドの値を受け取るためのフォームオブジェクトとして、EchoFormクラスの実装も必要です。getter/setterでnameプロパティをやり取りするだけのシンプルなクラスを実装しました。

EchoForm.java
package com.example.form;

import java.io.Serializable;

public class EchoForm implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

③Viewの作成…っていうかThymeleafってなんなんだ?

よ〜しロジックはできたぞ〜!あとはガワだ〜!JSPでしょ?知ってる知ってる!

…というように、View=JSPだとばかり思っていた人間です。
なので『Spring徹底入門』で以下記述を見たときに、控えめに言って絶望しました。

JSPの利用はさまざまな制約があるため推奨されていないため、本書でも扱いません。(p.602)

おやおや?JSPがないと「「Hello もょもと」ページに遷移」のように、入力値によって生成するHTML自体を変えるような実装はできないのではないか??と思いました。実際に検索すると、Spring BootではJSPの使用はやはり非推奨となっているようです。

If possible, JSPs should be avoided. There are several known limitations when using them with embedded servlet containers.

え…どないすればええんや…?汎用なHTMLを作って…都度DOM操作する気か…??(よく分かってない)

ご安心ください。ちゃんと『Spring徹底入門』12章に書いてありました。

Thymeleafは、Webアプリケーションと親和性の高いテンプレートエンジンです。テンプレートエンジンとは、雛形となるドキュメント(テンプレート)に対し、可変データを埋め込むことで動的にドキュメントを生成する仕組みです。この仕組みは、MVCフレームワークのModelとViewを分割する考え方と親和性が高く、しばしばMVCフレームワークのViewとして利用されます。(p.554)

なるほどなるほど。テンプレートエンジンってそういうものなのですね。よく分からぬままDependenciesに入れてました。だめな人ですね?‍♂️
仕組みとしては、先ほど想像していた形に近く、HTMLをDOMに変換してから処理を行うそうです。

以下がindex.htmlの実装です。

index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<link th:href="@{/style.css}" rel="stylesheet" type="text/css">
<title>Echo Application</title>
</head>
<body>
 <h1>お返事bot TOP</h1>
 <form th:object="${echoForm}" th:action="@{/echo}" method="post">
        <label for="name">名前を入れてください:</label>
        <input th:field="*{name}"> 
        <input type="submit">
</form>
</body>
</html>

頭にth:とついている属性が、Thymeleaf固有の書き方になっています。
フォームクラスとModelの紐付けはコントローラーに付与した@ModelAttributeアノテーションが行っていますが、ThymeleafとModelの紐付けは、以下の部分で行っています。

th:object="${echoForm}"

先述の通り@ModelAttributeアノテーションは、Modelの属性名をクラス名(EchoForm)の先頭を小文字にしたものにしているため、echoFormという名称で正しいです。Thymeleaf記法の変数式に入れた${echoForm}をobjectとして設定することで、入力値の受け渡しができるようになります。

th:actionはHTMLのaction属性と似たような働きです。ただし、パスの書き方がThymeleaf独自のものとなっています。なお、th:actionth:hrefの前に通常のaction属性やhref属性を設定しておくと、アプリの一部として動かす場合だけでなく、普通のHTMLとしても動くようです。ThymeleafのテンプレートはHTML5準拠のため、デザイナーとエンジニア間で共有できるのも強みですね。

「もょもと」を返すecho.htmlは以下のような実装となっています。
ここの変数名は、コントローラーのechoメソッド内の第一引数から来ているため、nameになっています。

echo.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<link th:href="@{/style.css}" rel="stylesheet" type="text/css">
<title>Echo Application</title>
</head>
<body>
<p th:text="|Hello ${name}|"></p> 
<p><a th:href="@{/}">お返事bot TOP</a></p>
</body>
</html>

これで、もょもと完成です!!いええええい!!ぺぺぺ!!

もょもと.gif

おわりに

Springのプロジェクトを再度作ったので、前回より結構分かるようになっていた気がします…!
コントローラーとメインメソッドを分けたこともあって、どのアノテーションがどこに必要なのか、とかも分かりやすくなったのが良かったなと思います!規模が大きくなってきたら、index.htmlとecho.htmlもコントローラー分けてあげなきゃな…と思うなどしました。

Thymeleafは初見だったので、結構いろいろ調べました!最終的に『Spring徹底入門』が最強だったので、Springに手を出したい人は買って損はないです…!辞書的に使えます!

今回もお読みいただき、ありがとうございました!


  1. 調べたらSpring Boot 2.3.0が2020年5月15日、2.3.1が2020年6月12日にリリースされているのですね…1ヶ月スパン…早い…。前回はなかった2.4.0のスナップショットも出ていて、移り変わりの速さにびっくりしました。 

  2. のちにクラス名プレフィクスをEchoに変えたりしているので、特に参考にしなくて大丈夫です。 

  3. HTMLベターッはさすがにやめたくなったので、CSSはこちらのサイトの「ゆっくりゆっくり色変化」を参考にさせていただきました。「サイトの引き立て役はコレ! おしゃれすぎる背景をコピペで実装 【 HTML/CSS 】」https://deshinon.com/2019/03/06/background-oshare-kopipe/ 

  4. こちらのサイトを参考にさせていただいています。「2.3. はじめてのSpring MVCアプリケーション」https://macchinetta.github.io/server-guideline-thymeleaf/current/ja/Overview/FirstApplication.html 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBoot入門ガイドやってみた【Building a RESTful Web Service編】

目的

Spring Quickstart Guideを取り組み終えた方、SpringBootを学び始めた方、復習をしたい方に向けて、

公式が人気ガイドだからやってみて!と勧めてくれている、Building a RESTful Web Serviceを実際に取り組み学んだことを共有します。

開発環境
OS: macOS Mojave バージョン10.14.6
テキストエディタ: Visual Studio Code(以下VSCode)
Java: 11.0.2

前回のおさらいはこちらから

1.SpringBoot projectを始めよう!

まずは、spring initializrにアクセスします。

ADD DEPENDENCIESボタンをクリックします。
スクリーンショット_2020-06-29_13_14_12.png

webと入力して、Spring Webを選択します。

スクリーンショット 2020-06-29 13.30.24.png

Artifact, Nameは、restserviceに変更しました。
Javaのversionは11なので、11を選択します。

スクリーンショット_2020-07-01_14_56_49.png

GENERATEボタンをクリックします。

Zipファイルがダウンロードされるので、

スクリーンショット 2020-07-01 15.09.25.png

そのZipファイルを展開してください。

スクリーンショット 2020-07-01 15.10.29.png

準備完了です。

2.コードを追加しよう!

先ほどのフォルダをVSCodeで開きます。
拡張機能のJava Extension Packのインストールの推奨します。と言われるのでインストールしておきましょう。

スクリーンショット 2020-06-30 10.08.25.png

Greeting.javaを作成しよう!

src/main/java/com/example/restservice/にGreeting.javaファイルを作成します。

スクリーンショット 2020-07-01 15.30.40.png

Greeting.javaファイル内にコードを追加していきます。

Greeting.java完成形
package com.example.restservice;

public class Greeting {
  private final long id;
  private final String content;

  public Greeting(long id, String content) {
    this.id = id;
    this.content = content;
  }

  public long getId() {
    return id;
  }
  public String getContent() {
    return content;
  }
}

Greeting.javaファイルに追加したコードを深掘りしていきます。

①定数の宣言

private final long id;
private final String content;

long型のid、string型のcontentという定数をそれぞれ宣言しています。

アクセス修飾子はprivate、そして、final修飾子を用いているので、同一クラス内からしかアクセス出来ない定数となります。
定数なので、値の再代入は不可能となります。

②コンストラクタの定義

public Greeting(long id, String content) {
  this.id = id;
  this.content = content;
}

インスタンス化された時に同時に行っておきたい処理を記述しておきます。
今回は、インスタンス定数であるid、contentに初期値を代入しています。

③ゲッターメソッドの定義

public long getId() {
  return id;
}
public String getContent() {
  return content;
}

id、contentの値を呼び出すためのメソッドです。

Greeting.javaはこれで完成です!

GreetingController.javaを作成しよう!

src/main/java/com/example/restservice/にGreetingController.javaファイルを作成します。

スクリーンショット 2020-07-01 16.36.36.png

GreetingController.javaファイル内にコードを追加していきます。

GreetingController.java完成形
package com.example.restservice;

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {
  private static final String template = "Hello, %s!";
  private final AtomicLong counter = new AtomicLong();

  @GetMapping("/greeting")
  public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
    return new Greeting(counter.incrementAndGet(), String.format(template, name));
  }
}

GreetingController.javaファイルに追加したコードを深掘りしていきます。

@RestController

@RestController
public class GreetingController {
}

このアノテーションを記述する事により、SpringBootはControllerとして認識してくれます。Viewには遷移せずにメソッドの戻り値がそのままレスポンスのコンテンツとなります。

後述のgreetingメソッドはGreetingオブジェクトを戻り値にしていますが、SpringBootがJSONに自動変換してくれているのでJSONが画面に表示される事になります。

②定数の宣言

private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();

string型のtemplate、long型のcounterという定数をそれぞれ宣言しています。

アクセス修飾子はprivate、そして、final修飾子を用いているので、同一クラス内からしかアクセス出来ない定数となります。
定数なので、値の再代入は不可能となります。

templateの方は、static修飾子を用いているので、クラスに対して1つしか存在しない定数となります。
そして%sは、String.formatというメソッドを使用する際に、第一引数にきめたれた書式を指定しなければいけないため、記載されています。

counterの方は、AtomicLongクラスを用いています。
なぜこのクラスを用いているのかは後ほど記述します。

③greetingメソッドの定義

@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
  return new Greeting(counter.incrementAndGet(), String.format(template, name));
}

@GetMapping("/greeting")は、URLで〇〇/greetingとアクセスされた時(GETリクエストがあった時)、greetingメソッドの処理が行われるためのアノテーションです。
http://localhost8080/greeting とアクセスがあった時にgreetingメソッドが呼ばれるという事です。

greetingメソッドの引数の@RequestParamは、URLのクエリパラメータを受け取る事が出来ます。
String nameにどのような値を格納するのかを指定しており、

http://localhost8080/greeting とアクセスがあった場合は、nameにはWorldが格納され
http://localhost8080/greeting?name=tanaka とアクセスがあった場合は、nameにはtanakaが格納されます

最後にreturnの部分を深掘りします。

return new Greeting(counter.incrementAndGet(), String.format(template, name));

http://localhost8080/greeting とアクセスされる毎にGreetingクラスをインスタンス化したJavaオブジェクトを返しています。
new Greetingのコンストラクタの第一引数にcounter.incrementAndGet()、第二引数に String.format(template, name)を渡しています。

Greetingクラスのインスタンス化、counterのインクリメント、書式を指定して文字列を返す、と複数処理を同時に行っています。

インクリメントはcounter++;と記述も出来ますが、複数の処理が実行された場合に正しく処理されない場合があるため、

上述のAtomicLongクラスでcounterを定義して、同クラスのメソッドであるincrementAndGet()を使用し、現在の値(counter)をインクリメントして、インクリメントした値を返しています。

アトミックは不可分操作と言われており、加算が終わるまで他の処理の割り込みをさせないためにAtomicLongクラスを用いてcounterを定義した、と解釈しました。

3.実行してみよう!

アプリケーション実行の準備が出来たので確認しましょう。

ターミナルで以下のコマンドを入力してEnterしてください。

ターミナル
$ ./mvnw spring-boot:run

2秒ぐらい待った後、http://localhost:8080/greeting にアクセスすると、

スクリーンショット 2020-07-02 11.33.39.png

次に、http://localhost:8080/greeting?name=tanaka でアクセスすると、

スクリーンショット 2020-07-02 11.35.10.png

次に、http://localhost:8080/greeting?name=suzuki でアクセスすると、

スクリーンショット 2020-07-02 11.35.31.png

アクセスする毎にidが増えていき、/greeting?name=任意の文字列でアクセスすると入力した値が表示されていますね!

参考サイト

SpringBoot入門ガイド
OracleのAtomicLong
アトミックであるとはどういうことか
マルチスレッド
Java並行処理の基本

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Android 9.0 Pie】ActivityやFragment以外でのstrings.xmlの呼び出し方の例

初めに

定数の管理に便利なstrings.xmlですが、ActivityやFragmentを継承していないクラスで呼び出す際の方法をメモしておきます。

実装方法

私はApplicationクラスを継承したクラスにコンテキストを生成してしまい、定数取得用の関数を作成して呼び出し側のコードを少しでも削れるようにしてみました。

Model.java
public class Model extends Application {
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
    }

    /**
     * 定数取得関数
     * strings.xmlに定義した定数をどこからでも取得できる
     * @param resId R.string.resId
     * @return String 定数
     */
    public static String getConst(int resId) {
        return context.getResources().getString(resId);
    }
}
strings.xml
<string name="chat_list_delete_button_label">DELETE</string>

呼び出し側は以下のような記述で呼び出せます

Model.getConst(R.string.chat_list_delete_button_label);

以上です。

どなたかの参考になれたら幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【最初理解できんかった】配列でソートしたいけど、配列が全部文字列!?

こんな感じで出力するつもりが、

/*
[出力結果イメージ]
1,犬
2,猫
(省略)
9,鳥
10,サル
11,馬
(省略)
20,獅子
*/

なぜかこうなる…

/*
1,犬
10,サル
11,馬
12,カエル
13,みみず
14,ミミズク
15,フクロウ
16,ライオン
17,パンダ
18,ラマ
19,鹿
2,猫
20,獅子
3,魚
4,貝
5,アシカ
6,シャチ
7,かもしか
8,虫
9,鳥
*/

これが解決したんで載せておきます。

もとのコード(失敗例)

//importはしている前提
//データ一覧
private static final String[] dataList = {
"8,~~",
"10,~~",
"11,~~",
"12,~~",
"20,~~",
"1,~~",
"18,~~",
"13,~~",
"5,~~",
"3,~~",
"19,~~",
"17,~~",
"7,~~",
"16,~~",
"6,~~",
"15,~~",
"2,~~",
"4,~~",
"14,~~",
"9,~~"
};

   public static void main(String[] args) {

    Arrays.sort(dataList); //ソートしています。
       for(String arr : dataList){
       System.out.println(arr);
       }
   }
}

これすると
1
11
12
って先頭だけでソートしてしまいます…?

しかも
❶”int型”,"Strnig型"と区切られてないのでソートしにくい!
❷そもそも文字列だから配列ごと比較するしかないの?でもどうやって

と悩みまくりでした…。

改善後のコード

//importはしている前提
//データ一覧
private static final String[] dataList = {
"8,~~",
"10,~~",
"11,~~",
"12,~~",
"20,~~",
"1,~~",
"18,~~",
"13,~~",
"5,~~",
"3,~~",
"19,~~",
"17,~~",
"7,~~",
"16,~~",
"6,~~",
"15,~~",
"2,~~",
"4,~~",
"14,~~",
"9,~~"
};

   public static void main(String[] args) {

       //ソートしています。
       Arrays.sort(dataList, (s1, s2) -> {
         String[] s1Parts = s1.split(',');
         int n1 = Integer.parseInt(s1Parts[0]);
         String[] s2Parts = s2.split(',');
         int n2 = Integer.parseInt(s2Parts[0]);
         return Integer.compare(n1, n2);
       });
       for(String arr : dataList){
       System.out.println(arr);
       }
   }
}

ただなんもわかってないので、自分用に解説します。

なぜこうなるのか?

まずArrays.sortのドキュメント。具体的にはシグニチャを確認しましょう。
また、ドキュメント見るときはGoogle翻訳もうまく活用しましょうね。

シグニチャとは…
シグニチャはメソッド名と引数の数、引数の型、引数の順番が含まれています。※引数の数、引数の型、引数の順番をまとめて引数のリストともいいます。
signature_image.png

public static <T> void sort(T[] a,
                            Comparator<? super T> c)

1つ目の引数はT[] aであり、String[] dataListがそれにあたります。

2つ目の引数はComparator<? super T> cComparatorは interface で、「比較器」の意味です。1つ目の引数により、TStringに固定されています。? super Tは、Tのスーパークラスです。全体に見れば、この引数は「Stringあるいはそのスーパークラスを比較できる関数」の意味です。sortはこの関数を使って配列をソートします。

またComparatorはこのようなものです:
公式ドキュメントより java.util.Comparator.compare

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

前言により、ここのTも「Stringあるいはそのスーパークラス」になります。

Java 8 以降、Comparatorみたいな@FunctionalInterfaceを求める引数にラムダ式(lambda expression)を入れられます。ラムダ式はこの部分です:

(s1, s2) -> {
  String[] s1Parts = s1.split(',');
  int n1 = Integer.parseInt(s1Parts[0]);
  String[] s2Parts = s2.split(',');
  int n2 = Integer.parseInt(s2Parts[0]);
  return Integer.compare(n1, n2);
}

これが実はcompareを定義しているのです。

(s1, s2)は引数リストで、s1もs2もStringです。これはComparatorよりJavaもう知っていますから、略しても大丈夫です。

->ただの提示符で、「この後はcompareの内容です」と提示しています。

{}の中はcompareの内容です。compareの要求は、

s1がs2と同じの場合、0を返す;
s1がs2より大きいの場合、正の整数(ここでは1)を返す;
s1がs2より小さいの場合、負の整数(ここでは-1)を返す

ことです。この要求をInteger.compareが満たせますので、ここで使います。

オブジェクトの配列をソートする場合、オブジェクトがComparableを実装したら、Comparatorの必要がなくなりますが、ここではStringのComparableが要求の順番と違いますから、新たにComparatorを作って渡すそうです!

そして、Integerが実はjava.util.Integerというクラスで、intに関して様々な関数を含めています。なぜなら、intがプリミティブ型であり、メソッドをつけられなくて、Integerに付けることになりました。だから、Integer.compareが「Integerを返す」ことがなく、「Integerを比較する」の意味です。たまたまcompareがintを返すべきことがあるだけで、最後のreturnは「Integerを比較する」の意味比較した結果を返しているという認識です。

並べ方について(ソートの名前)

1
10
11
12
2
20
21

という並び方は「辞書式順序」 (lexicographical order) と呼ばれています。なぜなら、Stringをソートする場合はほぼアルファベット順序でソートするので

a
ab
abc
ac
b
ba
bb

みたいなので、そのまま数字に伸びてしまいました。一方

1
2
3
5
11
13
22

というのは「自然順」 (natural order) と呼ばれています。バーション数字をソートするのにもよく使われてますので、「バーションソート」 (version sort) とも呼ばれています。

非常に勉強になりました…。
なぜ?の部分が結構自分なりに解釈出来て感動しております
?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SDKMAN でインストールした java のバージョンをディレクトリ移動時に切り替える

.sdkmanrc を使うと、direnv のようにディレクトリ移動時に SDKMAN でインストールした java, scala などのバージョンを切り替えることができる。
比較的最近追加されたので、利用するには SDKMAN を最新(執筆時点では 5.8.3)にアップデートしておく。

.sdkmanrc を生成

バージョンを指定したいディレクトリで sdk env init を実行する。

$ cd sdkenv-test/
$ sdk env init
.sdkmanrc created.

.sdkmanrc が生成され、次のようになっている。

$ cat .sdkmanrc
# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=8.0.202.j9-adpt

設定

生成された .sdkmanrc に使用したいバージョンを指定する。

# Enable auto-env through the sdkman_auto_env config
# Add key=value pairs of SDKs to use below
java=13.0.2.j9-adpt

~/.sdkman/etc/config で sdkman_auto_env=true に設定する。
sdkman_auto_env=true とすることで direnv のようにディレクトリに移動したときに自動的に切り替わる。

シェルをリスタートする。

$ exec $SHELL -l

実行例

$ cd sdkenv-test/

Using java version 13.0.2.j9-adpt in this shell.

sdkman_auto_env=false の場合は手動で切り替える必要がある

sdkman_auto_env=false に設定されていると、.sdkmanrc のあるディレクトリに移動しても自動では切り替わらない。
sdk env を実行して切り替えないといけない。

$ cd sdkenv-test/
$ java --version
openjdk 14.0.1 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.20.0, JRE 14 Linux amd64-64-Bit Compressed References 20200416_44 (JIT enabled, AOT enabled)
OpenJ9   - 05fa2d361
OMR      - d4365f371
JCL      - 5757187cae based on jdk-14.0.1+7)
$ sdk env

Using java version 13.0.2.j9-adpt in this shell.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaの配列

はじめに

順次進行、繰り返し、条件分岐でだいたい作れるって言われても全然作れんわッッ!!(本投稿に関係はない)

配列

変数は1つの値しか入れられないが、配列は複数の値をまとめて入れれる。
1列のものを一次元配列、2列以上は多次元配列という。
ひとつひとつを要素といい、その数を要素数という

kane.java
データ型[] 配列変数名;
配列変数名 = new データ型[要素数];

String[] banana;
banana = new string[3];

banana[0] = "Cavendish"
banana[1] = "Lakatan"
banana[2] = "GrosMichel"

上記は宣言と代入を別に記述
下記は宣言と代入を同時に

kane.java
データ型[] 配列変数名 = {要素1,要素2,...};

String[] banana = {"Cavendish", "Lakatan", "GrosMichel"}

[]はデータ型の後くっつけて書く(int[],String[]のように)

要素の上書きは、配列変数名[インデックス番号] = "新しい要素"

各要素は、配列名[インデックス番号]で取得できる。

kane.java
String[] banana = {"Cavendish", "Lakatan", "GrosMichel"};

banana[1] = "LadyFinger" //要素の上書き

System.out.println(banana[1]);

//実行結果 LadyFinger

多次元配列

エクセルの表計算ソフト的な

banana 0 1
0 Cavendish Lakatan
1 GrosMichel LadyFinger
kane.java
データ型[][] 配列変数名;
配列変数名 = new データ型[要素数][要素数];

String[][] banana;
banana = new string[2][2];

banana[0][0] = "Cavendish"
banana[0][1] = "Lakatan"
banana[1][0] = "GrosMichel"
banana[1][1] = "LadyFinger"

System.out.println(banana[1][0]);

//実行結果 GrosMichel

これも省略できる

kane.java
String[][] banana = {{"Cavendish", "Lakatan"},{"GrosMichel","LadyFinger"}};

System.out.println(banana[0][0]);

//実行結果 Cavendish

終わりに

多次元配列って便利なのはわかるが自分の頭が一次元だからパンクする

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む