20200224のJavaに関する記事は9件です。

SpringBootプロジェクトをXAMPPのTomcatにデプロイする

はじめに

SpringBoottomcat を内蔵しておりjar実行するだけでアプリを起動できる。
別で構築した tomcat にデプロイする方法を知らなかったので調べてみた。

検証環境

  • Windows10
  • Java : 1.8.0_221
  • XAMPP : 7.3.11
  • Tomcat(XAMPPに同梱) : 7.0.96
  • Spring Boot : 2.2.4

SpringBootでアプリを作成する

「Spring Initializr」で検証用の雛形プロジェクトをgradleで作成する。
https://start.spring.io/

テキトーにWebAPIっぽいものを作る。

  • 「/」で「Hello World!」を返却
  • 「/hoge」で「hogehoge」を返却

なんの変哲もないコントローラを作成。

AppController.java
package warapp.app.controller;

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

@RestController
public class AppController {
    @GetMapping("/")
    public String index() {
        return "Hello World!";
    }
    @GetMapping("/hoge")
    public String hoge() {
        return "hogehoge";
    }
}

Applicationクラスを実行して、SpringBootを起動。
「localhost:8080」、「localhost:8080/hoge」に接続して期待通りの結果が取得できた。

Tomcatにデプロイする準備

Tomcatに配置するためには2点の条件をクリアしなければならない。

  1. warファイルの作成
  2. 内臓Tomcatを使わないように設定

Initializrで作成した状態だと、jarを作成するタスクはあるがwar作成のタスクがない。
やり方がちゃんと公式に書いてあった。

Spring Boot Reference Documentation

この手順に沿って行くだけでよい。

SpringBootServletInitializer を継承する

  • @SpringBootApplicationの付与されているメインクラスを SpringBootServletInitializer のサブクラスにする。
  • configure メソッドをオーバライドする。

【変更前】

AppApplication.java
package warapp.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AppApplication {
    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }
}

【変更後】

AppApplication.java
package warapp.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

@SpringBootApplication
public class AppApplication extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(AppApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(AppApplication.class, args);
    }
}

warプラグインを追加

build.gradle にwarプラグインを追加する。

build.gradle
plugins {
    id 'org.springframework.boot' version '2.2.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'war-app'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

//以下1行を追加
apply plugin: 'war'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

test {
    useJUnitPlatform()
}

組み込みTomcatが干渉しないようにする

dependenciesに以下の設定を追加。

build.gradle
plugins {
    id 'org.springframework.boot' version '2.2.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'java'
}

group = 'war-app'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

apply plugin: 'war'

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    //以下を追加
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}

test {
    useJUnitPlatform()
}

これでwarファイルがbuildできるようになる。

warファイルのビルド

※IntelliJを使用したが、eclipseなどでも同じだと思われる

  1. gradleのタスク一覧を開く。
  2. 「Tasks」 → 「build」 → 「bootWar」を実行

【実行結果】

22:31:35: Executing task 'bootWar'...

> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :bootWar

BUILD SUCCESSFUL in 1s
3 actionable tasks: 1 executed, 2 up-to-date
22:31:37: Task execution finished 'bootWar'.

<プロジェクトルート>/build/libs の配下に 「~-0.0.1-SNAPSHOT.war」というファイルができている。

Tomcatに反映

あとで面倒くさいのでwarファイル名を変える。

「~-0.0.1-SNAPSHOT.war」→「~.war」
ex. 「app-0.0.1-SNAPSHOT.war」→「app.war」

ファイルの配置

「~.war」をTomcatのwebappsディレクトリに配置する。
今回はxamppを使っているので、「xampp\tomcat\webapps」に配置。

Tomcatの起動

xamppのコントロールパネルからTomcatを起動する。
起動するとwarファイルが展開される。

ログを確認

「xampp\tomcat\logs\catalina.yyyy-mm-dd.log」に以下のようなエラーが出ているはず。

catalina.2020-02-24.log
情報: Webアプリケーションアーカイブ C:\xampp\tomcat\webapps\app.war を配備します
2 24, 2020 10:43:45 午後 org.apache.catalina.startup.TldConfig execute
情報: At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
2 24, 2020 10:43:49 午後 org.apache.catalina.core.ContainerBase addChildInternal
重大: ContainerBase.addChild: start: 
org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/app]]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:162)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1018)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:994)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:662)
    at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1127)
    at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:2020)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'defaultValidator' defined in class path resource [org/springframework/boot/autoconfigure/validation/ValidationAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.beanvalidation.LocalValidatorFactoryBean]: Factory method 'defaultValidator' threw exception; nested exception is java.lang.NoClassDefFoundError: javax/el/ELManager
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:656)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:484)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1338)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
    at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.run(SpringBootServletInitializer.java:152)
    at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:132)
    at org.springframework.boot.web.servlet.support.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:92)
    at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:172)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5709)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
    ... 10 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.validation.beanvalidation.LocalValidatorFactoryBean]: Factory method 'defaultValidator' threw exception; nested exception is java.lang.NoClassDefFoundError: javax/el/ELManager
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:651)
    ... 32 more
