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

Java で文字列や値から Enum 定数を逆引きする

概要

  • Java で値から Enum 定数を取得するサンプルコード
  • サンプルコードは javac コマンドでビルドして java コマンドで実行する
  • 動作確認環境: macOS Catalina + Java 8 (AdoptOpenJDK 1.8.0_242-b08)

Enum は Java 5 (コードネーム Tiger) から使用できる

列挙型

Tiger では、Java™ プログラミング言語で列挙された型を言語的にサポートしました。列挙のもっとも簡単な形式では、C、C++、および C# の形式に似ています。

サンプルコード

package com.example.enums;

/** シンプルな列挙型を定義 */
public enum MetaName {
  FOO, BAR, BAZ;
}
package com.example.enums;

/** 値を1つ持つ列挙型を定義 */
public enum OneValueEnum {

  ONE(1), TWO(2), THREE(3);

  private final int number;

  /**
   * 値を指定して enum 定数を生成。
   */
  OneValueEnum(int number) {
    this.number = number;
  }

  /**
   * 値を返すメソッドを用意。
   */
  public int getNumber() {
    return number;
  }

  /**
   * 値に合致する enum 定数を返す。
   */
  public static OneValueEnum getByNumber(int number) {
    // 値から enum 定数を特定して返す処理
    for (OneValueEnum value : OneValueEnum.values()) {
      if (value.getNumber() == number) {
        return value;
      }
    }
    return null; // 特定できない場合
  }
}
package com.example;

import com.example.enums.MetaName;
import com.example.enums.OneValueEnum;

public class Main {

  public static void main(String[] args) {
    testMetaName();
    testOneValueEnum();
  }