Caused by: java.lang.NoClassDefFoundError: javax/el/ELManager
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:88)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.<init>(ResourceBundleMessageInterpolator.java:47)
    at org.hibernate.validator.internal.engine.ConfigurationImpl.getDefaultMessageInterpolator(ConfigurationImpl.java:474)
    at org.springframework.boot.validation.MessageInterpolatorFactory.getObject(MessageInterpolatorFactory.java:53)
    at org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration.defaultValidator(ValidationAutoConfiguration.java:57)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
    ... 33 more

2 24, 2020 10:43:49 午後 org.apache.catalina.startup.HostConfig deployWAR
重大: Webアプリケーションアーカイブ C:\xampp\tomcat\webapps\app.war を配備中のエラーです
java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/app]]
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:1022)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:994)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:662)
    at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:1127)
    at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:2020)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

tomcat側の javax/el/ ライブラリが古いのが原因のエラーのようだ。
バージョンは 2.2 だが、 3.0 以上に上げる必要がある。

下記リンクから3.0のjarをダウンロードして、「xampp\tomcat\libs」配下に配置する。
このとき、古いバージョンのjarは削除してよい。

jarを配置したらTomcatを再起動(STOP→START)する。

立ち上がった後、ログを確認し、スタックトレースが出ていなければ問題なし。

デプロイ確認

ブラウザでデプロイされているのを確認する。
※以下からは「app.war」を配置した体で確認を行っている。

ブラウザで「localhost:8080/app」に接続 → 「HelloWorld!」を確認
ブラウザで「localhost:8080/app/hoge」に接続 → 「hogehoge」を確認

正常にデプロイされていることが確認できた。

おわりに

今までTomcatにはお世話になってきたが、自分でデプロイしたことがなかったので良い勉強になった。
eclipseの内蔵tomcat、SpringBootの内蔵tomcatを使わなくても自分で対応することができるようになったので安心。
Linux環境へのインストールとデプロイも今後やってみようと思う。

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

Javaでよく使う構文例

はじめに

Javaで開発することが多くなり、使っている構文などを載せていこうかと思います。
基本的な構文なので、当たり前だと思うこともあると思いますがメモぐらいの気持ちで見ていただければと思います。

StringUtils.isEmpty()

String str = "Hello";
if (StringUtils.isEmpty(str)) { }

使い方としては空かnullなどをチェックする時に使います。nullチェックがこれでできるのでよく使ったりします。
最近のJavaであればOptionalを使う方がいいですが、まだまだよく使われる記法
isEmptyというのもありますが、nullチェックはしてないのでStringUtils.isEmptyを使ったほうがいいです。

ObjectUtils.isEmpty()

StringUtils.isEmpty()と同様の働きをします、Objectに対して行うので、APIなどで取得したデータをEntityに入れたあとにチェックしたりします。APIの取得がうまく行かなくてObjectが正しく作られていない場合にfalseを返すようになります。

簡易的な等式チェック strA.equals(strB)

private static final String RESULT_CODE = "OK";
// 何かのAPIの結果
String apiResult = nanikanoApi();
if (RESULT_CODE.equals(apiResult)) {}

このような使い方をします。RESULT_CODE側はnullにならない定数や決まった値にする必要があります。
nullの値をしてしまうとNullPointerExceptionで落ちますので注意。
比較したい値はapiResult側で処理してください。

こういうのもあります A == null

基本的にはString型のものだけです。他の型でやるとうまく動きません。
※ 「==」の特性上、メモリの参照先が同じならtrue、メモリの参照先が違えばfalseとなります。今回はnullの場合ですので頭の片隅に置いていただければと思います。
昔から「null == A」と「A == null」の2つの書き方がありますが、現在のJavaではどちらも同じ動きをします。なので「A == null」でいいと思います。(昔はNullPointerExceptionを防げるとかあったらしい)
ちなみに「null == A」はヨーダ記法と呼ばれています。ヨーダがその昔「強いぞ、ベーダ卿は」といったことにつながるとかなんとか。

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

SocialHub を支える技術 - iOS Android 共通ライブラリ編 -

SocialHub とは?

 まずはアプリの紹介をさせてください。SocialHub とは、先日筆者がリリースした iOS 向けマルチ SNS クライアントアプリです。大きな特徴として挙げられるのが、複数の SNS を同時に見て投稿できる ことで、現在対応している SNS の種類は Twitter, Mastodon, Slack, Tumblr になります。

SocialHub - マルチ SNS クライアント - | App Store

マルチプラットフォーム

 SocialHub は現在 iOS のアプリしかリリースしていませんが、Android のアプリも見据えたプロジェクトで、複数の SNS を統合して扱う都合上複雑なロジックから逃げられないため、できればワンコードでロジックを記述したい気持ちがありました。昨今流行りのマルチプラットフォームなフレームワークを用いての実装を、当初 (2018年初旬) 考えており、当時のマルチプラットフォームというと、XamarinReact Native が選択肢としてあったのですが、自分がその分野に明るくなかった事や、一番の問題として、そのライブラリで本当に自分の目指した UI/UX が実現できるか自信がなかったことです。

 また、技術選定中に Flutter も注目を浴びましたが、こちらについてもサンプルプロジェクトを作って動作を検証したのですが、パフォーマンスが気になる事や、情報の少なさから、自分の求める UI が作成できるか自信がなかったので採用を見送りました。

共有ライブラリという選択

 そこで注目したのが、iOS/Android で共有ライブラリを作ることのできるフレームワークです。共有ライブラリを用いることで、共通のロジックは全て単一の言語で記述し、クライアントアプリ側はネイティブで UI を記述することによって、リッチな体験を担保します。以下に選択肢として考えたものについて記述します。(理解が浅い部分があったらコメントくださると助かります)

Embeddinator-4000


 共有ライブラリという方針を決めてから、一番初めに見つけたのが Embeddinator-4000 です。これは Xamarin のサブプロジェクトの一つで C# で書かれたコードを iOS/Android 向けの共有ライブラリとして扱うことができるようにするコンパイラ? (適切な表現かどうか不明) です。細かい内容については、以下の自分のブログ記事を御覧ください。

 .NET のコードを他言語に変換? Embeddinator-4000 を使ってみた。| urushi blog

 動作確認までしたのですが、正直 iOS 向けにバイナリをビルドする際に、C# のジェネリクスが使えない事がかなりの痛手で、ほぼまともに動作するライブラリを作成することができませんでした。理由としては iOS 向けに mono のランタイムをアプリに同梱することができないためで、一朝一夕でなんとかなる問題ではありませんでした。また、開発もあまり活発的ではなく、上記の問題の解決が期待できないので諦めざるを得ない状況でした。

J2ObjC


 次に発見したのが J2ObjC です。Google が主に開発を進めているプロジェクトで、Java のコードを Objective-C に変換してくれるコンパイラ になります。既に公開から八年以上経っているプロジェクトですが、まだ開発が行われており、Google 社内でも恐らく使われているようなので、安心感があります (?) J2ObjC についても、ブログ記事を書いたので、詳しくは以下を参照してください。

 Java のコードを Objective-C に? J2ObjC を試してみた。 | urushi blog

 SocialHub はこの J2ObjC を採用しました。 理由としては、

  • 自分に Java に対しての言語理解があること
  • プロジェクトがメンテされていること
  • パフォーマンスの劣化がほぼ無いこと

 が挙げられます。J2ObjC はコンパイル前後の言語 (要するに Java と Objective-C) の知識をある程度必要とするので、問題に対応する際に、Java が分からないでは対処が難しいケースが存在します。

 ということで SocialHub で作成したコアライブラリがこちら。

 SocialHub | github

 (ライブラリ名も SocialHub) J2ObjC でここまでコンパイルしている例って Google 外ではあまりないんじゃないかと思う。(そもそも J2ObjC なんて誰も使ってねえよ、っていう声は聞かないことにします) コンパイルは GitHub Actions に代行させており、大体所要時間は 15 分程。Java を Objective-C に変換した後、マルチアーキテクチャ向けにバイナリを作成し、Cocoapod のレポジトリを作成するところまで実行します。

 補足として、J2ObjC のサブプロジェクトでは、既にメンテナンスされていないものも存在し、ややエコシステムとして不便な部分もありました。J2ObjC はコンパイルする際に make (or bazel) を使うケースが多いのですが、自分はめんどくさがりなので、J2ObjC の Gradle Plugin を用いてコンパイルを行っていました。Plugin を使うと Gradle で記述されている依存関係についても自動的にコンパイルターゲット加え、かつ import での参照関係も確認してくれるため、余計なコンパイルをしないで済みます。しかし Gradle Plugin は既にメンテされなくなっており、Java11 では動作しない問題を抱えていました。そのため Fork して修正しました。(結局手間がかかっている)

 正直、J2ObjC での初期の実装は茨の道でしたが、その道を通り抜けると、それなりに快適な開発環境を整えることができました。ということで、SocialHub では Java のライブラリを使用して iOS アプリを作成しています。

 アプリ内の権利表記にはコアライブラリ内で使用した Java のライブラリも記載しています。Twitter4J をはじめとした各種 SNS クライアントライブラリを使っています。