  private static void testMetaName() {
    try {
      // 自動生成される valueOf メソッドを使うと、文字列から Enum 定数を取得できる
      System.out.println("***** FOO *****");
      MetaName foo = MetaName.valueOf("FOO");
      System.out.println("foo.getClass=" + foo.getClass());
      System.out.println("foo.getDeclaringClass=" + foo.getDeclaringClass());
      System.out.println("foo.toString=" + foo.toString());

      // 存在しない定数を取得しようとすると IllegalArgumentException が発生する
      System.out.println("***** bar *****");
      MetaName bar = MetaName.valueOf("bar");
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private static void testOneValueEnum() {
    try {
      // 自動生成される valueOf メソッドを使うと、文字列から Enum 定数を取得できる
      System.out.println("***** ONE *****");
      OneValueEnum one = OneValueEnum.valueOf("ONE");
      System.out.println("one.getClass=" + one.getClass());
      System.out.println("one.getDeclaringClass=" + one.getDeclaringClass());
      System.out.println("one.toString=" + one.toString());
      System.out.println("one.getNumber=" + one.getNumber());

      // 値に合致する Enum 定数を取得するためには、自前実装が必要
      System.out.println("***** 1 *****");
      OneValueEnum ichi = OneValueEnum.getByNumber(1); // 自前実装メソッドをコール
      System.out.println("ichi.getClass=" + ichi.getClass());
      System.out.println("ichi.getDeclaringClass=" + ichi.getDeclaringClass());
      System.out.println("ichi.toString=" + ichi.toString());
      System.out.println("ichi.getNumber=" + ichi.getNumber());

      // 値に合致する Enum 定数が無かったので null を返すようにしている
      // (自前実装なので任意の処理をすれば良い)
      System.out.println("***** 9 *****");
      OneValueEnum kyu = OneValueEnum.getByNumber(9);
      System.out.println("kyu=" + kyu);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

サンプルコードのビルド

ソースコードを配置。

$ tree
.
└── src
    └── com
        └── example
            ├── Main.java
            └── enums
                ├── MetaName.java
                └── OneValueEnum.java

クラスファイル出力先のディレクトリを作成。

$ mkdir dest

javac コマンドでコンパイル。

$ javac -sourcepath src src/com/example/Main.java -d dest

クラスファイルが生成されているのを確認。

$ tree
.
├── dest
│   └── com
│       └── example
│           ├── Main.class
│           └── enums
│               ├── MetaName.class
│               └── OneValueEnum.class
└── src
    └── com
        └── example
            ├── Main.java
            └── enums
                ├── MetaName.java
                └── OneValueEnum.java

サンプルの実行結果

$ java -classpath dest com.example.Main
***** FOO *****
foo.getClass=class com.example.enums.MetaName
foo.getDeclaringClass=class com.example.enums.MetaName
foo.toString=FOO
***** bar *****
java.lang.IllegalArgumentException: No enum constant com.example.enums.MetaName.bar
    at java.lang.Enum.valueOf(Enum.java:238)
    at com.example.enums.MetaName.valueOf(MetaName.java:3)
    at com.example.Main.testMetaName(Main.java:23)
    at com.example.Main.main(Main.java:9)
***** ONE *****
one.getClass=class com.example.enums.OneValueEnum
one.getDeclaringClass=class com.example.enums.OneValueEnum
one.toString=ONE
one.getNumber=1
***** 1 *****
ichi.getClass=class com.example.enums.OneValueEnum
ichi.getDeclaringClass=class com.example.enums.OneValueEnum
ichi.toString=ONE
ichi.getNumber=1
***** 9 *****
kyu=null

参考資料

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

OpenJDK 8 の java コマンドと javac コマンドのヘルプ

環境

  • macOS Catalina + AdoptOpenJDK 1.8.0_242-b08

java -help

$ java -help
使用方法: java [-options] class [args...]
           (クラスを実行する場合)
   または  java [-options] -jar jarfile [args...]
           (jarファイルを実行する場合)
optionsには次のものがあります。
    -d32      使用可能な場合は32ビットのデータ・モデルを使用する
    -d64      使用可能な場合は64ビットのデータ・モデルを使用する
    -server   "server" VMを選択する場合
                  デフォルトVMはserverです,
                  これはサーバークラスのマシンで実行しているためです。


    -cp <ディレクトリおよびzip/jarファイルのクラス検索パス>
    -classpath <ディレクトリおよびzip/jarファイルのクラス検索パス>
                  クラス・ファイルを検索するディレクトリ、
                  JARアーカイブおよびZIPアーカイブの:で区切られたリストです。
    -D<name>=<value>
                  システム・プロパティを設定する
    -verbose:[class|gc|jni]
                  詳細な出力を行う
    -version      製品バージョンを出力して終了する
    -version:<value>
                  警告: この機能は非推奨であり、詳細のリリースで
                  廃止されます。
                  指定したバージョンを実行に必須にする
    -showversion  製品バージョンを出力して続行する
    -jre-restrict-search | -no-jre-restrict-search
                  警告: この機能は非推奨であり、詳細のリリースで
                  廃止されます。
                  ユーザーのプライベートJREをバージョン検索に含める/除外する
    -? -help      このヘルプ・メッセージを出力する
    -X            非標準オプションに関するヘルプを出力する
    -ea[:<packagename>...|:<classname>]
    -enableassertions[:<packagename>...|:<classname>]
                  指定した粒度でアサーションを有効にする
    -da[:<packagename>...|:<classname>]
    -disableassertions[:<packagename>...|:<classname>]
                  指定した粒度でアサーションを無効にする
    -esa | -enablesystemassertions
                  システム・アサーションを有効にする
    -dsa | -disablesystemassertions
                  システム・アサーションを無効にする
    -agentlib:<libname>[=<options>]
                  ネイティブ・エージェント・ライブラリ<libname>をロードする。例: -agentlib:hprof
                  -agentlib:jdwp=helpと-agentlib:hprof=helpも参照
    -agentpath:<pathname>[=<options>]
                  フルパス名でネイティブ・エージェント・ライブラリをロードする
    -javaagent:<jarpath>[=<options>]
                  Javaプログラミング言語エージェントをロードする。java.lang.instrumentを参照
    -splash:<imagepath>
                  指定したイメージでスプラッシュ画面を表示する
詳細はhttp://www.oracle.com/technetwork/java/javase/documentation/index.htmlを参照してください。

javac -help

$ javac -help
使用方法: javac <options> <source files>
使用可能なオプションには次のものがあります。
  -g                         すべてのデバッグ情報を生成する
  -g:none                    デバッグ情報を生成しない
  -g:{lines,vars,source}     いくつかのデバッグ情報のみを生成する
  -nowarn                    警告を発生させない
  -verbose                   コンパイラの動作についてメッセージを出力する
  -deprecation               非推奨のAPIが使用されているソースの場所を出力する
  -classpath <path>          ユーザー・クラス・ファイルおよび注釈プロセッサを検索する位置を指定する
  -cp <path>                 ユーザー・クラス・ファイルおよび注釈プロセッサを検索する位置を指定する
  -sourcepath <path>         入力ソース・ファイルを検索する位置を指定する
  -bootclasspath <path>      ブートストラップ・クラス・パスの場所をオーバーライドする
  -extdirs <dirs>            インストール済拡張機能の場所をオーバーライドする
  -endorseddirs <dirs>       推奨規格パスの場所をオーバーライドする
  -proc:{none,only}          注釈処理やコンパイルを実行するかどうかを制御します。
  -processor <class1>[,<class2>,<class3>...] 実行する注釈プロセッサの名前。デフォルトの検出処理をバイパス
  -processorpath <path>      注釈プロセッサを検索する位置を指定する
  -parameters                メソッド・パラメータにリフレクション用のメタデータを生成します
  -d <directory>             生成されたクラス・ファイルを格納する位置を指定する
  -s <directory>             生成されたソース・ファイルを格納する場所を指定する
  -h <directory>             生成されたネイティブ・ヘッダー・ファイルを格納する場所を指定する
  -implicit:{none,class}     暗黙的に参照されるファイルについてクラス・ファイルを生成するかどうかを指定する
  -encoding <encoding>       ソース・ファイルが使用する文字エンコーディングを指定する
  -source <release>          指定されたリリースとソースの互換性を保つ
  -target <release>          特定のVMバージョン用のクラス・ファイルを生成する
  -profile <profile>         使用されているAPIが指定したプロファイルで使用可能かどうかを確認します
  -version                   バージョン情報
  -help                      標準オプションの概要を出力する
  -Akey[=value]              注釈プロセッサに渡されるオプション
  -X                         非標準オプションの概要を出力する
  -J<flag>                   <flag>を実行システムに直接渡す
  -Werror                    警告が発生した場合にコンパイルを終了する
  @<filename>                ファイルからの読取りオプションおよびファイル名

参考資料

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

Top 75+ JSP Interview Questions and Answers In 2020

In this article, I am going to provide you with a lot of JSP questions that are often asked in Interviews along with their answers. Let’s move on to the Q and A tour.

click here to read more
https://www.positronx.io/top-jsp-interview-questions-and-answers/

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

React Native Alert Example – Show Alert in React Native App

This tutorial shows you how to easily show an alert message in React Native application using the React Native Alert API. We will also learn to create custom alert using React Native Awesome Alerts module.

click here to read more
https://www.positronx.io/react-native-alert-example-show-alert-in-react-native-app/

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

HTML6 is Coming – Here is a Sneak Peek

HTML, the language of the web, is one of the most well-known web technology. HTML has been in use continually for building the internet since the time it was introduced.

click here to read more
https://www.positronx.io/html6-is-coming-here-is-a-sneak-peek/

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

動的計画法でnCrを求める

導入

こんにちは。けちょんです。
皆さんは組み合わせの総数を出力するプログラムを書けますか?
自分は競技プロで出会ったときに書けませんでした。
恥ずかしくて復習です。

方法

今回は以下の漸化式を利用して、動的計画法で導きます。

nCr = n-1Cr-1 + n-1Cr

ちなみに、この漸化式のイメージは以下です。

n個からr個を選ぶ組み合わせは、以下の和である。
(n個から一個除いて考える)
1.追加されている1個が選択されている場合
 総数はn-1個、選択する数は残りr-1個
2.追加されている1個が選択されていない場合
 総数はn-1個、選択する数は残りr個 

図で書きたかったんですが、怠けました。

ソースは以下です。

import java.util.Scanner;

public class Main {
    static Integer[][] dp;

    public static void main(String[] args) throws Exception {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int R = sc.nextInt();

        dp = new Integer[N + 1][R + 1];

        // 初期値を設定
        for (int i = 0; i <= N; i++) {
            dp[i][1] = i;
        }
        for (int i = 0; i <= R; i++) {
            dp[0][i] = 0;
        }

        // "nCr = nCn-r" を使い、計算量を節約
        if (R > N / 2) {
            R = N - R;
        }
        int res = comb(N, R);
        System.out.println(res);
    }

    private static int comb(int n, int r) {
        if (r == 0) {
            return 1;
        }
        // "nCr = n-1Cr-1 + n-1Cr"を使用
        return getInt(n - 1, r) + getInt(n - 1, r - 1);
    }

    private static int getInt(int n, int r) {
        if (dp[n][r] != null) {
            // 与えられた(n,r)に対して、dp表から値を返す。
            return dp[n][r].intValue();
        }
        // dp表に無い場合は、combに渡し、導出する
        return comb(n, r);
    }
}

ちゃんと動いていそうです。
ちなみに計算量はO(n^2)らしいです。
参考:https://www.slideshare.net/chokudai/abc021

動的計画法を使わないと計算量が莫大になるので注意です。

次回

次回は逆元を用いる方法を紹介します。
計算量はなんとO(n logp)とのこと
※p = 10^9+7 であり、出力値をpで割る前提

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

IntellijIDEA+SpringBoot+Gradle+MyBatisの設定例

属性もりもり。

IDEAはCommunityの2019.3.1
Javaのバージョンは11
SpringBootは2.2.4.RELEASE
Gradleは5.2.1
MyBatisは3.5.2
(mybatis-spring-boot-starter:2.1.0)

build.gradle
group 'com.xxx.xxx'
version '0.1-SNAPSHOT'

// Spring Boot ver:2.2.4
buildscript {
    def springBootVersion = '2.2.4.RELEASE'
    repositories { mavenCentral() }
    dependencies {
        classpath ("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

// Javaプラグイン
apply plugin:'java'
// 各種IDEの設定ファイルを出力するGradleプラグインの設定
apply plugin:'idea'
apply plugin:'eclipse'
// SpringBoot関連のプラグイン
apply plugin:'org.springframework.boot'
apply plugin:'io.spring.dependency-management'
// warファイルを出力する
apply plugin: 'war'

// Javaバージョン
def javaVersion = JavaVersion.VERSION_11
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

// 依存ライブラリ取得先リポジトリ
repositories { mavenCentral() }
// 依存ライブラリ
dependencies {
    // SpringBoot
    compile("org.postgresql:postgresql")
    compile("org.springframework.boot:spring-boot-devtools")
    compile("org.springframework.boot:spring-boot-starter-web")
    compile("org.springframework.boot:spring-boot-starter-jdbc")
    testCompile("org.springframework.boot:spring-boot-starter-test")
    // JUnit
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.2.0'
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.2.0'
    testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.2.0'
    // GSON
    compile("com.google.code.gson:gson")
    // myBatis
    compile("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.0")
    compile("ch.qos.logback:logback-classic")
    testCompile("org.mybatis.spring.boot:mybatis-spring-boot-starter-test:2.1.0")
}
test {
    // gradle buildの時にJUnit5を使うようにする
    useJUnitPlatform()
    // テストの並列実行スレッド数
    maxParallelForks = 4
    filter{
        // 表に出せないやつなので伏せる
    }
}

// リリースバージョンをgradle.propertiesから取得し設定
def applicationVersion = project.properties['release.version']
version = applicationVersion
def springBootApplicationName = 'hogehoge'
bootJar {
    archiveBaseName  = springBootApplicationName
    version = applicationVersion
}
bootWar {
    archiveBaseName  = springBootApplicationName
    version = applicationVersion
}

属性もりもりで、ご参考になれば幸いです。

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

logbackを使ってExcelで読めるCSVファイルをログ出力する

はじめに

ユーザーから、Webアプリケーションの利用状況について分析・監査したいとの要望をいただきまして、
もともとログ出力にlogbackを使っていたのでlogbackからCSVファイルを出力させることで対応することにしました。

作ったもの

  • BOM付UTF-8のCSVファイルを出力します。
    • Excelでは、BOMの付いていないUTF-8のCSVファイルを開くと文字化けします。
  • Excelから読んだときに分かりやすくするため、ファイルの初めに固定文言のヘッダを付けます。
  • ログファイルは月単位でローテーションさせます。

実現方法

Add an expression at the start of every log file using Logback - Stack Overflow を参考に作りました。

独自Appenderの作成

ファイルの先頭にBOMと固定文言を入れるAppenderを用意します。

AuthFileAppender.java
public class AuthFileAppender extends RollingFileAppender {
    @Override
    public void openFile(String fileName) throws IOException {
        super.openFile(fileName);
        File activeFile = new File(getFile());
        if (activeFile.exists() && activeFile.isFile() && activeFile.length() == 0) {
            // 出力対象が空ファイルである場合のみBOMとヘッダを出力する
            lock.lock();
            try {
                OutputStream outputStream = super.getOutputStream();
                // BOMを出力
                outputStream.write(0xef);
                outputStream.write(0xbb);
                outputStream.write(0xbf);
                // 固定文言(CSVヘッダ)を出力
                outputStream.write("操作日付,操作時刻,ユーザID,ユーザ名,操作内容,操作対象者ID・・・\n".getBytes());
                if (super.isImmediateFlush()) {
                    outputStream.flush();
                }
            } finally {
                lock.unlock();
            }
        }
    }
}
  • 月単位のログローテーションにはLogbackにあるRollingFileAppenderの仕組みをそのまま使いたかったので、RollingFileAppenderを継承したクラスとします。
  • 出力対象(activeFile)について、対象ファイルが空である場合にのみBOMと固定文言(CSVヘッダ)を出力します。
    • 上記のStack Overflowのページでは固定文言を外部設定するようにしていましたが、この実装ではハードコードしています。
  • 操作内容,操作対象者ID・・・の箇所には、実際のコードではアプリケーション固有の項目名を設定しています。

ログ出力用クラスの作成

ログインユーザ情報の取得処理など共通化したかったのと、Loggerも共通化したかったので、クラスを用意することにしました。

AuthLogger.java
@Component
public class AuthLogger {

    /** 監査ログを出力するLogger */
    public static final Logger AUTH_LOGGER = LoggerFactory.getLogger(AuthLogger.class);

    /** 個人を扱うService */
    @Autowired
    protected PersonService personService;

    /**
     * 対象の個人を指定せず、監査ログを出力します。
     *
     * @param operation 操作内容
     */
    public void log(String operation) {
        log(operation, null);
    }

    /**
     * 対象の個人を指定して、監査ログを出力します。
     *
     * @param operation 操作内容
     * @param personId 扱った対象者のID
     */
    public void log(String operation, String personId) {
        SsoLoginData ssoLoginData = SsoLoginDataUtil.getSsoLoginData(); // ログインユーザ情報を取得
        AUTH_LOGGER.trace(String.join(
            ",", // カンマ区切りで出力する
            // 操作日付、操作時刻はlogback.xmlにてレイアウトで設定するためここでは不要
            ssoLoginData.getPersonId(), // ユーザID
            ssoLoginData.getUserName(), // ユーザ名
            operation, // 操作内容
            personId // ユーザ名
            // 実際はここでpersonServiceを使った出力内容を設定する
        ));
    }
}
  • SpringBootで構築しているアプリだったので、このクラスもBean化して扱うことにしました。そのためにComponentを付けています。
    • また、PersonServiceは@AutowiredでDIしています。
  • Loggerの生成時には、AuthLogger.classをそのまま渡すことにしました。
    • よって、logback.xmlでは、nameとしてAuthLoggerの完全修飾クラス名を指定することになります。
  • 操作内容のみ指定するメソッドと、扱った対象者のIDを指定するメソッドの両方を用意しました。
    • 対象者が特定されない処理では、前者によりログを出力します。
  • CSVを出すのに、String.joinを使っています。
    • ダブルクォーテーションで囲いたい場合や、出力する値に改行が含まれる場合などは、opencsvなどCSVに特化したライブラリを利用することをお勧めします。

logback.xmlの設定追加

logback.xmlに、以下の設定を追加します。

logback.xml
  <appender name="AUTH_APPENDER" class="your.project.web.common.util.AuthFileAppender">
    <file>/your/file/path.csv</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>/your/file/path.%d{yyyy-MM}.csv</fileNamePattern>
    </rollingPolicy>
    <encoder>
      <charset>UTF-8</charset>
      <pattern>%d{yyyy-MM-dd},%d{HH:mm:ss},%msg%n</pattern>
    </encoder>
  </appender>

  <logger name="your.project.web.common.util.AuthLogger" level="TRACE" additivity="false">
    <appender-ref ref="AUTH_APPENDER" />
  </logger>
  • appender の設定について
    • 前述のAuthFileAppenderを利用します。
    • /your/file/pathの箇所は、実際は${log.auth.output}としており、アプリケーションのビルド時に稼働する環境に応じた値に置換しています。
    • 月単位でローテーションさせるため、rollingPolicyのfileNamePatternは%d{yyyy-MM}とします。
    • CSVの最初の2項目である操作日付と操作時刻はLogbackの機能により出すことにしました。このため、patternの最初に%d{yyyy-MM-dd},%d{HH:mm:ss},を設定しています。
  • loggerの設定について
    • nameは、前述のとおりAuthLoggerの完全修飾クラス名とします。
      • AuthLoggerにてLogger生成時に独自の名前を指定したならば、その名前をここでも指定すればよいです。
    • additivity="false"を指定することで。AUTH_APPENDERのみが利用されるようにしています。

個別実装へのログ出力の追加

今回は、SpringMVCのControllerに前述のAuthLoggerをDIして、直接logメソッドを実行することにしました。(ここでは実装は省略します。)
プロジェクトの規模や要件によってはAOPにより出力させる選択肢もあると思います。

まとめと感想

Logbackにはファイルの先頭に固定文言(BOMも含めて)を入れるような機能はなかったため独自のAppenderを用意することになりましたが、比較的に簡単に実現できました。
今回は出力したログファイルをユーザが直接Excelで確認する前提だったため、上記のような手段をとりました。
要件によってはログファイルはBOMなし・ヘッダなしで出力しておき、あとからBOM・ヘッダとスクリプトなどで結合するという手段も取れると思います。

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

【JSP、Servlet】新規プロジェクトを立ち上げ、接続確認をする!!

新規プロジェクト立ち上げ~接続確認

今回はJSP、Servletを使ってWebアプリケーションの制作をしていきます。
動的Webプロジェクト(JSP、Servlet)の作成方法と、接続確認について説明します。

接続確認がなぜ必要かというと…
アプリケーションの制作に本格的に入っていく前に、接続を確認し、しっかり動くか確かめるためです。

接続確認対象は以下の通りです。

接続確認対象

  • JSPからServlet呼び出し(データ渡し)
  • CSS(適応されるか確認)
  • JS(コンソールログに出力されるか確認)
  • MySQL接続確認

プロジェクト立ち上げの後に1つ1つ見ていきましょう。

Eclipseで動的Webプロジェクトを作成

まず、動的Webプロジェクトの作成をします。

ファイルメニュー→「新規」→「動的Webプロジェクト」を選択します。

image.png

「動的Webプロジェクト」が表示されない場合は、パースペクティブが「Java EE」が選択されていない可能性があります。

プロジェクト名をつけて、完了します。

image.png

プロジェクトができたのを確認出来たら動的Webプロジェクトをサーバーに追加し実行できるようにします。

image.png

※サーバービューにサーバーがない場合は、先にアプリケーションサーバー追加する必要があります。

使用可能のところから先ほど作成したプロジェクトを選択し→「追加」します。
構成済みの方に入れ、完了します。

image.png

追加した動的Webプロジェクトを有効にするため、サーバーを起動します。

サーバービューのサーバーを選択→「右クリック」→「開始」を選択します(既に起動している場合は「再開」を選択して再起動を行います)。
image.png

これで動的Webプロジェクトは完成です。次にJSP、Servletの作成に入ります。

JSP,Servletの作成

JSPとServletを作成していきます。

JSPの作成方法と詳細については以下を参照:point_down_tone2:
JSPって何?〜JSPの基礎を知ろう!!〜

Servletの作成方法
ファイルメニュー→「新規」→「サーブレット」を選択します。
image.png

Javaパッケージとクラス名を決めたら完了します。
image.png

今回のサーブレット名はMainservletにしました。

image.png

Webアノテーションについて

@WebServlet("/URLパターン")
http://<サーバー名>/<アプリケーション名>/

WebServletアノテーションの中身の文字列がURLの最後に追加される。

例(上記のプロジェクトの場合)

@WebServlet("/Mainservlet")

http://localhost:8090/ConnectSample/Mainservlet

URLパターンの中の文字列は自由に設定できる。

@WebServlet("/")

例えば上のようにアノテーションの中身を変えるとそのサーブレットがエントリーポイントになる。

接続確認

JSPからServlet呼び出し(データ渡し)

index.jspのリンクからSecondservletを呼び出す

image.png

Secondservlet

image.png

サーバーで実行
image.png
リンクを押すと…
以下のような結果が出たのでJSPからServlet呼び出し完了です。

image.png

CSS(適応されるか確認)

Webcontentの中にcssフォルダとexam.cssファイルを作成

image.png

index.jspに以下のリンクを貼り、cssファイルを読み込む
image.png

exam.css
image.png

結果h1の色を変えることができました。
image.png

JS(コンソールログに出力されるか確認)

Webcontentの中にJSフォルダとexam.jsファイルを作成

image.png

index.jspにJSを読み込むリンクを入れます。
image.png

exam.js
image.png

結果を確認。右クリック検証を選びましょう。
image.png

ConsoleのところにConsole.logに入力したものがあれば確認OK
image.png

MySQL接続確認

最後にDB接続確認です。
MySQL接続に関してはこちらの記事を参照してください:point_down_tone2:
【Java】MySQLを絶対に接続させたい!!!~JDBCドライバの取得とJARファイル位置について~

これで確認は完了です。

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

jMeterでCSVファイルをJSONにしてリクエストする方法

1万件超のCSVデータをリクエストにしてポストしたい。

いま、ビッグデータといかないまでも、大容量データの処理を作っているんですが、
大きなデータの処理は1日にテストできる回数が限られるので、

ウチでもその環境欲しいなー、jMeterで何かできないかなーと思って。
作ってみました。

jMeterにはデフォルトでCSVをJSONリクエストにする機能はない

JMeter多機能で高性能なリクエスト送信アプリですが、
デフォルトで「CSVをJSONによしなに変換してくれる機能」はないです。

しかし、「BeanPreProcesser」という送信時にScriptを走らせることで、
色んな制御ができる機能があります。

大雑把にこんな処理をします。
・HTTPリクエストに、受取変数をセット

・Bean PreProcessorを使って、JSON文字列を作り、受取変数に代入

・jMeterでHTTPリクエスト送信

JSONの送信設定をする

まずは普通にHTTPリクエストの送信設定を行います。
HTTPヘッダマネージャーを作成して、
Content-Typeをapplication/jsonにセット

image.png

リクエストボディに代入用変数を作る

まず普通に、対象サーバーやパスに、ターゲットになるアドレスを記入します。

そして、リクエストに「スクリプトの結果を格納する変数」を入れます
今回は${post_data}という名前を付けました

image.png

BeanPreProcessorを作る

そして、右クリックからBeanPreProcessorを作成してJSON文字列を作ります。

BeanPreProcessorはJavaで書くので、慣れてる人は使いやすいかと、
JSONのシリアライズ、デシリアライズもjMeterにライブラリを読み込ませれば可能らしいですヾ(。>﹏<。)ノ゙✧*。

image.png

処理内容は見ての通りです。
CSVを1行ごとにループし

request.json
"csvリスト":[{
  "ほげほげ":"CSV1カラム目"
  "なんとか":"CSV2カラム目"
  "foo":"CSV3カラム目"
}
{
  "ほげほげ":"CSV1カラム目"
  "なんとか":"CSV2カラム目"
  "foo":"CSV3カラム目"
}]
...

みたいなJSONを作ります!

BeanShellPreProcessor.java
//▼-----------BeanPreProcessor処理開始-------------▼
log.info("BeanShell PreProcessor start");


StringBuilder requestBody = new StringBuilder();
// JSONのスタートを作る
requestBody.append("{\r\n");
// 改行もちゃんと反映されますヾ(。>﹏<。)ノ゙✧*。
// リストオブジェクトの作成開始 

requestBody.append("\"requestCSV\":[\r\n");

// CSVファイルを読み込む
BufferedReader reader = new BufferedReader(new FileReader(new File("C:\\csvFileForBatche\\06YAMAGAcopied.CSV")));
String line;
// 1行ごとにループ

boolean secondLoopFlg = false;
while ((line = reader.readLine()) != null) {
    // 読み込んだCSVをカンマで分割
    String[] params = line.split(",");

        if(secondLoopFlg){
            requestBody.append(",\r\n");
        }
        requestBody.append("{\r\n");
        // 項目ごとにCSVパラメーターを作ります
            requestBody.append("\"countryPublicOrgCd\":\"").append(params[0]).append("\",\r\n");
        requestBody.append("\"zipcodePare\":").append(params[1]).append(",\r\n");
        requestBody.append("\"zipcode\":").append(params[2]).append(",\r\n");
        requestBody.append("\"prefNameKana\":").append(params[3]).append(",\r\n");
        requestBody.append("\"cityNameKana\":").append(params[4]).append(",\r\n");
        requestBody.append("\"streetNameKana\":").append(params[5]).append(",\r\n");

        requestBody.append("\"prefNameKanji\":").append(params[6]).append(",\r\n");
        requestBody.append("\"cityNameKanji\":").append(params[7]).append(",\r\n");

        requestBody.append("\"streetNameKanji\":").append(params[8]).append(",\r\n");
        requestBody.append("\"var1\": \"").append(params[9]).append("\",\r\n");
        requestBody.append("\"var2\": \"").append(params[10]).append("\",\r\n");
        requestBody.append("\"var3\": \"").append(params[11]).append("\",\r\n");
        requestBody.append("\"var4\": \"").append(params[12]).append("\",\r\n");
        requestBody.append("\"var5\": \"").append(params[13]).append("\",\r\n");
        requestBody.append("\"var6\": \"").append(params[14]).append("\"\r\n");

        // オブジェクトを閉じる
        requestBody.append("}\r\n");

        secondLoopFlg = true;
}

// JSONオブジェクトを閉じる
requestBody.append("]\r\n");

requestBody.append("}\r\n");
// ファイルをクローズ
reader.close();

// リクエストが用意する変数に代入する
vars.put("post_data", requestBody.toString());

// ▲-----------BeanPreProcessor処理開始-------------▲
log.info("BeanShell PreProcessor start");

れっつ実行ヾ(。>﹏<。)ノ゙✧*。

この状態で実際にリクエストを送信してみます
image.png

3万件にCSVをリクエストに送信できましたb

参考
Sending Complete JSON Data from the CSV file in one request in JMeter
https://stackoverflow.com/questions/32628934/sending-complete-json-data-from-the-csv-file-in-one-request-in-jmeter

receiving json and deserializing as List of object at spring mvc controller
https://stackoverflow.com/questions/23012841/receiving-json-and-deserializing-as-list-of-object-at-spring-mvc-controller

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

FileIOのDynamic DestinationのDestinationTはequals/hashCodeが必要だよ

細かすぎて伝わらないApache Beam/Dataflow選手権#1です。

FileIOって?

Apache Beamのファイル一般を扱うIOクラスです。
ドキュメントによるとS3/GCS/HDFS/ローカルのファイルを、扱う事が出来るようです。

Dynamic Destinationって?

PCollectionの中身によって出力先を変える機能です。
具体的な設定はFileIO.Writeで定義されており、

  • byで、入力(UserT)をグループ(DestinationT)に分類するクラスを指定
  • viaで、入力(UserT)を出力(OutputT)に変換するクラスを指定
  • withNamingで、グループ(DestinationT)からファイル名を決めるクラスを指定

の3つが、Dynamic Destinationの主な設定です。

DestinationTって

入力(UserT)分類し、グループ化するためのラベルとして使われるクラス(ジェネリクス)です。
境界(extends)が定義されてはいないので、特に必要なメソッドや親クラスは指定されていません。

って思うじゃん‥

詰まった

やろうとしていたこと

  • 数十万件のPub/Subメッセージを、Dynamic Destinationで仕分け(5種類くらい)してGCSに書き込みたい
  • Dynamic Destinationなしでは、楽勝に処理出来ていた

起きたこと

  • ファイルを書き出す段階で、WorkerでOut Of Memory(OOM)が起きた
    • (Dynamic Destination無しと、同じインスタンス種類の場合)
  • ごく一部しか結果がファイルに書き込まれなかった
  • ワーカーのメモリを巨大(64GB)にすると、ゆっくり書き出される
    • オートスケールにしても台数は増えない

原因

DestinationTの実装に

  • equals
  • hashCode

を(正しく)実装していなかったのが原因。

なぜ必要か

端的にはDestinationTがHashMapで使われているためです。

細かい流れが気になる人向けに:

Dynamic Destinationでは、以下の流れで書き込みを行います。

  1. 入力(UserT)をファイルに割り振るために、番号を付ける(ここらへん
  2. 番号でGroupByKey(ここらへん)
  3. 一時ファイルに書き出す(ここらへん
  4. 一時ファイルを改名(ここらへん

下のような理由で、DestinationTがHashMapに使われています:

  • 入力を連番に割り振る時(1)に、ハッシュが衝突する可能性がある
  • 衝突すると、異なるDestinationがGroupByKeyでまとめられる
  • そのため、一時ファイル書き出し(3)の部分でもDestinationTを計算。異なるDestinationTを異なるファイルに書き出すようにしている
    • この時、DestinationTと書き出し先を管理するために、DestinationTをHashMapに突っ込んでいる

このため、DestinationTがhashCode/equalsを(正しく)実装していないと、

  • 入力毎に違う書き出し先とみなされる
    • つまり1ファイル1行
  • 超絶細かい単位で書き出すので時間がかかる
  • 出力が終わるまで、入力データが保持するので、メモリ使用量が多くなる

のではないかと推測しています。

今回学んだこと

  • equals/hashCodeを実装しないでHashMapを使うと死ぬ
    • Effective Java大事
  • DataflowでもDumpが取れる
    • Googleの人がわかりやすい資料を書いてくれています
    • (がダンプしても分かるとは限らない)
  • Beamのファイル書き出しは、ちょっと複雑な処理をしている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SpringBootでEhCashe2.xでキャッシュを使う

背景

Spring Bootを利用していて、キャッシュを実装したくなったので試しました。

実行環境

  • Spring Boot v2.2.4.RELEASE

Spring Bootでのキャッシュ

Spring Bootではいくつかのキャッシュが利用できます。
https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-caching.html

うち、上から眺めていってEhCache2.xは導入が簡単そうに見えます。
クラスパスにehcache.xml があれば利用されるとのことなので、導入が簡単そうに見えるので、試してみます。

EhCache 2.x

実装

キャッシュを利用するメソッドの呼び出し元

今回は@SpringBootApplication のアノテーションをつけたクラスから呼び出してみます。
@EnableCaching のアノテーションをつけます

CacheApplication.java
@SpringBootApplication
@EnableCaching
public class CacheApplication implements CommandLineRunner {

    @Autowired
    Cache cache;

    private final static Logger logger = LoggerFactory.getLogger(CacheApplication.class);
    private final static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");

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

    public void run(String[] args){

        this.getValue("キャッシュされる");
        this.getValue("キャッシュされる");//キャッシュされる
        this.getValue("引数が違うのでキャッシュされない");//引数が違うのでキャッシュされない
        this.getValue("キャッシュされる");//引数違うのを挟んでキャッシュされる
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.getValue("キャッシュされる");//引数が同じだけど生存時間を過ぎているのでキャッシュされない
    }

    public void getValue(String key){
        logger.info(key + " Start at: " + LocalDateTime.now().format(dateTimeFormatter));
        String ret = cache.getFromCache(key); //キャッシュが有効なメソッド
        logger.info(ret + " End at: " + LocalDateTime.now().format(dateTimeFormatter));
    }
}

キャッシュを利用するクラス

キャッシュを有効にしたいメソッドに@Cacheableをつけます

Cache.java
@Component
public class Cache {

    @Cacheable("getCache") //このメソッドについてキャッシュを有効にする
    public String getFromCache(String key){
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "End getFromCache:" + key;
    }
}

pom.xml

pom.xmlにspring-boot-starter-cacheehcacheの2系を追加します。

pom.xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
  <groupId>net.sf.ehcache</groupId>
  <artifactId>ehcache</artifactId>
  <version>2.10.3</version>
</dependency>

ehcache.xml

ehcache.xmlをsrc/main/resources/ 以下に作成します。
timeToLiveSecondsを5として、キャッシュの生存時間は5秒の設定にしておきます。

ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <diskStore path="java.io.tmpdir"/>
    <cache
            name="getCache"
            timeToLiveSeconds="5"
            maxEntriesLocalHeap="0">
    </cache>
</ehcache>

application.properties

application.propertiesには、ehcacheを利用するよう以下の設定を追加します。
利用できるキャッシュがehcacheのみならばここは不要です。

spring.cache.type=ehcache

実行

実行します。
実行結果はこうなりました。

2020-02-29 21:29:48 - キャッシュされる Start at: 21:29:48
2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51
2020-02-29 21:29:51 - キャッシュされる Start at: 21:29:51
2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51
2020-02-29 21:29:51 - 引数が違うのでキャッシュされない Start at: 21:29:51
2020-02-29 21:29:54 - End getFromCache:引数が違うのでキャッシュされない End at: 21:29:54
2020-02-29 21:29:54 - キャッシュされる Start at: 21:29:54
2020-02-29 21:29:54 - End getFromCache:キャッシュされる End at: 21:29:54
2020-02-29 21:29:57 - キャッシュされる Start at: 21:29:57
2020-02-29 21:30:00 - End getFromCache:キャッシュされる End at: 21:30:00

以下で結果を見ていきます。
一回目は3秒かかっているのに対し、二回目は一瞬なのでキャッシュが効いているようです。

2020-02-29 21:29:48 - キャッシュされる Start at: 21:29:48
2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51
2020-02-29 21:29:51 - キャッシュされる Start at: 21:29:51
2020-02-29 21:29:51 - End getFromCache:キャッシュされる End at: 21:29:51

違う引数を渡した際はキャッシュされないので、また3秒かかっています。

2020-02-29 21:29:51 - 引数が違うのでキャッシュされない Start at: 21:29:51
2020-02-29 21:29:54 - End getFromCache:引数が違うのでキャッシュされない End at: 21:29:54

以下については生存時間の5秒を過ぎているので、キャッシュがされず、3秒かかっています。

2020-02-29 21:29:57 - キャッシュされる Start at: 21:29:57
2020-02-29 21:30:00 - End getFromCache:キャッシュされる End at: 21:30:00

意図通りのキャッシュの挙動となりました。

所感

SpringBootとEhCache2.Xは、アノテーションと設定ファイルだけでキャッシュが実装できるので手っ取り早いと思いました。
ただ、EhCache3系がすでに利用できるのをその後知りまして、あえてEhCache2系を使うことはあまりないかもしれません。

caffeineを試したら同じようにキャッシュが実装できたのでそちらも使えそうです。
以下記事が参考になりました。
https://qiita.com/yut_arrows/items/4b664acdfa852c0bd6cd

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