Kotlin/Native

 一応、共有ライブラリということなので、忘れてはいけないもう一つの選択肢である
Kotlin/Native について記載します。こちらは Kotlin で書かれたコードを LLVM エコシステムに混ぜてしまおうという、実にシンプルな目的のプロジェクトです。

 ここで厄介なのが、Kotlin で書かれたコードといっても、その内容に制約がかかることです。Kotlin といえば一般的に Java の資産がそのまま流用できるイメージを持たれていると思いますが、Kotlin/Native においては JVM 外での Kotlin の使用になるので java, javax 以下のパッケージのライブラリには使用の制限がかかりますし、既存の Kotlin ライブラリもマルチプラットフォーム用に記述されていないと使用できません。

 利点としては、プラットフォーム API 等を用いたより高度な抽象化ロジックを記述することができる事にあります。Kotlin/Native では iOS の UIKit を扱うためのライブラリ等が提供されているため、共有ライブラリのレベルで実現できる範囲が広いのが特徴です。SocialHub ではあまりその恩恵を受けられないので、J2ObjC を選択しました。将来的に Java のライブラリが困難なく使用できるレベルになってきたら使用するか検討したいと考えています。(そんな日が来るのかあんま詳しくない)

まとめ

  • マルチプラットフォームを選択する場合、本当に実現したい事が何かに立ち戻る

 SocialHub の場合ユーザー体験について妥協することがどうしても嫌だったので、UI やパフォーマンスに対してフレームワークを理由に妥協することがどうしてもできなかった → 共通ライブラリという選択

 無論、何を意識するかで全てが変わるので、実装スピードやスキルセットに合わせて Flutter や ReactNative を使用することは全然アリです。というか、かなり自分は異端だと感じています。

  • J2ObjC 使ってる人いないけどいいぞ

 共通ライブラリをまとめると以下のような印象。

フレームワーク 言語 導入難度 知名度 実用性
Embeddinator-4000 C# 難しい ほぼ無名
J2ObjC Java 難しい ほぼ無名
Kotlin/Native Kotlin 普通 有名

 J2ObjC のすごいところは、Java の標準ライブラリをほぼ Objective-C で再実装されている点で、思った以上に普通に Java がコンパイルできることです。でも Java なんだよなぁ。。。

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

Spring Boot パフォーマンス関連の設定

Spring Bootでパフォーマンス関連の設定をする機会があったので、メモしておきます。

私の仮説も含んでいますので、その点はご了承ください。
随時ブラッシュアップしていこうと思います。

各種バージョン

openjdk version "11.0.6"
org.springframework.boot:spring-boot-starter-web:jar:2.1.7.RELEASE
org.springframework.boot:spring-boot-starter-jetty:jar:2.1.7.RELEASE
org.springframework.boot:spring-boot-starter-jdbc:jar:2.1.7.RELEASE
com.h2database:h2:jar:1.4.199

Jetty(Java Servletコンテナ/Webサーバ)

Jetty はJava Servletコンテナ/Webサーバで、Spring Bootの組み込みコンテナです。
Jettyでは主にスレッド数を設定します。

設定項目

設定項目 意味 デフォルト値 application.propertiesの項目名
スレッドプールの最大スレッド数 意味は左記の通りですが、application.propertiesで値を設定してもデフォルト値から変更されませんでした。QueuedThreadPoolには外部からの設定を受け入れるような箇所がないのが原因かと思いますが、調査次第、記載しようと思います。ただ、最大で200もあれば十分なのかなと思ってしまうので、設定できる必要性は薄いのかもしれません。 200 server.jetty.max-threads
スレッドプールの最小スレッド数 最大スレッド数と同様、設定しても有効になりませんでした。調査していこうと思います。 8 server.jetty.min-threads
アクセプタースレッドの数 アクセプタースレッドは、クライアントからの要求を受け付け、セレクタースレッドに処理を任せます。アクセプタースレッド自体は大したことをしないので、このスレッド数は少なくても大丈夫です。デフォルトから変更する必要はほぼ無いかと思います。なお、アクセプタースレッドもスレッドプールのスレッドです。 -1(オペレーティング環境から導出される。導出のロジックについては参考ページをご覧ください。) server.jetty.acceptors
セレクタースレッドの数 セレクターとは、ノンブロッキングIOでクライアントとの通信チャンネルを管理するコンポーネントです。従来のブロッキングIOでは、クライアントとの通信が開始してから完了するまで、スレッドがブロックされていました。Jettyで採用されているノンブロッキングIOでは、ちょっとした待ち時間ではスレッドはブロックされず、その間に別の処理を実行することができ、効率性が向上しています。このためには、通信するチャンネルを覚えておく必要があり、その役割を担うのがセレクターです。セレクターは、後続のJava処理を呼び出したりするので、セレクタースレッドの数は性能に大きな影響を与えます。なお、セレクタースレッドもスレッドプールのスレッドです。 -1(オペレーティング環境から導出される。導出のロジックについては参考ページをご覧ください。) jetty.server.selectors

application.propertiesに設定すると、以下のようになります。

application.properties
server.jetty.acceptors=2
server.jetty.selectors=3

設定が効いているかどうかを確認する方法

Macで確認する方法です。(他の環境でも確認できるはずですが、未確認です。)

①Spring Bootアプリケーションを起動
②jconsoleを起動

ターミナルでjconsoleと入力し、実行します。jconsoleはJDKに同梱されているGUIツールで、JMXを利用してMBeanの状態を可視化してくれます。

起動すると、以下のような画面が表示されます。Spring Bootアプリケーションを選択して、接続してみましょう。
スクリーンショット 2020-02-24 14.26.05.png

③スレッドの状態を確認

スレッドの状態を確認すると、以下のようになっており、設定が効いていることが確認できます。

スレッド.png

なお、ここでは先述のとおりserver.jetty.acceptors=2server.jetty.selectors=3と設定しています。

ちなみに、qtpで始まるスレッドが8本あるのは、スレッドプールの最小サイズ(スレッド数)のデフォルト値が8だからです。

参考

https://www.eclipse.org/jetty/documentation/current/architecture.html
https://support.sonatype.com/hc/en-us/articles/360000744687-Understanding-Eclipse-Jetty-9-4-8-Thread-Allocation
https://www.techscore.com/tech/Java/JavaSE/NIO/5/
https://www.techscore.com/tech/Java/JavaSE/NIO/5-2/
https://spring.pleiades.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#server-properties

HikariCP(コネクションプール)

HikariCPは高速なコネクションプールとして、Spring Bootでもよく使われます。spring-boot-starter-jdbc -> HikariCP という依存となっていて、spring-boot-starter-jdbcを使用するとHikariCPがデフォルトで利用されます。

HikariCPではコネクションプールで維持するコネクションの数などについて、設定します。

設定項目

設定項目 意味 デフォルト値 application.propertiesの項目名
connectionTimeout クライアントがコネクションプールからコネクションを取得するときの最大待ち時間です。 30000(30秒) spring.datasource.hikari.connection-timeout
idleTimeout コネクションプールの中のコネクションが、この時間だけ使われずに放って置かれた場合、そのコネクションはコネクションプールから解放されます。この設定は、minimumIdleがmaximumPoolSize未満に定義されている場合にのみ適用されます。アイドルなコネクションがたくさんあっても、minimumIdleの数のコネクションは解放されずに維持されます。コネクションが解放されるかどうかの判定は、この設定値そのものが閾値になるわけではなく、この設定値を起点としてバラつきがあります(設定値+平均15秒、+最大30秒のバラつき)。値0は、アイドル接続がプールから解放されないことを意味します。許可される最小値は10000ms(10秒)です。 600000(10分) spring.datasource.hikari.idle-timeout
maxLifetime プールされてからこの時間が経過したコネクションは、コネクションプールから解放されます。使用中のコネクションは、そのコネクションがcloseされるのを待って削除されます。とはいえ、一気に大量のコネクションが解放されるのはマズイので、そうならないように制御されます。 1800000(30分) spring.datasource.hikari.max-lifetime
minimumIdle プールに最低限 維持しておこうとHikariCPが努める、アイドルなコネクションの数。つまりこの数を下回らないことは保証されません。下回った場合はHikarriCPがベストエフォルトでこの数だけのアイドルなコネクションを生成してくれます。しかし、最高のパフォーマンスと、突発的な処理要求に対応できるようにするために、そもそもHicariCPではこの設定を使わず、デフォルト値のまま(maximumPoolSize と同じ)が推奨されます。 maximumPoolSize と同じ。 spring.datasource.hikari.minimum-idle
maximumPoolSize プールで維持するコネクション(使用中、アイドルの両方)の上限数です。プール中のコネクションが全て使用中だった場合、getConnection()すると、connectionTimeoutだけブロックします。 10 spring.datasource.hikari.maximum-pool-size

application.propertiesに設定すると、以下のようになります。

application.properties
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.minimum-idle=10
spring.datasource.hikari.maximum-pool-size=10

設定が効いているかどうかを確認する方法

Macで確認する方法です。(他の環境でも確認できるはずですが、未確認です。)

①HikariCPのオブジェクトがMBeanとして登録されるように設定

以下の設定を追加します。

application.properties
spring.datasource.hikari.register-mbeans=true

これで、HikariCPのオブジェクトがMBeanとして登録され、JMXを利用して外部からモニタリングできるようになります。

②Spring Bootアプリケーションを起動
③jconsoleを起動

ターミナルでjconsoleと入力し、実行します。jconsoleはJDKに同梱されているGUIツールで、JMXを利用してMBeanの状態を可視化してくれます。

起動すると、以下のような画面が表示されます。Spring Bootアプリケーションを選択して、接続してみましょう。
スクリーンショット 2020-02-24 14.26.05.png

④MBeanの状態を確認

アクティブなコネクション、アイドルなコネクションの状況については、以下のように確認できます。
スクリーンショット 2020-02-24 14.29.53.png

適用された設定値については、以下のように確認できます。
スクリーンショット 2020-02-24 14.32.12.png

参考

https://github.com/brettwooldridge/HikariCP
https://spring.pleiades.io/spring-boot/docs/current/reference/html/appendix-application-properties.html#data-properties
https://matsumana.info/blog/2016/02/06/spring-boot-hikaricp-metrics/

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

Java8メソッド参照

今回みなさんと一緒にJava8のメソッド参照を勉強しましょう。

まずは、Java8以前の方法で年齢ソードのメソットを作りましょうか。

lombokを使う

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;

public class App {
    public static void main( String[] args ) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jack",25));
        personList.add(new Person("Mary",23));
        personList.add(new Person("Jill",24));
        personList.add(new Person("Dock",34));
        personList.add(new Person("Lily",27));


        // 匿名クラス
        Collections.sort(personList, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        System.out.println(personList);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private int age;
}

実装結果:[Person(name=Mary, age=23), Person(name=Jill, age=24), Person(name=Jack, age=25), Person(name=Lily, age=27), Person(name=Dock, age=34)]

Lambda式

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;

public class App {
    public static void main( String[] args ) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jack",25));
        personList.add(new Person("Mary",23));
        personList.add(new Person("Jill",24));
        personList.add(new Person("Dock",34));
        personList.add(new Person("Lily",27));

        // Lambda式
        Collections.sort(personList,(p1,p2) -> p1.getAge() - p2.getAge());
        System.out.println(personList);
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private int age;

}

実装結果:[Person(name=Mary, age=23), Person(name=Jill, age=24), Person(name=Jack, age=25), Person(name=Lily, age=27), Person(name=Dock, age=34)]

static メソッド参照

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.*;

public class App {
    public static void main( String[] args ) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jack",25));
        personList.add(new Person("Mary",23));
        personList.add(new Person("Jill",24));
        personList.add(new Person("Dock",34));
        personList.add(new Person("Lily",27));

        // static メソッド参照
        Collections.sort(personList, Person::CompareAge);
        System.out.println(personList);

    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person {
    private String name;
    private int age;

    public static int CompareAge(Person o1, Person o2) {
        return o1.getAge() - o2.getAge();
    }
}


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

ラムダ式入門

何でラムダ式を使うのか?

大規模な開発に使われるJavaでは、なるべくコードの量を減らし読みやすいことを求められています。そのような時役にたつのがJava8で使われている「ラムダ式」です。
ラムダ式の特徴としては、メソッドを変数のように扱えることです。

書き方:

[インタフェース宣言]= (引数)->{実行ブロック}

さっそくソースコードから確認してみましょう。
まずは伝統的な表現式:

public class LambdaTest {

    public static void main(String[] args) {
        // 伝統的な表現
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threading" + Thread.currentThread().getId());
            }
        }).start();
    }
}

次は、Lambda式の表現です。

public class LambdaTest {

    public static void main(String[] args) {
        // Lambda表現
        new Thread(() -> {
            System.out.println("Lambda threading" + Thread.currentThread().getId());
        }).start();
    }
}

Java関数型インタフェース

Javaの関数型インタフェースは伝統的なインタフェースとほとんど同じですが、唯一の特徴は関数インタフェースが「定義されている抽象メソッドが1つだけあるインターフェース」であることです。staticメソッドやデフォルトメソッドは含まれていても構わないです。(java.lang.FunctionalInterfaceアノテーションを付けるのが良いでしょう)。

Java関数インタフェースについての詳細紹介を参考ください。
https://qiita.com/Jenny1025/items/470e304ff77c44874fc8

例:

@FunctionalInterface
public interface UserIdentify {
    String verifyUser(String userName);
}

Lambda式のメリット

JavaのLambda式のメリットは、匿名クラスを用いる関数型インタフェースの記述を簡潔になることが実現できます。
*匿名クラス:一度だけ使わないようなクラス

匿名クラス:

public class App 
{
    public static void main( String[] args ) {

        // 関数型インタフェース
        UserIdentify ic = new UserIdentify() {
            @Override
            public String verifyUser(String userName) {
                return "admin".equals(userName)? "admin":"member";
            }
        };

        System.out.println(ic.verifyUser("admin"));
        System.out.println(ic.verifyUser("manager"));
        }
    }

では、Lambda式でどう表現するか?

public class App {
    public static void main( String[] args ) {

        UserIdentify ic1 = (String userName) -> {
                return "admin".equals(userName)? "admin":"member";
            };
        System.out.println(ic1.verifyUser("admin"));
        System.out.println(ic1.verifyUser("manager"));
        }
    }

具体的にラムダ式の書き方の説明

1)宣言:ラムダ式のインタフェースの型
2)引数:()中に記入する。インタフェースの引数の数及び順番と同じ
3)表現式:->
4)実行ブロック:{}中に実装する

では、具体的な例から説明に行きましょう。

引数なし、戻り値なし

public class App {
    public static void main( String[] args ) {

        // 引数なし、戻り値なし
        Lambda1 lambda1 = () -> System.out.println("Hello world");
        lambda1.test();
    }
    interface Lambda1 {
        void test();
    }
}

実装結果:Hello world

引数あり、戻り値なし

public class App {
    public static void main( String[] args ) {

        // 引数あり、戻り値なし
        Lambda2 lambda2 = (n, a) -> System.out.println(n + " is " + a + " years old");
        lambda2.test("Sally",18);
    }
    interface Lambda2 {
        void test(String name, Integer age);
    }
}

実装結果:Sally is 18 years old

引数あり、戻り値あり

public class App {
    public static void main( String[] args ) {

        // 引数あり、戻り値あり
        Lambda3 lambda3 = (a, b) -> a + b;
        System.out.println(lambda3.test(100,3000));
    }
    interface Lambda3 {
        int test(int a, int b);
    }
}

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

関数型インタフェース紹介

みなさん、こんにちは。
今回関数型のインタフェースを紹介させていただきます。

Predicate (戻り値→ boolean)

public interface Predicate <T>

メソッド例:

import java.util.function.Predicate;

public class App {
    public static void main( String[] args ) {

        Predicate <String> pre = (String username) -> {
            return "admin".equals(username);
        };

        System.out.println(pre.test("manager"));
        System.out.println(pre.test("admin"));
      }
    }

戻り値:

false
true

Consumer (戻り値なし)

public interface Consumer<T>
import java.util.function.Consumer;

public class App {
    public static void main( String[] args ) {
        Consumer<String> con = (String message) -> {
            System.out.println("Return message: " + message);
            System.out.println("The message send succeed");
        };
        con.accept("Hello world");
        con.accept("Lambda");
      }
    }

Function

public interface Function<T,R>

例:

import java.util.function.Function;

public class App {
    public static void main( String[] args ) {

        Function<String, Integer> fun = (String gender) -> {
            return ("male".equals(gender))?0:1;
        };
        System.out.println(fun.apply("male"));
        System.out.println(fun.apply("female"));
      }
    }

結果:

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

[Java] 今(2020年)から始めるJava開発で選択できるプラットフォーム

Java界隈のウワサ話は聞いていたけれど、いざ使おうとしたらどれを使ったらいいかわからない。
サポート期間が長くて、あまりお金がかからないのがいいな。
そんなひと(自分 :smiley: :point_left:)がのための情報まとめです。

JDK一覧

受託開発業者が、客先へ納品するプロダクトの実行環境として使用できそうなJavaをまとめてみました。

名前 Javaバージョン サポートOS サポート期限 必要なもの ライセンス
Oracle JRE (Oracle Java SE) 8 Windows, Linux, macOS, Solaris 2025年3月 サブスクリプションを購入 OTN Agreement for Oracle Java SE
Oracle Java SE 11 Windows, Linux, macOS 2026年9月 サブスクリプションを購入 OTN Agreement for Oracle Java SE
Red Hat OpenJDK (Windows用) 11 Windows 2024年10月 サブスクリプションを購入 GPLv2 with CPE
AdoptOpenJDK 8 Windows, Linux, macOS, AIX, Solaris 2023年9月 (なし) GPLv2 with CPE
AdoptOpenJDK 11 Windows, Linux, macOS, AIX 2022年9月 (なし) GPLv2 with CPE
Amazon Corretto 8 Windows, Linux, macOS 2023年6月 (なし) GPLv2 with CPE
Amazon Corretto 11 Windows, Linux, macOS 2024年8月 (なし)  GPLv2 with CPE
Azul Zulu Enterprise 8 Windows, Linux, macOS 2026年3月 サブスクリプションを購入 GPLv2 with CPE
Azul Zulu Enterprise 11 Windows, Linux, macOS 2027年9月 サブスクリプションを購入 GPLv2 with CPE
Azul Zulu Community 11 Windows, Linux, macOS, Solaris 2027年9月 (なし) GPLv2 with CPE
SapMachine 11 Windows, Linux, macOS 2022年9月 (なし) GPLv2 with CPE

Oracle OpenJDK 13(サポート期限2020年3月)とOracle OpenJDK 14(サポート期限2020年9月)は省きました。

Linuxディストリビューション付属のパッケージ

Linuxディストリビューションに含まれているOpenJDKパッケージです。
たいていはこちらを利用することになると思います。

ディストリビューションとバージョン Javaバージョン サポート期限
RHEL 6, 7, 8 8 2023年6月
RHEL 7, 8 11 2024年10月
CentOS 6, 7, 8 8 RHELに準ずる?
CentOS 7, 8 11 RHELに準ずる?

クラウド環境で使用できるJDK

クラウドベンダ サービス OS Javaバージョン サポート期限
Amazon Web Services EC2 Amazon Linux 1 8 2020年6月(?)
Amazon Web Services EC2 Amazon Linux 2 8 2023年4月(?)
Amazon Web Services EC2 Amazon Linux 2 11 2023年4月(?)
Microsoft Azure VM Windows, Linux 7 2023年7月
Microsoft Azure VM Windows, Linux 8 2025年3月
Microsoft Azure VM Windows, Linux 11 2026年9月

結局どれ使えばいい?

バージョンは?

今すぐ動かすなら11一択です。

2021年9月以降に動かすなら、次のLTSである17へ向けて備えると良いと思われます。

JDKは?

条件に応じての選択になるかと思われます。

Oracleにする?Oracle以外にする?

まずOracleにするか、Oracle以外にするかを選びます。
フェラーリを買えるようなお金持ちはOracleを選ぶんだろうなぁ(ひがみ)。

Oracle以外の場合

クラウドの場合は、環境についてくるJavaを使うのがよいと思われます。

オンプレのLinuxの場合は、OSとサポートを合わせられますし、付属のパッケージを使うのが簡単かと思われます。

オンプレのWindowsで有償サポートが欲しい場合は、Red HatかZuluがよいと思われます。

リンク集

ところで

いまどきのJavaアプリケーションのデプロイは、どうするのが正解なんでしょうか?

  1. 昔ながらのjarを配布
  2. dockerイメージを作成して配布
  3. GraalVMでネイティブ化して配布

使う側からしたら、3.が一番手間なくてよさそうですが、実際にやっているひといるのかな。

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

Javaコメントカウント(VBA資材)

備忘録
2020/02.24
単体コメントカウント

Option Explicit
Const Start_Row As Integer = 3
Const Start_Col As Integer = 2
Const Start_msg As String = "ここにスタートコメント"
Const End_msg As String = "ここにエンドコメント"

Sub Delコメントカウント()
Dim i As Integer
Dim MaxRow As Long
Dim DelCount As Long
Dim checkStr As String
Dim checkPoint As Boolean
checkPoint = False

' B列の最後行を取得
MaxRow = Range("B65536").End(xlUp).Row
For i = 3 To MaxRow
    checkStr = Cells(i, Start_Col).Value
    checkStr = Trim(checkStr)

'    スタートメッセージもしくは2周目の場合
    If checkStr = Start_msg Or checkPoint Then
        checkPoint = True
        checkStr = Left(checkStr, 1)

        ' /コメント判定
        If checkStr = "/" Then
        ' *コメント反映
        ElseIf checkStr = "*" Then
        ' 空白行判定
        ElseIf checkStr = "" Then
        Else
        ' 2周目フラグに真を設定
        ' カウント数インクリメントする
            DelCount = DelCount + 1
        End If
    End If

    ' エンドメッセージの場合
    If checkStr = End_msg Then
        ' 2周目フラグに偽を設定
        checkPoint = False
        ' スタート、エンドメッセージ分カウントを引く
        DelCount = DelCount - 2

    End If

Next i

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