20200927のJavaに関する記事は16件です。

LINEの通知から色々な情報をスクレイピングする

はじめに

2011年6月23日にLINEサービスが開始され今年で9年目になりますが、国内の月間アクティブユーザー数が約8,700万人ということで、身近でもLINEを使っているという方はほとんどだと思います。
普段のやり取りは主にLINEで行うと思いますが、頻繁に使うLINEからデータが取れれば色々活用できると思い、どうにかやりとりの情報を取得できないかと考えました。

私はこの方法を使って、LINEの通知音・着信音を個別に鳴り分けるようにする「ピックアップ通知音」というアプリをGoogle Playに公開しています!
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409628/e48b6416-da86-2db1-0c9a-0c1a5c4be66e.png
https://play.google.com/store/apps/details?id=rabbitp.sns.notifisort
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409628/a4ce1b87-24e8-d6ae-c0ac-1e4ed07cf6f4.png
Google Play および Google Play ロゴは、Google LLC の商標です。

やり方をざっくり説明すると...

LINEでメッセージが届く

通知が表示される

通知内容を、Androidの「通知へのアクセス」機能を使って取得する

必要な情報のみスクレイピングする

こんな感じです。
詳しいやり方はこの後紹介します。
(前置きを飛ばしたい方はここをクリック)

スクレイピングの前に...「通知を取得する方法」

参考になるプロジェクトがあるのでそれを使うと便利です。
こちら→ https://github.com/oggata/NotificationListenerServiceDemo
ダウンロード後にAndroid Studioを開くと、次のようになります。
000261.png
2015年製のプロジェクトなので、Gradleバージョンが古いため警告が表示されます。
ファイルメニュー→プロジェクト構造...を開き、Android Gradle プラグインバージョンとGradle バージョンを任意のものに変更してOKをクリックしてください。
000262.png
(私はプラグイン4.0.0・Gradle6.1.1を使いました。)

OKをクリックした後、このような画面になったら、「repositories」の部分2箇所を次のように変更します。
000263.png

build.gradle
buildscript {
    repositories {
        google()
        jcenter()
    }

    ...省略

allprojects {
    repositories {
        google()
        jcenter()
        maven {
            url "https://jitpack.io"
        }
    }
}

修正が完了したら、左上の「再試行」をクリックして、同期が終わるまで待ちます。

すると再度エラーが出るので、build.gradleの「targetSdkVersion 21」を削除し、リンクになっている「Remove minSdkVersion and sync project」をクリックし、次の記述を削除します。
000264.png

AndroidManifest.xml
<uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="18" />

...他の部分は省略

削除したら、再度build.gradleを開いて右上の「今すぐ同期」をクリックします。
これでエラーがすべて消えるはずです。

このプロジェクトは、「通知が来たら、その生データをオーバーレイで画面の上から流れるように表示する」というものですが、確認に使うには少し不便なので改良します。

「通知へのアクセス」の設定画面を開くボタンを追加する

activity_main.xmlに適当にボタンを配置し、次のプログラムを追加します。

MainActivity.java
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //通知アクセス許可画面
                Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
                startActivity(intent);
            }
        });

オーバーレイをやめて、EditTextに表示するようにする

コピペできると良いと思ったので、今回はTextViewではなくEditTextを使います。
activity_main.xmlの全画面にEditTextを配置し、次のプログラムを追加します。

MainActivity.java
public class MainActivity extends Activity {
    EditText editText;
    String log = "";

...省略

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editText = (EditText) findViewById(R.id.editText);

...省略

    class NotificationReceiver extends BroadcastReceiver{
        @Override
        public void onReceive(Context context, Intent intent) {
            String msg = intent.getStringExtra("notification_msg") + "\n";

            final DateFormat df = new SimpleDateFormat("MM/dd HH:mm:ss");
            final Date date = new Date(System.currentTimeMillis());
            log=df.format(date)+"\n"+msg+"----------------\n"+log;

            editText.setText(log);
        }
    }
}

新しい通知の方が上になるように表示されます。

リセットボタンを追加する

ログと表示をリセットするためのボタンを付けます。
activity_main.xmlに適当にボタンを配置し、次のプログラムを追加します。

MainActivity.java
        Button button2 = (Button) findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //リセット
                log="";
                editText.setText("");
            }
        });

通知の詳細情報を取得する

「sbn.getNotification().extras」を使用すると、実際通知には表示されない情報も取得することができます。
送信者名やメッセージの全文などは、すべてこの中から取得することができます。

NLService.java
...省略

            i.putExtra("notification_msg", ""
                    + "ID :" + sbn.getId() + "\n"
                    + "Txt :" + sbn.getNotification().tickerText + "\n"
                    + "Pkg :" + sbn.getPackageName() + "\n"
                    + "Tim :" + sbn.getPostTime() + "\n"
                    + "extras :" + sbn.getNotification().extras+"\n"
                    + "\n");

...省略

アプリの実行画面

レイアウト等は変更している箇所があります。
https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/409628/6d2f3141-695c-2efc-b2e8-9764cc7f5ad2.png

スクレイピング方法の目次

分かりやすいように、スクレイピングしたい情報ごとにまとめています。

【 判別 】
LINEの通知かどうか
メッセージか無料通話か

【 メッセージの場合 】
送信者名
対象グループ名
メッセージ内容
送信されたLINEスタンプのURL
送信者のプロフィール画像
チャットID
既読にした or 通知が削除された場合

【 無料通話の場合 】
発信開始
着信
不在着信
通話開始
通話終了

countStringInString関数について

プログラムの一部に、文字の出現回数を数えるcountStringInString関数が使用されています。

    //文字出現回数取得
    public static int countStringInString(String target, String searchWord) {
        //return (target.length() - target.replaceAll(searchWord, "").length()) / searchWord.length();

        Pattern p = Pattern.compile(searchWord);
        Matcher m = p.matcher(target);
        String result = m.replaceAll("");

        int out = target.length() - result.length();
        return out;
    }

判別

LINEの通知かどうか

記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        if(sbn.getPackageName().equals("jp.naver.line.android")) {
            //ここにLINEの通知を受信した際にしたい処理を記述します

        }

メッセージか無料通話か

記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        if (String.valueOf(sbn.getId()).indexOf("880002") != -1 || String.valueOf(sbn.getId()).equals("110000")) {
             //不在着信 or 無料通話系の通知ID の場合→無料通話

        } else {
             //違った場合→メッセージ

        }

記述場所:「NLService.java」の「onNotificationRemoved」内

NLService.java
        if (String.valueOf(sbn.getId()).equals("110000")) {
             //無料通話が拒否されたり終話ボタンが押されたりして終了した場合→無料通話

        }

メッセージの場合

送信者名

送信した相手の名前を取得できます。
個別とグループ、両方の場合で取得可能です。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        //整形
        String ex=String.valueOf(sbn.getNotification().extras);
        ex=ex.substring(8);  //先頭から8文字削除「Bundle[{」
        String[] extars = ex.split(",");
        int dir = countStringInString(ex, ",");  //「,」の数

        //送信者名を抜き出す
        String Name="";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf("android.title=")==0){
                //先頭に「android.title=」がきた→送信者名
                Name = extars[a].substring(14);  //先頭から文字削除「android.title=」
                break;
            }
        }
        if(Name.indexOf(" - ")!=-1){
            //Android5・6系ではNameが「[送信者名] - [グループ名]」になる
            Name=Name.split(" - ")[0];
        }
        //Name:送信者名

対象グループ名

メッセージが送信されたグループの名前を取得できます。
グループではなく個別だった場合はnullが返ります。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        //整形
        String ex=String.valueOf(sbn.getNotification().extras);
        ex=ex.substring(8);  //先頭から8文字削除「Bundle[{」
        String[] extars = ex.split(",");
        int dir = countStringInString(ex, ",");  //「,」の数

        //グループ名を抜き出す
        String GroupName="";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf("android.title=")==0){
                //先頭に「android.title=」がきた→送信者名
                GroupName = extars[a].substring(14);  //先頭から文字削除「android.title=」
                break;
            }
        }
        if(GroupName.indexOf(" - ")!=-1){
            //Android5・6系ではNameが「[送信者名] - [グループ名]」になる
            GroupName=Name.split(" - ")[1];
        }else {
            GroupName=sbn.getNotification().extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
            if(GroupName!=null){
                Name=Name.substring(GroupName.length()+2);  //先頭に「: 」が付与されているから削除する
            }
        }
        //GroupName:グループ名

メッセージ内容

送信されたメッセージを取得できます。
通知では「...」と表示されて省略される場合でも、全文取得可能です。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        //内容を抜き出す
        String Text = sbn.getNotification().extras.getString(Notification.EXTRA_TEXT);
        //Text:内容

送信されたLINEスタンプのURL

https://stickershop.line-scdn.net/products/」から始まり「.png」で終わるURLが取得できます。
このURLを元にHTTP GETすればスタンプの画像を表示することも可能です。
(HTTP GETして画像を表示する方法:https://akira-watson.com/android/httpurlconnection-get.html)
スタンプではなかった場合はnullが返ります。
ちなみに、画像サイズはスタンプによりバラバラですが大体120px以上はあります。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        //スタンプの画像URLを抜き出す
        String StampURL="null";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf(" line.sticker.url=")==0){
                //先頭に「line.sticker.url=」がきた→スタンプの画像URL
                StampURL = extars[a].substring(18);  //先頭から18文字削除「line.sticker.url=」
                break;
            }
        }
        //StampURL:スタンプの画像URL

送信者のプロフィール画像

Bitmap型で取得できるので、ImageViewに表示したりPNGとして保存したりできます。
画像サイズは108px × 108pxです。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        Bitmap LargeIcon = drawableToBitmap(sbn.getNotification().getLargeIcon().loadDrawable(NLService.this));

...省略

    //Drawable型をBitmap型に変換
    public static Bitmap drawableToBitmap (Drawable drawable) {
        Bitmap bitmap = null;

        if (drawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
            if(bitmapDrawable.getBitmap() != null) {
                return bitmapDrawable.getBitmap();
            }
        }

        if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
            bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
        } else {
            bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(bitmap);
        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        drawable.draw(canvas);
        return bitmap;
    }

チャットID

友達やグループごとに決まっている識別子です。
これを使えば、同じ名前の友達やグループがあったとしても判別することができます。
恐らく、LINEの「トークショートカットを作成」機能はこのIDを使用したURLスキームだと思われますが、検索しても出てきませんでした...
※友達登録時に使うIDではありません。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        //整形
        String ex=String.valueOf(sbn.getNotification().extras);
        ex=ex.substring(8);  //先頭から8文字削除「Bundle[{」
        String[] extars = ex.split(",");
        int dir = countStringInString(ex, ",");  //「,」の数

        //チャットIDを抜き出す
        String ChatID="";
        for (int a = 0; a < dir; a++){
            if(extars[a].indexOf("line.chat.id=")==0){
                //先頭に「line.chat.id=」がきた→チャットID
                ChatID = extars[a].substring(13);  //先頭から13文字削除「line.chat.id=」
                break;
            }
        }
        //ChatID:チャットID

既読にした or 通知が削除された場合

LINEのトーク画面を開いて既読にしたか、メッセージ通知を削除した場合に呼ばれます。
記述場所:「NLService.java」の「onNotificationRemoved」内

NLService.java
        if (String.valueOf(sbn.getId()).indexOf("880000") != -1) {  //15880000or16880000のどちらか?
            //既読にした or 通知が削除された場合

        }

無料通話の場合

次のような流れで処理が呼ばれます。

  • 電話をかけて通話せずに終了したとき:発信開始→通話終了
  • 電話をかけて通話して通話が終わったとき:発信開始→通話開始→通話終了
  • 着信があり相手が切ったとき:着信→通話終了→不在着信
  • 着信があり自分が切ったとき:着信→通話終了
  • 着信があったが反応せず「応答なし」になったとき:(着信があり相手が切ったときと同じ)
  • 着信があり通話して終話ボタンを押したとき:着信→通話開始→通話終了

発信開始

自分から電話をかけたときに呼ばれます。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        if (String.valueOf(sbn.getId()).indexOf("110000") != -1 && Text.indexOf("発信中") != -1) {  //15880002or16880002のどちらか?
            //発信開始

        }

着信

電話がかかってきたときに呼ばれます。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        if (String.valueOf(sbn.getId()).indexOf("110000") != -1 && Text.indexOf("着信中") != -1) {  //15880002or16880002のどちらか?
            //着信

        }

不在着信

着信があり相手が切った(拒否ボタンを押した or 応答がなかった)場合に呼ばれます。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        if (String.valueOf(sbn.getId()).indexOf("880002") != -1 && Name.equals("LINE不在着信")) {  //15880002or16880002のどちらか?
            //不在着信

        }

通話開始

通話が開始されると呼ばれます。
記述場所:「NLService.java」の「onNotificationPosted」内

NLService.java
        if (String.valueOf(sbn.getId()).indexOf("110000") != -1 && Text.indexOf("通話中") != -1) {  //15880002or16880002のどちらか?
            //通話開始

        }

通話終了

何らかの方法で通話が終了したときに必ず呼ばれます。
記述場所:「NLService.java」の「onNotificationRemoved」内

NLService.java
        if (String.valueOf(sbn.getId()).equals("110000")) {
            //通話終了

        }

注意

これは実際にLINEの通知を受信してみて、どうやったらうまくスクレイピングできるかを検証して分かったやり方です。
LINEの仕様変更によっては、今後使えなくなる可能性が十分ありますのでご注意ください。
(実際に2018年に大幅な仕様変更があり、2020年にも一部変更されました。)

また、Android 7.0未満(?)では、試しているうちに「onNotificationPosted」が呼ばれなくなる不具合があるようです。
検証に使用する場合は、Android 7.0以上を使ってください。
また、minSdkVersionを24(Android 7.0)以上に設定しておくことをおすすめします。

参考サイト

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

インタフェースと抽象クラスの使い分け

インタフェースと抽象クラスをどう使い分ける?

初心者が、インタフェースと抽象クラスがどんなものかを理解して、次に疑問に思うのは
それぞれ、どう使い分ければ良いの?
ということではないかと思います。

インタフェースは使用者のため、抽象クラスは実装者のため

  • インタフェースは呼び出す人のためにメソッドのAPIを定義する
  • 抽象クラスは子クラスを実装する人のために一部を実装する

端的に言えば、これだけです。

具体的には

ポリモーフィズムを使用して呼び出してもらいたいクラスは、インタフェースを実装しましょう。

使う側は、抽象クラス型の変数は宣言するべきではないです。
インタフェース型で変数を宣言しましょう。

AbstractFooBar = new FooBar(); // NG
IFooBar = new FooBar(); // OK

抽象クラスを仮引数の型とするメソッドも定義するべきではありません。
インタフェース型を指定しましょう。

void fooMethod(AbstractFooBar fooBar); // NG
void fooMethod(IFooBar fooBar); // OK

補足 抽象クラスでポリモーフィズムかけたい場合は?

では、抽象クラスの子クラス群に対してポリモーフィズム使いたい場合はどうするの?
という疑問が生まれそうなので。
その場合は、以下の感じで。

抽象クラス-インタフェース.png

こうしておけば、使用者は常にIFooBarインタフェース経由で呼び出せば良いですし、
IFooBarインタフェースを実装する人は、
AbstractFooBarを拡張するか、直接IFooBarを実装するかを選べて、
皆が幸せになります。

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

【Springframework】プロキシサーバー利用時のRestTemplateによるHTTPリクエストのエラー対応

概要

プロキシサーバーを利用してインターネットにアクセスするネットワーク環境下でSpringのRestTemplateを利用して、外部のAPIにリクエストを送信しようとしたところエラーが発生しました。

普段開発する環境ではプロキシサーバーを利用しておらず調査に時間がかかったので、今回対応した方法をメモしておきます。

発生した事象

プロキシサーバーを利用してインターネットにアクセスするネットワーク環境下でRestTemplate.exchangeを利用して外部APIを利用してGETリクエストをしたところ下記のエラーが発生。

ただし、同じAPIをブラウザ(Chrome)でアクセスした際にはレスポンスがきちんと返ってきている状況でした。

エラーメッセージ

org.springframework.web.client.ResourceAccessException: I/O error on GET request for "{targetURL}":
 Connection refused: connect;
 nested exception is java.net.ConnectException: Connection refused: connect

イメージ図(ネットワーク環境)

proxy.png

原因

ブラウザはホストサーバーのプロキシ設定を利用していたため、インターネットアクセス時にはプロキシサーバーを経由してアクセスできていましたが、SpringBootアプリケーション側ではリクエスト時にプロキシサーバーを経由できていませんでした。

SpringBootアプリケーション側では明示的に設定ファイルや実行時引数でプロキシ設定を行わないとプロキシサーバーをいけないようです。

SpringBootアプリケーションでプロキシ設定をする方法

1. System.setPropertyの利用

// このケースではプロキシサーバーとして webcache.example.com:8080 を利用
// httpsの場合はhttps.proxyHost, https.proxyPortを利用します

System.setProperty("http.proxyHost", "webcache.example.com");
System.setProperty("http.proxyPort", "8080");

2. JVM起動時の引数として指定

Springアプリケーションの実行時引数として指定します

java ./application.jar -Dhttp.proxyHost=webcache.example.com -Dhttp.proxyPort=8080

3. RestTemplateのインスタンス作成時factoryを利用して設定

1,2の方法だとシステム全体に影響を与えるので、影響範囲を限定したい場合にはこちらの方法を利用します。

//ここでプロキシ設定を行う
Proxy proxy = new Proxy(Type.HTTP, new InetSocketAddress(PROXY_SERVER_HOST, PROXY_SERVER_PORT));

// RestTermplate用にプロキシ設定をするClientHttpRequestFactoryを用意する
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setProxy(proxy);

RestTemplate restTemplate = new RestTemplate(requestFactory);
// こちらのRestTemplateを利用してHTTP(S)通信を行う

参考リンク

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

Tomcat を使った Java Servlet 開発

はじめに

  • Tomcat を使って Java Servlet を開発する方法についてまとめました。

概要

  • Java Servlet(サーブレット)とは、Java で実装された WEB アプリケーションにおいて、動的 WEB ページを生成するプログラムです。
    WEB アプリケーションは、サーバ・クライアントシステムになっており、以下の図のように、クライアントがサーバにリクエストを送信し、サーバがクライアントにレスポンスを返して動作します。

  • Java Servlet(サーブレット)は単体で動作するのではなく、サーブレットコンテナと呼ばれるソフトウェアと一緒に動作します。Tomcat とは、このサーブレットコンテナに該当します。
    WEB ブラウザから WEB サーバにリクエストを送信すると、Tomcat がこのリクエストを受信します。サーバブレットは、リクエストの種類に応じた動的 WEB ページを生成し、Tomcat は、この動的 WEB ページをWEB ブラウザにレスポンスとして送信します。WEB ブラウザには、この動的 WEB ページをが表示されます。

開発環境

  • Windows 10(64bit)
  • OpenJDK 11
  • Eclipse IDE for Java Developers(Version: 2020-06)
  • Tomcat 9

開発環境構築①:OpenJDK 11 インストール

Java Servlet、Eclipse、Tomcat を動かすためには JRE(Java Runtime Environment)が必要になるので、OpenJDK をインストールします。
JRE をインストールしていないと、Eclipse 起動時にエラーになるので、Eclipse のインストール前に行う必要があります。

  1. 以下の OpenJDK の公式サイトに移動し、「Download」項目内の jdk.java.net/14 のリンクをクリックします。
    jdk.java.net/14 のリンクは、OpenJDK がバージョンアップされる度に変わります。)
    https://openjdk.java.net/

  2. 左側のリンク一覧の中の「Java SE 11」をクリックします。
    (最新版の OpenJDK をダウンロードしたい場合は、このサイトから OpenJDK をダウンロードできます。)
    https://jdk.java.net/14/

  3. 「Windows/x64 Java Development Kit」をクリックします。
    https://jdk.java.net/java-se-ri/11

  4. 「openjdk-11+28_windows-x64_bin.zip」がダウンロードされます。

  5. ダウンロードが完了したら、任意のフォルダに解凍し、「jdk-11\bin」の絶対パスをシステム環境変数に追加します。
    (ここでは、C:\openjdk に解凍し、「C:\openjdk\jdk-11\bin」をシステム環境変数に追加しました。)

  6. コマンドプロンプトを起動して、Java のバージョン確認を行う。(openjdk 11 であれば OK です。)

    >java --version
    openjdk 11 2018-09-25
    OpenJDK Runtime Environment 18.9 (build 11+28)
    OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
    

開発環境構築②:Eclipse インストール

OpneJDK のインストールが完了したら、Eclipse をダウンロードして起動し、Tomcaat Plagin をインストールします。

  1. Eclipse のパッケージインストーラのダウンロードサイトに移動し、「Eclipse IDE for Java Developers」内の「Windows 64-bit」をクリックします。
    https://www.eclipse.org/downloads/packages/

  2. ダウンロードページに移動したら、「Download」ボタンをクリックします。

  3. eclipse-java-2020-06-R-win32-x86_64.zip がダウンロードされます。

  4. ダウンロード完了したら、任意のフォルダに解凍し、「eclipse.exe」をダブルクリックして、Eclipse を起動します。
    (起動時に、workspaceフォルダのパスの入力が必要ですが、デフォルト設定のままにします。)

  5. Eclipse のメインメニュー「Help」>「Eclipse Marketplace」を実行して、「Eclipse マーケットプレース」ダイアログを起動します。

  6. 「Search」欄に、「Tomcat」と入力して、「Go」ボタンを押します。

  7. 「Eclipse Tomcat Plugin 9.1.4」を選択して、「Install」ボタンを押します。

  8. 「I accept the terms of the license agreement」を選択して「Finish」ボタンを押す。

  9. Eclipse を再起動すると、Eclipseのメインメニューのアイコンに Tomcat のアイコンがメニューに表示されます。

開発環境構築③:TOMCAT インストール

TOMCAT をインストールします。

  1. 下記のサイトにアクセスし、左側の「Download」項目の「Tomcat9」をクリックします。
    http://tomcat.apache.org/download-80.cgi

  2. 「Binary Distributions」内の「Core」下の「zip」をクリックします。
    https://tomcat.apache.org/download-90.cgi

  3. 「apache-tomcat-9.0.37.zip」がダウンロードされます。

  4. ダウンロードが完了したら任意のフォルダに解凍します。ここでは、以下のフォルダに解凍します。
    C:\apache\apache-tomcat-9.0.37

  5. 以下のシステム環境変数を追加します。

    ・CATALINA_HOME    :TOMCAT のインストールフォルダのパス(【例】C:\apache\apache-tomcat-9.0.37)
    ・JAVA_HOME        :Java のインストールフォルダのパス(【例】C:\openjdk\jdk-11)
    

Eclipse 初期設定

プロジェクト作成の前に Eclipse の初期設定を行います。最初に一度設定しておけば次回からは設定不要です。

  1. メインメニュー「Window」>「Preferences」を実行して、「Preferences」ダイアログを起動します。

  2. 左側のツリーから「General」>「Appearance」>「Colors and Fonts」を選択します。

  3. 「Java Editor Text-Font」を選択し、「Edit」ボタンを押して、「フォント」ダイアログを起動します。

  4. Eclipse 上での文字化け(コメント「//」の直後に日本語を記述すると文字化けする)対策のため、フォントを MS ゴシックに変更し、「Apply」ボタンを押します。

  5. 左側のツリーから「General」>「Workspace」を選択します。

  6. 「Text file encoding」で「Other」をチェックし、「UTF-8」を選択します。
    (デプロイする環境が Linux であることを想定して、Linux 上で動作するように UTF-8 に設定しておきます。)

  7. 「New text file line delimiter」で「その他」をチェックし、「Unix」を選択して、「Apply」ボタンを押します。

  8. 左側のツリーから「Tomcat」を選択し、「Tomcat バージョン」の「9」をチェックします。

  9. 「Tomcat ホーム」に Tomcat のフォルダを設定します。(【例】C:\apache\apache-tomcat-9.0.37)

  10. 「コンテキスト宣言モード」で「Server.xml」をチェックし、「設定ファイル」欄に Server.xml のパスを設定します。

  11. 「Apply」ボタンを押します。

  12. 「Apply and Close」ボタンを押します。

プロジェクト作成

プロジェクトの新規作成時と Server.xml の設定を行います。

  1. メインメニュー「File」>「New」>「Project」を実行して、「New Project」ダイアログを起動します。

  2. ツリー内の「Java」>「Tomcatプロジェクト」ノードをクリックします。

  3. 「Project name」を入力して、「Next」ボタンを押します。

  4. 「コンテキスト名」欄のパスを確認して、「Finish」ボタンを押します。
    (「コンテキスト名」欄のパスが、WEB サーバにアクセスするときの URL パスのルート部分になります。)
    http://localhost:8080/{「コンテキスト名」欄のパス}

  5. Tomcat のインストールフォルダ下の「conf\server.xml」に以下の行が追加されていることを確認します。
    この行が Server.xml に登録されていない場合、WEB サーバにアクセスしても、WEB 画面は表示されません。
    (【例】C:\apache\apache-tomcat-9.0.37\conf\server.xml)

    <Context path="{プロジェクト名}" reloadable="true" docBase="{プロジェクトのパス}" workDir="{プロジェクトのパス}\\work" />
    
  6. デフォルトでは、8080 番ポートを使用する設定になっています。8080 番ポート以外に設定したい場合は、以下の部分を変更します。

    <Connector port="8080" protocol="HTTP/1.1"      ★"8080"の部分を設定したいポート番号に変更
    connectionTimeout="20000" 
    redirectPort="8443" useBodyEncodingForURI="true" />
    

サーブレット作成

サーブレットの処理を実装します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックします。

  2. 「Nmae」欄にクラス名、「Super class」欄に「javax.servlet.http.HttpServlet」を入力して「Finish」ボタンを押します。
    (ここでは、「Nmae」欄に「SampleServlet」と入力し、「Nmae」欄と「Super class」欄以外の設定はデフォルトのままとします。)

  3. 「Package Explorer」上のツリーに「sample」>「WEB-INF/src」>「sample」>「SampleServlet.java」ノードが追加されます。

    SampleServlet.java
    package sample;
    
    import javax.servlet.http.HttpServlet;
    
    public class SampleServlet extends HttpServlet {
    
    }
    
  4. 「Package Explorer」上のツリーノード「SampleServlet.java」を選択して、メインメニュー「Source」>「Override/Inplement Methods」をクリックして、
    「Override/Inplement Methods」ダイアログが起動します。

  5. 「HttpServlet」内と「doGet」と「doPost」をチェックして「OK」ボタンを押して、doGet メソッド と doPost メソッドを生成します。
    doGetメソッドは URL にアクセスされたときのように、クライアントからリクエストが送信されたときに実行されます。
    また、doPostメソッドは WEB 画面上のフォームを操作したときのように、WEB サーバからリクエストが送信されたときに実行されます。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class SampleServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doGet(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doPost(req, resp);
        }
    
    }
    
  6. SampleServlet.java に以下のインポート処理を追加します。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    import java.io.PrintWriter;     // 追加
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @SuppressWarnings("serial")     // 追加
    public class SampleServlet extends HttpServlet {
    
  7. SampleServlet.java の SampleServlet クラス内に以下のフィールドを追加します。

    SampleServlet.java
    public class SampleServlet extends HttpServlet {
    
        // ↓ ここから追加
        static final String head = "<head><title>sample</title></head>";
        static final String msg = "<div class='msg' id='message'>テキストをここに書き込みます。</div>";
        static final String form = "<form method='post'>"
                + "<p><textarea name='name' cols='60' rows='4'></textarea></p>"
                + "<p><input name='send' value='write' type='submit' /></p>"
                + "</form>";
    
        static String result = "";
        // ↑ ここまで追加
    
  8. SampleServlet.java の doGet() を以下の実装に変更します。
    WEB ブラウザから WEB サーバにアクセスされたとき、WEB ページの HTML ファイルを読み込み、これをレスポンスボディに設定してレスポンスするようにします。

    SampleServlet.java
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doGet(req, resp);           // コメントアウト
    
        // ↓ ここから追加
        // 日本語の文字化け対策のため、文字エンコーディングを行う
        resp.setContentType("text/html; charset=UTF-8");
    
        // クライアントの WEB ブラウザに表示する WEB 画面を作成する
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println(head);
        out.println("<body>");
        out.println(msg);
        out.println(form);
        out.println("</body>");
        out.println("</html>");
        out.close();
        // ↑ ここまで追加
    }
    
  9. SampleServlet.java の doPost() を以下の実装に変更します。
    WEB ブラウザのフォームから WEB サーバにデータが送信されたとき、リクエストパラメータ(クライアントから送信されたデータ)を取得して、これをレスポンスボディに設定してレスポインスするようにします。

    SampleServlet.java
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doPost(req, resp);          // コメントアウト
    
        // ↓ここから追加
        req.setCharacterEncoding("UTF-8");                  // 文字エンコーディング(日本語の文字化け対策)
        resp.setContentType("text/html; charset=UTF-8");    // 文字エンコーディング(日本語の文字化け対策)
    
        try{
            // 「write」ボタンが押された場合、書き込まれたテキストを取得する。
            if(req.getParameter("send").equalsIgnoreCase("write")){
                result += "<hr><br>" + req.getParameter("name") + "<br>";
            }
        }catch(Exception e){
            e.printStackTrace();
            result = "書き込み失敗<br>" + e.getMessage();
        }
    
        // クライアントの WEB ブラウザに表示する WEB 画面を作成する
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println(head);
        out.println("<body>");
        out.println(msg);
        out.println(form);
        out.println(result);
        out.println("</body>");
        out.println("</html>");
        out.close();
        // ↑ここまで追加
    }
    

web.xml 作成

web.xml を作成します。web.xml はサーブレットコンテナの設定をするファイルです。

  1. 「Package Explorer」上のツリーノード「WEB-INF」を選択して右クリックし、ポップアップメニュー「New」>「Other」をクリックして、「New」ダイアログを起動します。

  2. 「XML」>「XML file」をクリックして、「New XML file」ダイアログを起動します。

  3. 「File name」欄に「web.xml」と入力して「Finish」ボタンを押すと、「Package Explorer」上にツリーノード「web.xml」が追加されます。

  4. 「Package Explorer」上のツリーノード「web.xml」を選択して右クリックし、ポップアップメニュー「Open With」>「Generic Text Editor」をクリックします。

    web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    
  5. web.xml に以下のコードを追加します。

    web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!-- ↓ここから追加 -->
    <web-app>
        <servlet>
            <servlet-name>sample</servlet-name>                 <!-- Servlet name -->
            <servlet-class>sample.SampleServlet</servlet-class> <!-- Class name -->
        </servlet>
    
        <servlet-mapping>
            <servlet-name>sample</servlet-name>                 <!-- Servlet name -->
            <url-pattern>/servlet</url-pattern>                 <!-- URL pattern -->
        </servlet-mapping>
    </web-app>
    <!-- ↑ここまで追加 -->
    

ビルド

ビルドは、自動ビルドを有効にして、自動的にビルドするようにします。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Reflesh」をクリックすると、
    「Package Explorer」上のツリーのファイル構成が現在の状態に自動的に更新されます。

  2. メインメニュー「Project」>「Build Automatically」をチェックして、自動ビルドを有効にします。
    (ビルドが実施されない場合は、メインメニュー「Project」>「Clean」をクリックします。)

実行

ビルドができたら実行します。まずは、Eclipse を使用して、Windows 上に WEB サーバを起動して、WEB ブラウザからアクセスします。

  1. メインメニューのアイコン「Tomcat 再起動」をクリックします。

  2. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://localhost:8080/sample

  3. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

プロジェクトのエクスポート

プロジェクト削除前にプロジェクトのバックアップを取得します。

  1. Tomcat を停止します。

  2. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Export」をクリックして、「Export」ダイアログを起動します。

  3. 「General」>「File System」を選択して「Next」ボタンを押します。

  4. プロジェクトのチェックボックスをチェックします。

  5. 「To directory」欄にエクスポート先のパスを入力して「Finish」ボタンを押します。

プロジェクトのインポート

プロジェクトを再度起動する場合はバックアップしたプロジェクトをインポートします。

  1. 『プロジェクト作成』項目にしたがって、プロジェクトを作成します。

  2. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Import」をクリックして、「Import」ダイアログを起動します。

  3. 「General」>「File System」を選択して「Next」ボタンを押します。

  4. プロジェクトのチェックボックスをチェックします。

  5. 「Into folder」欄にインポート先のパスを入力して「Finish」ボタンを押します。

  6. 「Overwrite '.classpath' in folder {プロジェクト名}?」と質問されたら、「Not to all」を選択します。

WAR ファイル作成

デプロイ(利用可能な状態にする)するために、WAR ファイル(WEB アプリの動作に必要なファイルをまとめたもの)を作成します。

  1. メインメニュー「Project」>「Properties」をクリックして、「Properties」ダイアログを起動します。

  2. ツリーノード「Tomcat」を選択し、「WARエクスポート設定」タブを開き、「参照」ボタンを押して、「開く」ダイアログを起動します。

  3. 「ファイル名」欄に保存する WAR ファイル名を入力して、「開く」ボタンを押します。

  4. 「エクスポートするWARファイル」欄に保存する WAR ファイルのパスが反映されたら、「適用して閉じる」ボタンを押します。

  5. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、「Tomcatプロジェクト」>「プロジェクト設定に従いWARファイル作成」を実行します。

  6. 「エクスポートするWARファイル」欄に設定したパスに WAR ファイルが生成されます。

デプロイ環境

ここから、デプロイの準備をします。ここでは、Ubuntu にデプロイします。

  • Ubuntu 20.04 LTS
  • OpenJDK 11
  • Tomcat 9

デプロイ環境構築①:OpenJDK 11 インストール

まず、Java をインストールします。

  1. OpenJDK 11 をインストールします。

    $ sudo apt-get install openjdk-11-jdk
    
  2. インストールした Java のバージョンを確認します。
    ~~~
    $ java --version
    openjdk 11.0.8 2020-07-14
    OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu120.04)
    OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu120.04, mixed mode, sharing)
    ~~~

デプロイ環境構築②:TOMCAT インストール

次に、TOMCAT をインストールします。

  1. 下記のサイトにアクセスし、左側の「Download」項目の「Tomcat9」をクリックします。
    http://tomcat.apache.org/download-80.cgi

  2. 「Binary Distributions」内の「Core」下の「tar.gz」をクリックします。
    ⇒「apache-tomcat-9.0.37.zip」がダウンロードされます。
    https://tomcat.apache.org/download-90.cgi

  3. ダウンロードが完了したら任意のディレクトリに解凍します。ここでは、/opt に解凍します。

    $ sudo tar zxvf apache-tomcat-9.0.37.tar.gz -C /opt
    
  4. /opt に解凍されたことを確認します。

    $ ls /opt/apache-tomcat-9.0.37/
    BUILDING.txt  CONTRIBUTING.md  LICENSE  NOTICE  README.md  RELEASE-NOTES  RUNNING.txt  bin  conf  lib  logs  temp  webapps  work
    

デプロイ

デプロイ環境が準備できたら、デプロイします。

  1. tomcat の デフォルト設定では、root ユーザ以外のユーザに実行権限がないため、root ユーザになります。

    $ sudo su
    
  2. 作成した WAR ファイルを TOMCAT の webapps 下のフォルダにコピーします。

    # mv sample.war /opt/apache-tomcat-9.0.37/webapps
    
  3. TOMCAT を起動します。

    # cd /opt/apache-tomcat-9.0.37/bin
    # sh startup.sh
    
  4. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://{デプロイしたPCのIPアドレス}:8080/sample

  5. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

  6. TOMCAT を終了します。

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

Eclipse を使って Tomcat プロジェクトを作成する

はじめに

  • Eclipse を使って Tomcat プロジェクト(Java Servlet)を作成する方法についてまとめました。

概要

  • Java Servlet とは、Java で実装された WEB アプリケーションにおいて、動的 WEB ページを生成するプログラムです。WEB アプリケーションは、サーバ・クライアントシステムになっており、以下の図のように、クライアントがサーバにリクエストを送信し、サーバがクライアントにレスポンスを返して動作します。

  • Java Servlet は単体で動作するのではなく、サーブレットコンテナと呼ばれるソフトウェアと一緒に動作します。Tomcat とは、このサーブレットコンテナに該当します。WEB ブラウザから WEB サーバにリクエストを送信すると、Tomcat がこのリクエストを受信します。サーブレットは、リクエストの種類に応じた動的 WEB ページを生成し、Tomcat は、この動的 WEB ページをWEB ブラウザにレスポンスとして送信します。WEB ブラウザには、この動的 WEB ページをが表示されます。

開発環境

  • Windows 10(64bit)
  • OpenJDK 11
  • Eclipse IDE for Java Developers(Version: 2020-06)
  • Tomcat 9

開発環境構築①:OpenJDK 11 インストール

Java Servlet、Eclipse、Tomcat を動かすためには JRE(Java Runtime Environment)が必要になるので、OpenJDK をインストールします。JRE をインストールしていないと、Eclipse 起動時にエラーになるので、Eclipse のインストール前に行う必要があります。

  1. 以下の OpenJDK の公式サイトに移動し、「Download」項目内の jdk.java.net/14 のリンクをクリックします。( jdk.java.net/14 のリンクは、OpenJDK がバージョンアップされる度に変わります。)
    https://openjdk.java.net/

  2. 左側のリンク一覧の中の「Java SE 11」をクリックします。(最新版の OpenJDK をダウンロードしたい場合は、このサイトから OpenJDK をダウンロードできます。)
    https://jdk.java.net/14/

  3. 「Windows/x64 Java Development Kit」をクリックします。
    https://jdk.java.net/java-se-ri/11

  4. 「openjdk-11+28_windows-x64_bin.zip」がダウンロードされます。

  5. ダウンロードが完了したら、任意のフォルダに解凍し、「jdk-11\bin」の絶対パスをシステム環境変数に追加します。(ここでは、C:\openjdk に解凍し、「C:\openjdk\jdk-11\bin」をシステム環境変数に追加しました。)

  6. コマンドプロンプトを起動して、Java のバージョン確認を行う。(openjdk 11 であれば OK です。)

    >java --version
    openjdk 11 2018-09-25
    OpenJDK Runtime Environment 18.9 (build 11+28)
    OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
    

開発環境構築②:Eclipse インストール

OpneJDK のインストールが完了したら、Eclipse をダウンロードして起動し、Tomcaat Plagin をインストールします。

  1. Eclipse のパッケージインストーラのダウンロードサイトに移動し、「Eclipse IDE for Java Developers」内の「Windows 64-bit」をクリックします。
    https://www.eclipse.org/downloads/packages/

  2. ダウンロードページに移動したら、「Download」ボタンをクリックします。

  3. eclipse-java-2020-06-R-win32-x86_64.zip がダウンロードされます。

  4. ダウンロード完了したら、任意のフォルダに解凍し、「eclipse.exe」をダブルクリックして、Eclipse を起動します。(起動時に、workspaceフォルダのパスの入力が必要ですが、デフォルト設定のままにします。)

  5. Eclipse のメインメニュー「Help」>「Eclipse Marketplace」を実行して、「Eclipse マーケットプレース」ダイアログを起動します。

  6. 「Search」欄に、「Tomcat」と入力して、「Go」ボタンを押します。

  7. 「Eclipse Tomcat Plugin 9.1.4」を選択して、「Install」ボタンを押します。

  8. 「I accept the terms of the license agreement」を選択して「Finish」ボタンを押す。

  9. Eclipse を再起動すると、Eclipseのメインメニューのアイコンに Tomcat のアイコンがメニューに表示されます。

開発環境構築③:TOMCAT インストール

TOMCAT をインストールします。

  1. 下記のサイトにアクセスし、左側の「Download」項目の「Tomcat9」をクリックします。
    http://tomcat.apache.org/download-80.cgi

  2. 「Binary Distributions」内の「Core」下の「zip」をクリックします。
    https://tomcat.apache.org/download-90.cgi

  3. 「apache-tomcat-9.0.37.zip」がダウンロードされます。

  4. ダウンロードが完了したら任意のフォルダに解凍します。ここでは、以下のフォルダに解凍します。
    C:\apache\apache-tomcat-9.0.37

  5. 以下のシステム環境変数を追加します。

    ・CATALINA_HOME    :TOMCAT のインストールフォルダのパス(【例】C:\apache\apache-tomcat-9.0.37)
    ・JAVA_HOME        :Java のインストールフォルダのパス(【例】C:\openjdk\jdk-11)
    

Eclipse 初期設定

プロジェクト作成の前に Eclipse の初期設定を行います。最初に一度設定しておけば次回からは設定不要です。

  1. メインメニュー「Window」>「Preferences」を実行して、「Preferences」ダイアログを起動します。

  2. 左側のツリーから「General」>「Appearance」>「Colors and Fonts」を選択します。

  3. 「Java Editor Text-Font」を選択し、「Edit」ボタンを押して、「フォント」ダイアログを起動します。

  4. Eclipse 上での文字化け(コメント「//」の直後に日本語を記述すると文字化けする)対策のため、フォントを MS ゴシックに変更し、「Apply」ボタンを押します。

  5. 左側のツリーから「General」>「Workspace」を選択します。

  6. 「Text file encoding」で「Other」をチェックし、「UTF-8」を選択します。(デプロイする環境が Linux であることを想定して、Linux 上で動作するように UTF-8 に設定しておきます。)

  7. 「New text file line delimiter」で「その他」をチェックし、「Unix」を選択して、「Apply」ボタンを押します。

  8. 左側のツリーから「Tomcat」を選択し、「Tomcat バージョン」の「9」をチェックします。

  9. 「Tomcat ホーム」に Tomcat のフォルダを設定します。(【例】C:\apache\apache-tomcat-9.0.37)

  10. 「コンテキスト宣言モード」で「Server.xml」をチェックし、「設定ファイル」欄に Server.xml のパスを設定します。

  11. 「Apply」ボタンを押します。

  12. 「Apply and Close」ボタンを押します。

プロジェクト作成

プロジェクトの新規作成時と Server.xml の設定を行います。

  1. メインメニュー「File」>「New」>「Project」を実行して、「New Project」ダイアログを起動します。

  2. ツリー内の「Java」>「Tomcatプロジェクト」ノードをクリックします。

  3. 「Project name」を入力して、「Next」ボタンを押します。

  4. 「コンテキスト名」欄のパスを確認して、「Finish」ボタンを押します。(「コンテキスト名」欄のパスが、WEB サーバにアクセスするときの URL パスのルート部分になります。)
    http://localhost:8080/{「コンテキスト名」欄のパス}

  5. Tomcat のインストールフォルダ下の「conf\server.xml」に以下の行が追加されていることを確認します。この行が Server.xml に登録されていない場合、WEB サーバにアクセスしても、WEB 画面は表示されません。
    (【例】C:\apache\apache-tomcat-9.0.37\conf\server.xml)

    <Context path="{プロジェクト名}" reloadable="true" docBase="{プロジェクトのパス}" workDir="{プロジェクトのパス}\\work" />
    
  6. デフォルトでは、8080 番ポートを使用する設定になっています。8080 番ポート以外に設定したい場合は、以下の部分を変更します。

    <Connector port="8080" protocol="HTTP/1.1"      ★"8080"の部分を設定したいポート番号に変更
    connectionTimeout="20000" 
    redirectPort="8443" useBodyEncodingForURI="true" />
    

サーブレット作成

サーブレットの処理を実装します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックします。

  2. 「Nmae」欄にクラス名、「Super class」欄に「javax.servlet.http.HttpServlet」を入力して「Finish」ボタンを押します。(ここでは、「Nmae」欄に「SampleServlet」と入力し、「Nmae」欄と「Super class」欄以外の設定はデフォルトのままとします。)

  3. 「Package Explorer」上のツリーに「sample」>「WEB-INF/src」>「sample」>「SampleServlet.java」ノードが追加されます。

    SampleServlet.java
    package sample;
    
    import javax.servlet.http.HttpServlet;
    
    public class SampleServlet extends HttpServlet {
    
    }
    
  4. 「Package Explorer」上のツリーノード「SampleServlet.java」を選択して、メインメニュー「Source」>「Override/Inplement Methods」をクリックして、「Override/Inplement Methods」ダイアログを起動します。

  5. 「HttpServlet」内と「doGet」と「doPost」をチェックして「OK」ボタンを押して、doGet メソッド と doPost メソッドを生成します。doGetメソッドは URL にアクセスされたときのように、クライアントからリクエストが送信されたときに実行されます。また、doPostメソッドは WEB 画面上のフォームを操作したときのように、WEB サーバからリクエストが送信されたときに実行されます。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class SampleServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doGet(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doPost(req, resp);
        }
    
    }
    
  6. SampleServlet.java に以下のインポート処理を追加します。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    import java.io.PrintWriter;     // 追加
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @SuppressWarnings("serial")     // 追加
    public class SampleServlet extends HttpServlet {
    
  7. SampleServlet.java の SampleServlet クラス内に以下のフィールドを追加します。

    SampleServlet.java
    public class SampleServlet extends HttpServlet {
    
        // ↓ ここから追加
        static final String head = "<head><title>sample</title></head>";
        static final String msg = "<div class='msg' id='message'>テキストをここに書き込みます。</div>";
        static final String form = "<form method='post'>"
                + "<p><textarea name='name' cols='60' rows='4'></textarea></p>"
                + "<p><input name='send' value='write' type='submit' /></p>"
                + "</form>";
    
        static String result = "";
        // ↑ ここまで追加
    
  8. SampleServlet.java の doGet() を以下の実装に変更します。WEB ブラウザから WEB サーバにアクセスされたとき、WEB ページの HTML ファイルを読み込み、これをレスポンスボディに設定してレスポンスするようにします。

    SampleServlet.java
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doGet(req, resp);           // コメントアウト
    
        // ↓ ここから追加
        // 日本語の文字化け対策のため、文字エンコーディングを行う
        resp.setContentType("text/html; charset=UTF-8");
    
        // クライアントの WEB ブラウザに表示する WEB 画面を作成する
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println(head);
        out.println("<body>");
        out.println(msg);
        out.println(form);
        out.println("</body>");
        out.println("</html>");
        out.close();
        // ↑ ここまで追加
    }
    
  9. SampleServlet.java の doPost() を以下の実装に変更します。WEB ブラウザのフォームから WEB サーバにデータが送信されたとき、リクエストパラメータ(クライアントから送信されたデータ)を取得して、これをレスポンスボディに設定してレスポインスするようにします。

    SampleServlet.java
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doPost(req, resp);          // コメントアウト
    
        // ↓ここから追加
        req.setCharacterEncoding("UTF-8");                  // 文字エンコーディング(日本語の文字化け対策)
        resp.setContentType("text/html; charset=UTF-8");    // 文字エンコーディング(日本語の文字化け対策)
    
        try{
            // 「write」ボタンが押された場合、書き込まれたテキストを取得する
            if(req.getParameter("send").equalsIgnoreCase("write")){
                result += "<hr><br>" + req.getParameter("name") + "<br>";
            }
        }catch(Exception e){
            e.printStackTrace();
            result = "書き込み失敗<br>" + e.getMessage();
        }
    
        // クライアントの WEB ブラウザに表示する WEB 画面を作成する
        PrintWriter out = resp.getWriter();
        out.println("<html>");
        out.println(head);
        out.println("<body>");
        out.println(msg);
        out.println(form);
        out.println(result);
        out.println("</body>");
        out.println("</html>");
        out.close();
        // ↑ここまで追加
    }
    

web.xml 作成

web.xml を作成します。web.xml はサーブレットコンテナの設定をするファイルです。

  1. 「Package Explorer」上のツリーノード「WEB-INF」を選択して右クリックし、ポップアップメニュー「New」>「Other」をクリックして、「New」ダイアログを起動します。

  2. 「XML」>「XML file」をクリックして、「New XML file」ダイアログを起動します。

  3. 「File name」欄に「web.xml」と入力して「Finish」ボタンを押すと、「Package Explorer」上にツリーノード「web.xml」が追加されます。

  4. 「Package Explorer」上のツリーノード「web.xml」を選択して右クリックし、ポップアップメニュー「Open With」>「Generic Text Editor」をクリックします。

    web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    
  5. web.xml に以下のコードを追加します。

    web.xml
    <?xml version="1.0" encoding="UTF-8"?>
    
    <!-- ↓ここから追加 -->
    <web-app>
        <servlet>
            <servlet-name>sample</servlet-name>                 <!-- Servlet name -->
            <servlet-class>sample.SampleServlet</servlet-class> <!-- Class name -->
        </servlet>
    
        <servlet-mapping>
            <servlet-name>sample</servlet-name>                 <!-- Servlet name -->
            <url-pattern>/servlet</url-pattern>                 <!-- URL pattern -->
        </servlet-mapping>
    </web-app>
    <!-- ↑ここまで追加 -->
    

ビルド

ビルドは、自動ビルドを有効にして、自動的にビルドするようにします。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Reflesh」をクリックすると、「Package Explorer」上のツリーのファイル構成が現在の状態に自動的に更新されます。

  2. メインメニュー「Project」>「Build Automatically」をチェックして、自動ビルドを有効にします。(ビルドが実施されない場合は、メインメニュー「Project」>「Clean」をクリックします。)

実行

ビルドができたら実行します。まずは、Eclipse を使用して、Windows 上に WEB サーバを起動して、WEB ブラウザからアクセスします。

  1. メインメニューのアイコン「Tomcat 再起動」をクリックします。

  2. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://localhost:8080/sample/servlet

  3. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

プロジェクトのエクスポート

プロジェクト削除前にプロジェクトのバックアップを取得します。

  1. Tomcat を停止します。

  2. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Export」をクリックして、「Export」ダイアログを起動します。

  3. 「General」>「File System」を選択して「Next」ボタンを押します。

  4. プロジェクトのチェックボックスをチェックします。

  5. 「To directory」欄にエクスポート先のパスを入力して「Finish」ボタンを押します。

プロジェクトのインポート

プロジェクトを再度起動する場合はバックアップしたプロジェクトをインポートします。

  1. 『プロジェクト作成』項目にしたがって、プロジェクトを作成します。

  2. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Import」をクリックして、「Import」ダイアログを起動します。

  3. 「General」>「File System」を選択して「Next」ボタンを押します。

  4. プロジェクトのチェックボックスをチェックします。

  5. 「Into folder」欄にインポート先のパスを入力して「Finish」ボタンを押します。

  6. 「Overwrite '.classpath' in folder {プロジェクト名}?」と質問されたら、「Not to all」を選択します。

WAR ファイル作成

デプロイ(利用可能な状態にする)するために、WAR ファイル(WEB アプリの動作に必要なファイルをまとめたもの)を作成します。

  1. メインメニュー「Project」>「Properties」をクリックして、「Properties」ダイアログを起動します。

  2. ツリーノード「Tomcat」を選択し、「WARエクスポート設定」タブを開き、「参照」ボタンを押して、「開く」ダイアログを起動します。

  3. 「ファイル名」欄に保存する WAR ファイル名を入力して、「開く」ボタンを押します。

  4. 「エクスポートするWARファイル」欄に保存する WAR ファイルのパスが反映されたら、「適用して閉じる」ボタンを押します。

  5. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、「Tomcatプロジェクト」>「プロジェクト設定に従いWARファイル作成」を実行します。

  6. 「エクスポートするWARファイル」欄に設定したパスに WAR ファイルが生成されます。

デプロイ環境

ここから、デプロイの準備をします。ここでは、Ubuntu にデプロイします。

  • Ubuntu 20.04 LTS
  • OpenJDK 11
  • Tomcat 9

デプロイ環境構築①:OpenJDK 11 インストール

まず、Java をインストールします。

  1. OpenJDK 11 をインストールします。

    $ sudo apt-get install openjdk-11-jdk
    
  2. インストールした Java のバージョンを確認します。

    $ java --version
    openjdk 11.0.8 2020-07-14
    OpenJDK Runtime Environment (build 11.0.8+10-post-Ubuntu-0ubuntu120.04)
    OpenJDK 64-Bit Server VM (build 11.0.8+10-post-Ubuntu-0ubuntu120.04, mixed mode, sharing)
    

デプロイ環境構築②:TOMCAT インストール

次に、TOMCAT をインストールします。

  1. 下記のサイトにアクセスし、左側の「Download」項目の「Tomcat9」をクリックします。
    http://tomcat.apache.org/download-80.cgi

  2. 「Binary Distributions」内の「Core」下の「tar.gz」をクリックします。
    ⇒「apache-tomcat-9.0.37.zip」がダウンロードされます。
    https://tomcat.apache.org/download-90.cgi

  3. ダウンロードが完了したら任意のディレクトリに解凍します。ここでは、/opt に解凍します。

    $ sudo tar zxvf apache-tomcat-9.0.37.tar.gz -C /opt
    
  4. /opt に解凍されたことを確認します。

    $ ls /opt/apache-tomcat-9.0.37/
    BUILDING.txt  CONTRIBUTING.md  LICENSE  NOTICE  README.md  RELEASE-NOTES  RUNNING.txt  bin  conf  lib  logs  temp  webapps  work
    

デプロイ

デプロイ環境が準備できたら、デプロイします。

  1. tomcat の デフォルト設定では、root ユーザ以外のユーザに実行権限がないため、root ユーザになります。

    $ sudo su
    
  2. 作成した WAR ファイルを TOMCAT の webapps 下のフォルダにコピーします。

    # mv sample.war /opt/apache-tomcat-9.0.37/webapps
    
  3. TOMCAT を起動します。

    # cd /opt/apache-tomcat-9.0.37/bin
    # sh startup.sh
    
  4. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://{デプロイしたPCのIPアドレス}:8080/sample/servlet

  5. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

  6. TOMCAT を終了します。

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

Jetty を使った Java Servlet 開発

はじめに

  • Jetty を使って Java Servlet を開発する方法についてまとめました。

概要

  • Java Servlet(サーブレット)とは、Java で実装された WEB アプリケーションにおいて、動的 WEB ページを生成するプログラムです。
    WEB アプリケーションは、サーバ・クライアントシステムになっており、以下の図のように、クライアントがサーバにリクエストを送信し、サーバがクライアントにレスポンスを返して動作します。

  • Java Servlet(サーブレット)は単体で動作するのではなく、サーブレットコンテナと呼ばれるソフトウェアと一緒に動作します。Jetty とは、このサーブレットコンテナに該当します。Jetty は軽量で組み込みやすいのが特徴です。
    WEB ブラウザから WEB サーバにリクエストを送信すると、Jetty がこのリクエストを受信します。サーバブレットは、リクエストの種類に応じた動的 WEB ページを生成し、Jetty は、この動的 WEB ページをWEB ブラウザにレスポンスとして送信します。WEB ブラウザには、この動的 WEB ページをが表示されます。

開発環境

  • Windows 10(64bit)
  • OpenJDK 11
  • Eclipse IDE for Java Developers(Version: 2020-06)
  • Jetty 9

開発環境構築①:OpenJDK 11 インストール

Java Servlet、Eclipse、Jetty を動かすためには JRE(Java Runtime Environment)が必要になります。ここでは、JRE 取得のために OpenJDK をダウンロードします。
JRE をインストールしていないと、Eclipse 起動時にエラーになるので、Eclipse のインストール前に行う必要があります。

  1. 以下の OpenJDK の公式サイトに移動し、「Download」項目内の jdk.java.net/14 のリンクをクリックします。
    jdk.java.net/14 のリンクは、OpenJDK がバージョンアップされる度に変わります。)
    https://openjdk.java.net/

  2. 左側のリンク一覧の中の「Java SE 11」をクリックします。
    (最新版の OpenJDK をダウンロードしたい場合は、このサイトから OpenJDK をダウンロードできます。)
    https://jdk.java.net/14/

  3. 「Windows/x64 Java Development Kit」をクリックします。
    https://jdk.java.net/java-se-ri/11

  4. 「openjdk-11+28_windows-x64_bin.zip」がダウンロードされます。

  5. ダウンロードが完了したら、任意のフォルダに解凍し、「jdk-11\bin」の絶対パスをシステム環境変数に追加します。
    (ここでは、C:\openjdk に解凍し、「C:\openjdk\jdk-11\bin」をシステム環境変数に追加しました。)

  6. コマンドプロンプトを起動して、Java のバージョン確認を行う。(openjdk 11 であれば OK です。)

    >java --version
    openjdk 11 2018-09-25
    OpenJDK Runtime Environment 18.9 (build 11+28)
    OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
    

開発環境構築②:Eclipse インストール

OpneJDK のインストールが完了したら、Eclipse をインストールします。

  1. Eclipse のパッケージインストーラのダウンロードサイトに移動し、「Eclipse IDE for Java Developers」内の「Windows 64-bit」をクリックします。
    https://www.eclipse.org/downloads/packages/

  2. ダウンロードページに移動したら、「Download」ボタンをクリックします。

  3. eclipse-java-2020-06-R-win32-x86_64.zip がダウンロードされます。

  4. ダウンロード完了したら、任意のフォルダに解凍し、「eclipse.exe」をダブルクリックします。
    (起動時に、workspaceフォルダのパスの入力が必要ですが、デフォルト設定のままにします。)

開発環境構築③:Jetty 取得

以下のサイトから Jetty を取得します。

  1. 下記サイトより、「Eclipse Jetty Downloads」の「.zip」をクリックします。
    ⇒jetty-distribution-9.4.31.v20200723.zip がダウンロードされます。
    https://www.eclipse.org/jetty/download.html

  2. ダウンロード完了したら、任意のフォルダに解凍します。

Eclipse 初期設定

プロジェクト作成の前に Eclipse の初期設定を行います。最初に一度設定しておけば次回からは設定不要です。

  1. メインメニュー「Window」>「Preferences」を実行して、「Preferences」ダイアログを起動します。

  2. 左側のツリーから「Java」>「Installed JREs」を選択し、OpenJDK 11 のパスが設定されていることを確認します。

  3. 文字コードを設定します。ここでは、Linux 上でも動かすことを想定して Linux 向けに変更します。
    左側のツリーから「General」>「Workspace」を選択して、「Text file encoding」で「Other」をチェックし、「UTF-8」を選択します。
    また、「New text file line delimiter」で「その他」をチェックし、「Unix」を選択し、「Apply」ボタンを押します。

  4. 文字化け対策のため、フォントを変更します。左側のツリーから「General」>「Appearance」>「Colors and Fonts」を選択し、「Java Editor Text-Font」を選択し、「Edit」ボタンを押して、「フォント」ダイアログを起動します。
    「フォント」ダイアログでフォントを変更します。最後に、「Apply」ボタンを押します。
    (ここでは、MS ゴシックに変更しました。選択するフォントによっては文字化けする場合があるので、文字化けしないフォントを選択して下さい。)

  5. 「Apply and Close」ボタンを押します。

プロジェクト作成

まず、プロジェクトを作成します。

  1. メインメニューの「File」>「New」>「Java Project」をクリックして、「New Java Project」ダイアログを起動します。

  2. 「Project name」欄にプロジェクト名を入力して、「Finish」ボタンを押します。
    (ここでは、プロジェクト名を「sample」、他の設定はデフォルトのままとします。)

  3. 「New module-info.java」ダイアログが起動したら、「Don't Create」ボタンを押します。

  4. 「Package Explorer」上に sample プロジェクトのツリーが生成されたことを確認します。

Jetty ライブラリの追加

Jetty には、XML でサーバ設定して「start.jar」を使ってサーバを起動する方法と、ライブラリをインポートしてサーバ設定を Java で実装する方法があります。
ここでは、後者の方法で実装するので、ライブラリをプロジェクトに追加します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Properties」をクリックして、「Properties」ダイアログを開きます。

  2. 左側のツリーの「Java Build Path」を選択し、「Libraries」タブを開いて、「Classpath」ノードを選択して「Add External JARs」ボタンを押し、
    以下の Jetty ライブラリを選択して、「Finish」ボタンを押します。

    【追加ライブラリ】
    ・jetty-http-9.4.31.v20200723.jar
    ・jetty-io-9.4.31.v20200723.jar
    ・jetty-security-9.4.31.v20200723.jar
    ・jetty-server-9.4.31.v20200723.jar
    ・jetty-servlet-9.4.31.v20200723.jar
    ・jetty-util-9.4.31.v20200723.jar
    ・jetty-webapp-9.4.31.v20200723.jar
    ・servlet-api-3.1.jar
    

メインクラス作成

次に、メインクラス(main メソッドを含むクラス)を作成します。メインクラスに、WEB サーバの設定を実装します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックして、「New Java Class」ダイアログを起動します。

  2. 「Name」欄にクラス名を入力し、「public static void main(String[] args)」にチェックをして「Finish」ボタンを押します。
    (ここでは、「Name」欄に「SampleMain」と入力します。)

  3. 「Package Explorer」上のツリーに、「sample」>「src」>「sample」>「SampleMain.java」ノードが追加されます。

  4. SampleMain.java のソースコードが以下のように生成されます。

    SampleMain.java
    package sample;
    
    public class SampleMain {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
        }
    
    }
    
  5. SampleMain.java に以下のインポート処理を追加します。

    SampleMain.java
    package sample;
    
    // ↓ここから追加
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.server.Handler;
    import org.eclipse.jetty.server.handler.HandlerList;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.servlet.ServletHolder;
    // ↑ここまで追加
    
    public class SampleMain {
    
  6. SampleMain.java の main() に以下の実装を追加します。ここでは、URI に "/" を追加し、プロジェクト上に配置した HTML ファイルのパスが URI になるようにします。
    また、8080番ポートを使用して WEB サーバを起動するように設定します。

    SampleMain.java
    public static void main(String[] args) throws Exception {   // throws Exception を追加
        // TODO Auto-generated method stub  // コメントアウト
        // ↓ここから追加
        // URI に "/" を追加する(ハンドラに追加)
        ServletHolder holder = new ServletHolder(new SampleServlet());
        ServletContextHandler handler = new ServletContextHandler();
        handler.addServlet(holder, "/");
    
        // ハンドラをハンドラリストに設定
        HandlerList handlerList = new HandlerList();
        handlerList.setHandlers(new Handler[] {handler});
    
        // サーバ作成
        Server server = new Server(8080);   // 8080 ポートを使用するサーバのインスタンス生成
        server.setHandler(handlerList);     // ハンドラリストをサーバのインスタンスに設定
        server.start();                     // サーバ起動
        // ↑ここまで追加
    }
    

サーブレット作成

サーブレットを作成します。クラスを作成した後、Eclipse のメソッド自動生成機能を使用して、doGet() と doPost() を追加します。
doGet() は GET メソッドを受信したときに動作します。GET メソッドは、クライアントの WEB ブラウザから、WEB サーバにアクセスがあったときに動作します。
また、doPost() は POST メソッドを受信したときに動作します。POST メソッドは、クライアントの WEB ブラウザのフォームから WEB サーバにデータが送信されたときに動作します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックします。

  2. 「Name」欄にクラス名、「Superclass」欄に「javax.servlet.http.HttpServlet」を入力して「Finish」ボタンを押します。
    (ここでは、「Name」欄に「SampleServlet」と入力します。)

  3. 「Package Explorer」上のツリーに、「sample」>「src」>「sample」>「SampleServlet.java」ノードが追加されます。

  4. 「Package Explorer」上のツリーノード「SampleServlet.java」を選択して、メインメニュー「Source」>「Override/Inplement Methods」をクリックして、
    「Override/Inplement Methods」ダイアログを開きます。

  5. 「HttpServlet」内と「doGet」と「doPost」をチェックして「OK」ボタンを押して、doGet メソッド と doPost メソッドを生成します。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class SampleServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doGet(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doPost(req, resp);
        }
    
    }
    
  6. SampleServlet.java に以下のインポート処理を追加します。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    import java.io.BufferedReader;  // 追加
    import java.io.OutputStream;    // 追加
    import java.io.FileReader;      // 追加
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @SuppressWarnings("serial")     // 追加
    public class SampleServlet extends HttpServlet {
    
  7. SampleServlet.java の doGet() を以下の実装に変更します。
    WEB ブラウザから WEB サーバにアクセスされたとき、WEB ページの HTML ファイルを読み込み、これをレスポンスボディに設定してレスポンスするようにします。

    SampleServlet.java
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doGet(req, resp);       // コメントアウト
    
        // ↓ ここから追加
        // 文字エンコーディング(日本語の文字化け対策)
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html; charset=UTF-8");
    
        String page = "";
        String line;
    
        // HTMLファイルの読み込み
        @SuppressWarnings("resource")
        BufferedReader buf = new BufferedReader(new FileReader("." + req.getRequestURI().toString()));
        while((line = buf.readLine()) != null){
            page += line;
        }
    
        // レスポンスボディ作成
        OutputStream msgbody = resp.getOutputStream();
        msgbody.write(page.getBytes());
        msgbody.close();
        // ↑ ここまで追加
    }
    
  8. SampleServlet.java の doPost() を以下の実装に変更します。
    WEB ブラウザのフォームから WEB サーバにデータが送信されたとき、リクエストパラメータ(クライアントから送信されたデータ)を取得して、これをレスポンスボディに設定してレスポインスするようにします。

    SampleServlet.java
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doPost(req, resp);      // コメントアウト
    
        // ↓ここから追加
        // 文字エンコーディング(日本語の文字化け対策)
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html; charset=UTF-8");
    
        String result = "以下のメッセージが書き込まれました<br><br>\n";
    
        // リクエストパラメータの取得
        if(req.getParameter("send").equalsIgnoreCase("write")){
            result += req.getParameter("name");
        }
    
        // レスポンスボディ作成
        OutputStream msgbody = resp.getOutputStream();
        msgbody.write(result.getBytes());
        msgbody.close();
        // ↑ここまで追加
    }
    

WEB ページ作成

クライアントの WEB ブラウザに表示する WEB ページを作成します。サーブレットのソースコード内に HTML 文を埋め込む方法もありますが、ファイル化しておくと、ファイルを差し替えるだけで、別の WEB ページを作成できます。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Folder」をクリックして、「New Folder」ダイアログを起動します。

  2. 「Foler name」欄にフォルダ名(ここでは「content」)を入力して、「Finish」ボタンを押して、ツリーノード「content」を追加します。

  3. ツリーノード「content」を選択して右クリックし、ポップアップメニュー「New」>「File」をクリックして、「Create New File」ダイアログを起動します。

  4. 「Foler name」欄にファイル名(ここでは「index.html」)を入力して、「Finish」ボタンを押して、ツリーノード「index.html」を追加します。
    「Package Explorer」の構成は以下のようになります。

  5. ツリーノード「index.html」を開いて、以下の HTML を追加します。

    index.html
    <html>
        <head>
            <title>sample</title>
        </head>
        <body>
            <p>ここにテキストを入力します</p>
            <form method='post'>
                <p><textarea name='name' cols='20' rows='4'></textarea></p>
                <p><input name='send' value='write' type='submit' /></p>
            </form>
        </body>
    </html>
    

ビルド

ビルドは、自動ビルドを有効にして、自動的にビルドするようにします。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Reflesh」をクリックすると、「Package Explorer」上のツリーのファイル構成が現在の状態に自動的に更新されます。

  2. メインメニュー「Project」>「Build Automatically」をチェックして、自動ビルドを有効にします。
    (ビルドが実施されない場合は、メインメニュー「Project」>「Clean」をクリックします。)

実行

ビルドができたら実行します。まずは、Eclipse を使用して、Windows 上に WEB サーバを起動して、WEB ブラウザからアクセスします。

  1. メインメニュー「Run」>「Run Configurations」をクリックします。

  2. 「Run Configurations」ダイアログのツリーの「Java Application」>「SampleMain」を選択します。

  3. 「Main」タブを開いて、「Project」欄にプロジェクト名、「Main class」欄に main メソッドを含むクラス名を入力して「Apply」ボタンを押します。

  4. 「Run Configurations」ダイアログの「Run」ボタンを押すか、アイコンの「Run」ボタンを押します。

  5. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://localhost:8080/sample

  6. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

JAR ファイル作成

デプロイ(利用可能な状態にする)するために、JAR ファイル(WEB アプリケーションの動作に必要なファイルをまとめたもの)を作成します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Export」をクリックして、「Export」ダイアログを開きます。

  2. ツリーノード「Java」>「Runnable JARfile」を選択し、「Next」ボタンを押します。

  3. ツリーノード「Tomcat」を選択し、「Launch configuration」欄にメインクラスを設定し、「Browser」ボタンを押して、「名前を付けて保存」ダイアログを開きます。

  4. 「ファイル名」欄に保存する JAR ファイル名を入力して、「保存」ボタンを押します。

  5. 「Export destination」欄に保存する JAR ファイルのパスが反映されたら、「Finish」ボタンを押します。

  6. 「Export destination」欄に設定したパスに JAR ファイルが生成されます。

デプロイ環境

ここから、デプロイの準備をします。ここでは、Raspberri Pi にデプロイします。

  • Raspberri Pi 3
  • OpenJDK 11

デプロイ環境構築:OpenJDK 11 インストール

まず、Java をインストールします。

  1. Raspberri Pi に開発で使用した OpenJDK 11 をインストールします。

    $ sudo apt-get install openjdk-11-jdk
    

デプロイ

デプロイ環境が準備できたら、デプロイします。

  1. JAR ファイルと content フォルダを Java を実行できるマシンの任意の場所に配置します。

  2. WEB サーバを起動します。

    $ java -jar -Dfile.encoding=UTF-8 sample.jar
    
  3. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://{Raspberry PiのIPアドレス}:8080/sample

  4. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

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

Eclipse を使って Jetty プロジェクトを作成する

はじめに

  • Eclipse を使って Jetty プロジェクト(Java Servlet)を作成する方法についてまとめました。

概要

  • Java Servlet とは、Java で実装された WEB アプリケーションにおいて、動的 WEB ページを生成するプログラムです。WEB アプリケーションは、サーバ・クライアントシステムになっており、以下の図のように、クライアントがサーバにリクエストを送信し、サーバがクライアントにレスポンスを返して動作します。

  • Java Servlet は単体で動作するのではなく、サーブレットコンテナと呼ばれるソフトウェアと一緒に動作します。Jetty とは、このサーブレットコンテナに該当します。Jetty は軽量で組み込みやすいのが特徴です。WEB ブラウザから WEB サーバにリクエストを送信すると、Jetty がこのリクエストを受信します。サーブレットは、リクエストの種類に応じた動的 WEB ページを生成し、Jetty は、この動的 WEB ページをWEB ブラウザにレスポンスとして送信します。WEB ブラウザには、この動的 WEB ページをが表示されます。

開発環境

  • Windows 10(64bit)
  • OpenJDK 11
  • Eclipse IDE for Java Developers(Version: 2020-06)
  • Jetty 9

開発環境構築①:OpenJDK 11 インストール

Java Servlet、Eclipse、Jetty を動かすためには JRE(Java Runtime Environment)が必要になります。ここでは、JRE 取得のために OpenJDK をダウンロードします。
JRE をインストールしていないと、Eclipse 起動時にエラーになるので、Eclipse のインストール前に行う必要があります。

  1. 以下の OpenJDK の公式サイトに移動し、「Download」項目内の jdk.java.net/14 のリンクをクリックします。( jdk.java.net/14 のリンクは、OpenJDK がバージョンアップされる度に変わります。)
    https://openjdk.java.net/

  2. 左側のリンク一覧の中の「Java SE 11」をクリックします。(最新版の OpenJDK をダウンロードしたい場合は、このサイトから OpenJDK をダウンロードできます。)
    https://jdk.java.net/14/

  3. 「Windows/x64 Java Development Kit」をクリックします。
    https://jdk.java.net/java-se-ri/11

  4. 「openjdk-11+28_windows-x64_bin.zip」がダウンロードされます。

  5. ダウンロードが完了したら、任意のフォルダに解凍し、「jdk-11\bin」の絶対パスをシステム環境変数に追加します。(ここでは、C:\openjdk に解凍し、「C:\openjdk\jdk-11\bin」をシステム環境変数に追加しました。)

  6. コマンドプロンプトを起動して、Java のバージョン確認を行う。(openjdk 11 であれば OK です。)

    >java --version
    openjdk 11 2018-09-25
    OpenJDK Runtime Environment 18.9 (build 11+28)
    OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
    

開発環境構築②:Eclipse インストール

OpneJDK のインストールが完了したら、Eclipse をインストールします。

  1. Eclipse のパッケージインストーラのダウンロードサイトに移動し、「Eclipse IDE for Java Developers」内の「Windows 64-bit」をクリックします。
    https://www.eclipse.org/downloads/packages/

  2. ダウンロードページに移動したら、「Download」ボタンをクリックします。

  3. eclipse-java-2020-06-R-win32-x86_64.zip がダウンロードされます。

  4. ダウンロード完了したら、任意のフォルダに解凍し、「eclipse.exe」をダブルクリックします。(起動時に、workspaceフォルダのパスの入力が必要ですが、デフォルト設定のままにします。)

開発環境構築③:Jetty 取得

以下のサイトから Jetty を取得します。

  1. 下記サイトより、「Eclipse Jetty Downloads」の「.zip」をクリックします。
    ⇒jetty-distribution-9.4.31.v20200723.zip がダウンロードされます。
    https://www.eclipse.org/jetty/download.html

  2. ダウンロード完了したら、任意のフォルダに解凍します。

Eclipse 初期設定

プロジェクト作成の前に Eclipse の初期設定を行います。最初に一度設定しておけば次回からは設定不要です。

  1. メインメニュー「Window」>「Preferences」を実行して、「Preferences」ダイアログを起動します。

  2. 左側のツリーから「Java」>「Installed JREs」を選択し、OpenJDK 11 のパスが設定されていることを確認します。

  3. 文字コードを設定します。ここでは、Linux 上でも動かすことを想定して Linux 向けに変更します。左側のツリーから「General」>「Workspace」を選択して、「Text file encoding」で「Other」をチェックし、「UTF-8」を選択します。また、「New text file line delimiter」で「その他」をチェックし、「Unix」を選択し、「Apply」ボタンを押します。

  4. 文字化け対策のため、フォントを変更します。左側のツリーから「General」>「Appearance」>「Colors and Fonts」を選択し、「Java Editor Text-Font」を選択し、「Edit」ボタンを押して、「フォント」ダイアログを起動します。「フォント」ダイアログでフォントを変更し、「Apply」ボタンを押します。(ここでは、MS ゴシックに変更しました。選択するフォントによっては文字化けする場合があるので、文字化けしないフォントを選択して下さい。)

  5. 「Apply and Close」ボタンを押します。

プロジェクト作成

まず、プロジェクトを作成します。

  1. メインメニューの「File」>「New」>「Java Project」をクリックして、「New Java Project」ダイアログを起動します。

  2. 「Project name」欄にプロジェクト名を入力して、「Finish」ボタンを押します。(ここでは、プロジェクト名を「sample」、他の設定はデフォルトのままとします。)

  3. 「New module-info.java」ダイアログが起動したら、「Don't Create」ボタンを押します。

  4. 「Package Explorer」上に sample プロジェクトのツリーが生成されたことを確認します。

Jetty ライブラリの追加

Jetty には、XML でサーバ設定して「start.jar」を使ってサーバを起動する方法と、ライブラリをインポートしてサーバ設定を Java で実装する方法があります。ここでは、後者の方法で実装するので、ライブラリをプロジェクトに追加します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Properties」をクリックして、「Properties」ダイアログを開きます。

  2. 左側のツリーの「Java Build Path」を選択し、「Libraries」タブを開いて、「Classpath」ノードを選択して「Add External JARs」ボタンを押し、以下の Jetty ライブラリを選択して、「Finish」ボタンを押します。

    【追加ライブラリ】
    ・jetty-http-9.4.31.v20200723.jar
    ・jetty-io-9.4.31.v20200723.jar
    ・jetty-security-9.4.31.v20200723.jar
    ・jetty-server-9.4.31.v20200723.jar
    ・jetty-servlet-9.4.31.v20200723.jar
    ・jetty-util-9.4.31.v20200723.jar
    ・jetty-webapp-9.4.31.v20200723.jar
    ・servlet-api-3.1.jar
    

メインクラス作成

次に、メインクラス(main メソッドを含むクラス)を作成します。メインクラスに、WEB サーバの設定を実装します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックして、「New Java Class」ダイアログを起動します。

  2. 「Name」欄にクラス名を入力し、「public static void main(String[] args)」にチェックをして「Finish」ボタンを押します。(ここでは、「Name」欄に「SampleMain」と入力します。)

  3. 「Package Explorer」上のツリーに、「sample」>「src」>「sample」>「SampleMain.java」ノードが追加されます。

  4. SampleMain.java のソースコードが以下のように生成されます。

    SampleMain.java
    package sample;
    
    public class SampleMain {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
        }
    
    }
    
  5. SampleMain.java に以下のインポート処理を追加します。

    SampleMain.java
    package sample;
    
    // ↓ここから追加
    import org.eclipse.jetty.server.Server;
    import org.eclipse.jetty.server.Handler;
    import org.eclipse.jetty.server.handler.HandlerList;
    import org.eclipse.jetty.servlet.ServletContextHandler;
    import org.eclipse.jetty.servlet.ServletHolder;
    // ↑ここまで追加
    
    public class SampleMain {
    
  6. SampleMain.java の main() に以下の実装を追加します。ここでは、URI に "/" を追加し、プロジェクト上に配置した HTML ファイルのパスが URI になるようにします。また、8080番ポートを使用して WEB サーバを起動するように設定します。

    SampleMain.java
    public static void main(String[] args) throws Exception {   // throws Exception を追加
        // TODO Auto-generated method stub  // コメントアウト
        // ↓ここから追加
        // URI に "/" を追加する(ハンドラに追加)
        ServletHolder holder = new ServletHolder(new SampleServlet());
        ServletContextHandler handler = new ServletContextHandler();
        handler.addServlet(holder, "/");
    
        // ハンドラをハンドラリストに設定
        HandlerList handlerList = new HandlerList();
        handlerList.setHandlers(new Handler[] {handler});
    
        // サーバ作成
        Server server = new Server(8080);   // 8080 ポートを使用するサーバのインスタンス生成
        server.setHandler(handlerList);     // ハンドラリストをサーバのインスタンスに設定
        server.start();                     // サーバ起動
        // ↑ここまで追加
    }
    

サーブレット作成

サーブレットを作成します。クラスを作成した後、Eclipse のメソッド自動生成機能を使用して、doGet() と doPost() を追加します。doGet() は GET メソッドを受信したときに動作します。GET メソッドは、クライアントの WEB ブラウザから、WEB サーバにアクセスがあったときに動作します。また、doPost() は POST メソッドを受信したときに動作します。POST メソッドは、クライアントの WEB ブラウザのフォームから WEB サーバにデータが送信されたときに動作します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックします。

  2. 「Name」欄にクラス名、「Superclass」欄に「javax.servlet.http.HttpServlet」を入力して「Finish」ボタンを押します。(ここでは、「Name」欄に「SampleServlet」と入力します。)

  3. 「Package Explorer」上のツリーに、「sample」>「src」>「sample」>「SampleServlet.java」ノードが追加されます。

  4. 「Package Explorer」上のツリーノード「SampleServlet.java」を選択して、メインメニュー「Source」>「Override/Inplement Methods」をクリックして、「Override/Inplement Methods」ダイアログを開きます。

  5. 「HttpServlet」内と「doGet」と「doPost」をチェックして「OK」ボタンを押して、doGet メソッド と doPost メソッドを生成します。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class SampleServlet extends HttpServlet {
    
        @Override
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doGet(req, resp);
        }
    
        @Override
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
            // TODO Auto-generated method stub
            super.doPost(req, resp);
        }
    
    }
    
  6. SampleServlet.java に以下のインポート処理を追加します。

    SampleServlet.java
    package sample;
    
    import java.io.IOException;
    import java.io.BufferedReader;  // 追加
    import java.io.OutputStream;    // 追加
    import java.io.FileReader;      // 追加
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    @SuppressWarnings("serial")     // 追加
    public class SampleServlet extends HttpServlet {
    
  7. SampleServlet.java の doGet() を以下の実装に変更します。WEB ブラウザから WEB サーバにアクセスされたとき、WEB ページの HTML ファイルを読み込み、これをレスポンスボディに設定してレスポンスするようにします。

    SampleServlet.java
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doGet(req, resp);       // コメントアウト
    
        // ↓ ここから追加
        // 文字エンコーディング(日本語の文字化け対策)
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html; charset=UTF-8");
    
        String page = "";
        String line;
    
        // HTMLファイルの読み込み
        @SuppressWarnings("resource")
        BufferedReader buf = new BufferedReader(new FileReader("." + req.getRequestURI().toString()));
        while((line = buf.readLine()) != null){
            page += line;
        }
    
        // レスポンスボディ作成
        OutputStream msgbody = resp.getOutputStream();
        msgbody.write(page.getBytes());
        msgbody.close();
        // ↑ ここまで追加
    }
    
  8. SampleServlet.java の doPost() を以下の実装に変更します。WEB ブラウザのフォームから WEB サーバにデータが送信されたとき、リクエストパラメータ(クライアントから送信されたデータ)を取得して、これをレスポンスボディに設定してレスポインスするようにします。

    SampleServlet.java
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // TODO Auto-generated method stub
        //super.doPost(req, resp);      // コメントアウト
    
        // ↓ここから追加
        // 文字エンコーディング(日本語の文字化け対策)
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html; charset=UTF-8");
    
        String result = "以下のメッセージが書き込まれました<br><br>\n";
    
        // リクエストパラメータの取得
        if(req.getParameter("send").equalsIgnoreCase("write")){
            result += req.getParameter("name");
        }
    
        // レスポンスボディ作成
        OutputStream msgbody = resp.getOutputStream();
        msgbody.write(result.getBytes());
        msgbody.close();
        // ↑ここまで追加
    }
    

WEB ページ作成

クライアントの WEB ブラウザに表示する WEB ページを作成します。サーブレットのソースコード内に HTML 文を埋め込む方法もありますが、ファイル化しておくと、ファイルを差し替えるだけで、別の WEB ページを作成できます。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Folder」をクリックして、「New Folder」ダイアログを起動します。

  2. 「Foler name」欄にフォルダ名(ここでは「content」)を入力して、「Finish」ボタンを押して、ツリーノード「content」を追加します。

  3. ツリーノード「content」を選択して右クリックし、ポップアップメニュー「New」>「File」をクリックして、「Create New File」ダイアログを起動します。

  4. 「Foler name」欄にファイル名(ここでは「index.html」)を入力して、「Finish」ボタンを押して、ツリーノード「index.html」を追加します。
    「Package Explorer」の構成は以下のようになります。

  5. ツリーノード「index.html」を開いて、以下の HTML を追加します。

    index.html
    <html>
        <head>
            <title>sample</title>
        </head>
        <body>
            <p>ここにテキストを入力します</p>
            <form method='post'>
                <p><textarea name='name' cols='20' rows='4'></textarea></p>
                <p><input name='send' value='write' type='submit' /></p>
            </form>
        </body>
    </html>
    

ビルド

ビルドは、自動ビルドを有効にして、自動的にビルドするようにします。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Reflesh」をクリックすると、「Package Explorer」上のツリーのファイル構成が現在の状態に自動的に更新されます。

  2. メインメニュー「Project」>「Build Automatically」をチェックして、自動ビルドを有効にします。(ビルドが実施されない場合は、メインメニュー「Project」>「Clean」をクリックします。)

実行

ビルドができたら実行します。まずは、Eclipse を使用して、Windows 上に WEB サーバを起動して、WEB ブラウザからアクセスします。

  1. メインメニュー「Run」>「Run Configurations」をクリックします。

  2. 「Run Configurations」ダイアログのツリーの「Java Application」>「SampleMain」を選択します。

  3. 「Main」タブを開いて、「Project」欄にプロジェクト名、「Main class」欄に main メソッドを含むクラス名を入力して「Apply」ボタンを押します。

  4. 「Run Configurations」ダイアログの「Run」ボタンを押すか、アイコンの「Run」ボタンを押します。

  5. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://localhost:8080/sample

  6. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

JAR ファイル作成

デプロイ(利用可能な状態にする)するために、JAR ファイル(WEB アプリケーションの動作に必要なファイルをまとめたもの)を作成します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Export」をクリックして、「Export」ダイアログを開きます。

  2. ツリーノード「Java」>「Runnable JARfile」を選択し、「Next」ボタンを押します。

  3. ツリーノード「Tomcat」を選択し、「Launch configuration」欄にメインクラスを設定し、「Browser」ボタンを押して、「名前を付けて保存」ダイアログを開きます。

  4. 「ファイル名」欄に保存する JAR ファイル名を入力して、「保存」ボタンを押します。

  5. 「Export destination」欄に保存する JAR ファイルのパスが反映されたら、「Finish」ボタンを押します。

  6. 「Export destination」欄に設定したパスに JAR ファイルが生成されます。

デプロイ環境

ここから、デプロイの準備をします。ここでは、Raspberri Pi にデプロイします。

  • Raspberri Pi 3
  • OpenJDK 11

デプロイ環境構築:OpenJDK 11 インストール

まず、Java をインストールします。

  1. Raspberri Pi に開発で使用した OpenJDK 11 をインストールします。

    $ sudo apt-get install openjdk-11-jdk
    

デプロイ

デプロイ環境が準備できたら、デプロイします。

  1. JAR ファイルと content フォルダを Java を実行できるマシンの任意の場所に配置します。

  2. WEB サーバを起動します。

    $ java -jar -Dfile.encoding=UTF-8 sample.jar
    
  3. WEB ブラウザを起動して以下の URL にアクセスすると、クライアントからサーバにGETメソッドが送信され、以下のような WEB ページが表示されます。
    http://{Raspberry PiのIPアドレス}:8080/sample

  4. テキストを入力して、「write」ボタンを押すと、クライアントからサーバにPOSTメソッドが送信され、以下のような WEB ページが表示されます。

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

JavaでTODOアプリを制作しよう9 TODOの表示を作成日時が新しい順にソートする + 期日のデフォルトを当日の日付にする

こんにちは。
今回の記事では今まで作ってきたTODOの微調整を行います。

TODOアプリ作成リンク集

1: [超基礎の理解] MVCの簡単な説明
2: [雛形を用意する] Spring Initializrで雛形を作ってHello worldしたい
3: [MySQLとの接続・設定・データの表示] MySQLに仮のデータを保存 -> 全取得 -> topに表示する
4: [POST機能] 投稿機能の実装
5: [PATCH機能] TODOの表示を切り替える
6: [JpaRepositoryの簡単な使い方] 検索機能の実装
7: [Thymeleaf テンプレートフラグメントで共通化] Headerの作成
8: [PUT機能] 編集機能の実装
9: TODOの表示を作成日時が新しい順にソートする + 期日のデフォルトを今日の日付にする(今ここ)

TODOの表示を作成日時が新しい順にする

現状の仕様ではTODOリストは作成日時が古い順に表示されるようになっています。

つまり新しいTODOは表示の一番下にくるわけです。

細かいですがこれを登録した日付順(つまり作成日時が新しい順)にソートしましょう!

ソート作業はデータを取り扱う上でついて回る作業なので結構大事です!

TodoRepositoryを編集する

java/com/example/todo/dao/TodoRepository.java
@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, Long> {

    List<TodoEntity> findByTitleContaining(String searchWord);

    //↓追加する
    List<TodoEntity> findAllByOrderByCreateTimeDesc();
}

Repositoryには部分一致検索を実装するメソッドが追加されていましたが、新しくfindAllByOrderByCreateTimeDesc()を追加してやります。

こうすることでJpaRepositoryがDB上にある全データを作成日時がdescending(降順)にソートされた状態で渡してくれます。

超便利ですね!

TodoServiceを編集する

次にサービスクラスを編集します。

java/com/example/todo/TodoService.java
    public List<TodoEntity> findAllTodo() {
        //return todoRepository.findAll();
       return todoRepository.findAllByOrderByCreateTimeDesc();
    }

コメントアウトしている部分が今までの記述でしたが、先ほど作った関数を呼んでやります。

こうすることで/topで表示されているTODOのリストが作成日時の降順になります!

TODO登録フォームの日付を今日の日付にする

JSで今日の日付を取得してformに埋め込む

今回はJSを直接top.htmlに書くことにします(直接書くのはあまり良くないですが、今回はシンプルなアプリなのでよしとしましょうw)

resources/templates/top.html
<script>
    var today = new Date();
    today.setDate(today.getDate());
    var yyyy = today.getFullYear();
    var mm = ("0" + (today.getMonth() + 1)).slice(-2);
    var dd = ("0" + today.getDate()).slice(-2);
    $("#date").val(`${yyyy}-${mm}-${dd}`);
</script>

こんなスクリプトをbodyの閉じタグの直前に書いてやればOKです。

slice(-2)とする事でStringの最後の2桁だけを表示する様にしています。

こうする事で一桁の数字は01, 05のように0がついた状態で表示され、二桁の数字は0がついてない状態で表示されます(012の下2桁だけ表示するという事です。)

このTODOアプリ作成手順を参考にされている方は既に

resources/templates/top.html
<input type="date" id="date" name="deadline" class="col-9 my-0">

となっているかと思いますが、実はここでid="date"としているのでJQueryで今日の日付を渡しています。

以上で今回の微調整となります。

次回以降は例外処理について触れていきたいと思います!

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

JavaでTODOアプリを制作しよう8 編集機能の実装

こんにちは。
今回はJava Springで投稿されたTODOの編集機能部分を実装していきたいと思います。

TODOアプリ作成リンク集

1: [超基礎の理解] MVCの簡単な説明
2: [雛形を用意する] Spring Initializrで雛形を作ってHello worldしたい
3: [MySQLとの接続・設定・データの表示] MySQLに仮のデータを保存 -> 全取得 -> topに表示する
4: [POST機能] 投稿機能の実装
5: [PATCH機能] TODOの表示を切り替える
6: [JpaRepositoryの簡単な使い方] 検索機能の実装
7: [Thymeleaf テンプレートフラグメントで共通化] Headerの作成
8: [PUT機能] 編集機能の実装(今ここ)

編集機能の流れ

まずは編集機能の流れを簡単に説明したいと思います!

  1. 編集したいTODOのデータをIDで検索してフロントに表示する

  2. 編集内容を入力してPUTでコントローラに送信

  3. 内部で処理してSAVEする。

ご想像通りかもしれませんがこんな感じです!

では早速フロントに編集したいTODOを表示するところからやってみましょう!

編集したいTODOを表示する

編集したいTODOを取得してフロント側に送る

java/com/example/todo/TodoController.java
    @GetMapping("/edit/{id}")
    public String showEdit(Model model, @PathVariable("id") long id ) {
        TodoEntity editTarget = todoService.findTodoById(id);
        model.addAttribute( "editTarget" , editTarget);
        return "edit";
    }

まずは以前から使用しているTodoEntityに編集したいTODOをID検索して詰めます。

そしてmodel.addAttribute("変数名", 渡したいオブジェクト)とする事でフロント側でこのオブジェクトを使う事ができる様になります。

今回はそのままeditTargetという名前で使っていくことにします。

return "edit";で編集ページに遷移するようになっているので編集ページを作成します。

フロント側の編集ページを作成する

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>編集</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    </head>
    <body>
        <!-- Header -->
        <header th:replace="common/header :: header_fragment()"></header>

        <!--編集フォーム-->
        <div class=" w-75 h-auto my-1 mx-auto pt-5">
            <p class="pl-5">ToDoを更新する</p>
            <form  th:action="@{'/edit/' + *{id} + '/complete'}" th:object="${editTarget}" method="post" class="container d-flex w-auto my-0 mx-auto">
                <div class="w-100">
                    <label class="row">
                        <span class="col-2 text-center">ToDo名</span>
                        <input type="text" name="title" th:value="${editTarget.title}" class="col-9">
                    </label>
                    <label class="row my-0">
                        <span class="col-2 text-center">期日</span>
                        <input type="date" name="deadline" th:value="${editTarget.deadline}" class="col-9 my-0">
                    </label>
                </div>
                <input type="hidden" name="_method" value="put">
                <button class="btn btn-primary w-25 col-2 mr-3" type="submit">更新</button>
            </form>
        </div>
        <!--/編集フォーム-->

        <!--    bootstrap js読み込み-->
        <script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
    </body>
</html>

ちょっと長くて見辛いかもしれませんが編集ページです。

th:action

↓最初のポイントとは編集フォームの書き出しの部分です↓

 <form  th:action="@{'/edit/' + *{id} + '/complete'}" th:object="${editTarget}" method="post" class="container d-flex w-auto my-0 mx-auto">

th:action="@{'/edit/' + *{id} + '/complete'}"
指定したURLにフォームの内容が送信される様にしています。

今回は
/edit/{id}/complete
というURLに送ります。この処理については後述します。

th:objects

th:object="${editTarget}"

これが先ほどshowEdit関数から送信したeditTargetになります。こうすることでこのform内でeditTargetが使える様になります。

PUTなのにmethod="post"???

ここが今回の処理で詰まりがちなポイントかと思います。

今回は編集処理をPUTのメソッドで行いたいのでmethod="put"では!?と思うかもしれません。
(実際に最初は自分もそうした)

しかしHTML5ではPUT/DELETEメソッドを扱うことができません!

ですので以下の様な処理が必要になります。

  1. HTMLからはフォームデータをPOSTで送る。
  2. Spring側でPOSTメソッドをPUTメソッドに切り替える!
  3. 想定した関数にフォームデータを送る。

なんでそんな面倒なことを・・・と思うかもしれませんが、今のところこのやり方をするしかなさそうです。

というわけで

 <input type="hidden" name="_method" value="put">

というフロント側には表示されないinputを仕込んであげることでPOSTをPUTに変換するような処理を行っているのでした。

POST -> PUTにするのにapplication.propertiesを書き換える

しかし実はこれだけではエラーが起きてしまうのでapplication.propertiesに下記を追記します。

resources/application.properties
spring.mvc.hiddenmethod.filter.enabled=true

こうすることでエラーがなくなります。

ちなみに
SpringBoot2.2+ThymeleafでHTTP PUT/DELETE メソッドを扱う
この記事を読んでみると理解が深まると思うので気になる方は是非。

編集処理を実装する

フロントから送られてきた編集内容を処理する

次に編集内容を処理する部分を実装していきます。

java/com/example/todo/TodoController.java
    @PutMapping("/edit/{id}/complete")
    public String edit(@ModelAttribute TodoForm formData, @PathVariable("id") long id) {
        todoService.editToDo(formData);
        return "redirect:/top";
    }

コントローラにPUTMappingを追加します。

@ModelAttributeでフロントから送られてきたデータをTodoFormクラスに格納します。

↓そしてそのオブジェクトをserviceクラスのeditTodoに送ってやります↓

java/com/example/todo/TodoService.java
    public void editToDo(TodoForm formData) {
        TodoEntity todoEntity = findTodoById(formData.getId());
        todoEntity.setTitle(formData.getTitle());
        todoEntity.setDeadline(formData.getDeadline());
        todoEntity.setStatus(formData.isStatus());
        todoRepository.save(todoEntity);
    }

idからTODOを取得して編集内容であるformDataにそれぞれ値を書き換えます。

今回は戻り値はないのでvoidにしています。

以上で編集処理の実装が完了しました!

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

Eclipse を使った Java プログラム開発

はじめに

  • Eclipse を使って Java プログラムを開発する方法についてまとめました。

動作環境

  • Windows 10(64bit)
  • OpenJDK 11
  • Eclipse IDE for Java Developers(Version: 2020-06)

開発環境構築①:OpenJDK 11 インストール

Eclipse を動かすためには JRE(Java Runtime Environment)が必要になります。ここでは、JRE 取得のために OpenJDK をダウンロードします。
JRE をインストールしていないと、Eclipse 起動時にエラーになるので、Eclipse のインストール前に行う必要があります。

  1. 以下の OpenJDK の公式サイトに移動し、「Download」項目内の jdk.java.net/14 のリンクをクリックします。
    jdk.java.net/14 のリンクは、OpenJDK がバージョンアップされる度に変わります。)
    https://openjdk.java.net/

  2. 左側のリンク一覧の中の「Java SE 11」をクリックします。
    (最新版の OpenJDK をダウンロードしたい場合は、このサイトから OpenJDK をダウンロードできます。)
    https://jdk.java.net/14/

  3. 「Windows/x64 Java Development Kit」をクリックします。
    https://jdk.java.net/java-se-ri/11

  4. 「openjdk-11+28_windows-x64_bin.zip」がダウンロードされます。

  5. ダウンロードが完了したら、任意のフォルダに解凍し、「jdk-11\bin」の絶対パスをシステム環境変数に追加します。
    (ここでは、C:\openjdk に解凍し、「C:\openjdk\jdk-11\bin」をシステム環境変数に追加しました。)

  6. コマンドプロンプトを起動して、Java のバージョン確認を行う。(openjdk 11 であれば OK です。)

    >java --version
    openjdk 11 2018-09-25
    OpenJDK Runtime Environment 18.9 (build 11+28)
    OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
    

開発環境構築②:Eclipse インストール

OpneJDK のインストールが完了したら、Eclipse をインストールします。

  1. Eclipse のパッケージインストーラのダウンロードサイトに移動し、「Eclipse IDE for Java Developers」内の「Windows 64-bit」をクリックします。
    https://www.eclipse.org/downloads/packages/

  2. ダウンロードページに移動したら、「Download」ボタンをクリックします。

  3. eclipse-java-2020-06-R-win32-x86_64.zip がダウンロードされます。

  4. ダウンロード完了したら、任意のフォルダに解凍し、「eclipse.exe」をダブルクリックします。
    (起動時に、workspaceフォルダのパスの入力が必要ですが、デフォルト設定のままにします。)

Eclipse 初期設定

プロジェクト作成の前に Eclipse の初期設定を行います。最初に一度設定しておけば次回からは設定不要です。

  1. メインメニュー「Window」>「Preferences」を実行して、「Preferences」ダイアログを起動します。

  2. 左側のツリーから「Java」>「Installed JREs」を選択し、OpenJDK 11 のパスが設定されていることを確認します。

  3. 文字コードを設定します。ここでは、Linux 上でも動かすことを想定して Linux 向けに変更します。
    左側のツリーから「General」>「Workspace」を選択して、「Text file encoding」で「Other」をチェックし、「UTF-8」を選択します。
    また、「New text file line delimiter」で「その他」をチェックし、「Unix」を選択し、「Apply」ボタンを押します。

  4. 文字化け対策のため、フォントを変更します。左側のツリーから「General」>「Appearance」>「Colors and Fonts」を選択し、「Java Editor Text-Font」を選択し、「Edit」ボタンを押して、「フォント」ダイアログを起動します。
    「フォント」ダイアログでフォントを変更します。最後に、「Apply」ボタンを押します。
    (ここでは、MS ゴシックに変更しました。選択するフォントによっては文字化けする場合があるので、文字化けしないフォントを選択して下さい。)

  5. 「Apply and Close」ボタンを押します。

プロジェクト作成

まず、プロジェクトを作成します。

  1. メインメニューの「File」>「New」>「Java Project」をクリックして、「New Java Project」ダイアログを起動します。

  2. 「Project name」欄にプロジェクト名を入力して、「Finish」ボタンを押します。
    (ここでは、プロジェクト名を「sample」、他の設定はデフォルトのままとします。)

  3. 「New module-info.java」ダイアログが起動したら、「Don't Create」ボタンを押します。

  4. 「Package Explorer」上に sample プロジェクトのツリーが生成されたことを確認します。

メインクラス作成

次に、メインクラス(main メソッドを含むクラス)を作成します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックして、「New Java Class」ダイアログを起動します。

  2. 「Name」欄にクラス名を入力し、「public static void main(String[] args)」にチェックをして「Finish」ボタンを押します。
    (ここでは、「Name」欄に「SampleMain」と入力します。)

  3. 「Package Explorer」上のツリーに、「sample」>「src」>「sample」>「SampleMain.java」ノードが追加されます。SampleMain.java のコードは以下のようになっています。

    SampleMain.java
    package sample;
    
    public class SampleMain {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
        }
    
    }
    
  4. SampleMain.java のソースコードに任意のコードに追加します。

    SampleMain.java
    package sample;
    
    public class SampleMain {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("Hello");        // 追加
            System.out.println("こんにちは");  // 追加
        }
    
    }
    

ビルド

ビルドは、自動ビルドを有効にして、自動的にビルドするようにします。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Reflesh」をクリックすると、「Package Explorer」上のツリーのファイル構成が現在の状態に自動的に更新されます。

  2. メインメニュー「Project」>「Build Automatically」をチェックして、自動ビルドを有効にします。
    (ビルドが実施されない場合は、メインメニュー「Project」>「Clean」をクリックします。)

実行

ビルドができたら実行します。

  1. メインメニュー「Run」>「Run Configurations」をクリックします。

  2. 「Run Configurations」ダイアログのツリーの「Java Application」をダブルクリックすると、子ノード「New_Configuration」が追加されます。

  3. 「Name」欄をメインクラス名「SampleMain」に変更して、「Apply」ボタンを押すと、子ノード「New_Configuration」が「SampleMain」に更新されます。

  4. 「Main」タブを開いて、「Project」欄にプロジェクト名、「Main class」欄に main メソッドを含むクラス名を入力して「Apply」ボタンを押します。

  5. 「Run Configurations」ダイアログの「Run」ボタンを押すか、アイコンの「Run」ボタンを押します。

  6. 「Console」ウィンドウに、「Hello こんにちは」と表示されます。

デバッグ

デバッグしたい場合は、以下のようにします。

  1. メインメニュー「Run」>「Debug Configurations」をクリックします。

  2. 「Debug Configurations」ダイアログのツリーの「Java Application」をダブルクリックすると、子ノード「New_Configuration」が追加されます。

  3. 「Name」欄をメインクラス名「SampleMain」に変更して、「Apply」ボタンを押すと、子ノード「New_Configuration」が「SampleMain」に更新されます。

  4. 「Main」タブを開いて、「Project」欄にプロジェクト名、「Main class」欄に main メソッドを含むクラス名を入力し、「Stop in main」をチェックして「Apply」ボタンを押します。

  5. 「Debug Configurations」ダイアログのツリーの「Debug」ボタンを押すか、アイコンの「Debug」ボタンを押すとデバッグが開始されます。

  6. 「Stop in main」をチェックしているので、main メソッドの先頭でブレークします。

  7. Ctrl + Shift + b キーでブレークポイントを設定し、F8 キー(実行)を押すと、ブレークポイントを設定した場所でブレークします。
    また、F5 キーでステップイン、F6 キーでステップアウトできます。

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

Eclipse を使って Java プロジェクトを作成する

はじめに

  • Eclipse を使って Java プロジェクトを作成する方法についてまとめました。

動作環境

  • Windows 10(64bit)
  • OpenJDK 11
  • Eclipse IDE for Java Developers(Version: 2020-06)

開発環境構築①:OpenJDK 11 インストール

Eclipse を動かすためには JRE(Java Runtime Environment)が必要になります。ここでは、JRE 取得のために OpenJDK をダウンロードします。JRE をインストールしていないと、Eclipse 起動時にエラーになるので、Eclipse のインストール前に行う必要があります。

  1. 以下の OpenJDK の公式サイトに移動し、「Download」項目内の jdk.java.net/14 のリンクをクリックします。( jdk.java.net/14 のリンクは、OpenJDK がバージョンアップされる度に変わります。)
    https://openjdk.java.net/

  2. 左側のリンク一覧の中の「Java SE 11」をクリックします。(最新版の OpenJDK をダウンロードしたい場合は、このサイトから OpenJDK をダウンロードできます。)
    https://jdk.java.net/14/

  3. 「Windows/x64 Java Development Kit」をクリックします。
    https://jdk.java.net/java-se-ri/11

  4. 「openjdk-11+28_windows-x64_bin.zip」がダウンロードされます。

  5. ダウンロードが完了したら、任意のフォルダに解凍し、「jdk-11\bin」の絶対パスをシステム環境変数に追加します。(ここでは、C:\openjdk に解凍し、「C:\openjdk\jdk-11\bin」をシステム環境変数に追加しました。)

  6. コマンドプロンプトを起動して、Java のバージョン確認を行う。(openjdk 11 であれば OK です。)

    >java --version
    openjdk 11 2018-09-25
    OpenJDK Runtime Environment 18.9 (build 11+28)
    OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)
    

開発環境構築②:Eclipse インストール

OpneJDK のインストールが完了したら、Eclipse をインストールします。

  1. Eclipse のパッケージインストーラのダウンロードサイトに移動し、「Eclipse IDE for Java Developers」内の「Windows 64-bit」をクリックします。
    https://www.eclipse.org/downloads/packages/

  2. ダウンロードページに移動したら、「Download」ボタンをクリックします。

  3. eclipse-java-2020-06-R-win32-x86_64.zip がダウンロードされます。

  4. ダウンロード完了したら、任意のフォルダに解凍し、「eclipse.exe」をダブルクリックします。(起動時に、workspaceフォルダのパスの入力が必要ですが、デフォルト設定のままにします。)

Eclipse 初期設定

プロジェクト作成の前に Eclipse の初期設定を行います。最初に一度設定しておけば次回からは設定不要です。

  1. メインメニュー「Window」>「Preferences」を実行して、「Preferences」ダイアログを起動します。

  2. 左側のツリーから「Java」>「Installed JREs」を選択し、OpenJDK 11 のパスが設定されていることを確認します。

  3. 文字コードを設定します。ここでは、Linux 上でも動かすことを想定して Linux 向けに変更します。左側のツリーから「General」>「Workspace」を選択して、「Text file encoding」で「Other」をチェックし、「UTF-8」を選択します。また、「New text file line delimiter」で「その他」をチェックし、「Unix」を選択し、「Apply」ボタンを押します。

  4. 文字化け対策のため、フォントを変更します。左側のツリーから「General」>「Appearance」>「Colors and Fonts」を選択し、「Java Editor Text-Font」を選択し、「Edit」ボタンを押して、「フォント」ダイアログを起動します。「フォント」ダイアログでフォントを変更し、「Apply」ボタンを押します。(ここでは、MS ゴシックに変更しました。選択するフォントによっては文字化けする場合があるので、文字化けしないフォントを選択して下さい。)

  5. 「Apply and Close」ボタンを押します。

プロジェクト作成

まず、プロジェクトを作成します。

  1. メインメニューの「File」>「New」>「Java Project」をクリックして、「New Java Project」ダイアログを起動します。

  2. 「Project name」欄にプロジェクト名を入力して、「Finish」ボタンを押します。(ここでは、プロジェクト名を「sample」、他の設定はデフォルトのままとします。)

  3. 「New module-info.java」ダイアログが起動したら、「Don't Create」ボタンを押します。

  4. 「Package Explorer」上に sample プロジェクトのツリーが生成されたことを確認します。

メインクラス作成

次に、メインクラス(main メソッドを含むクラス)を作成します。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「New」>「Class」をクリックして、「New Java Class」ダイアログを起動します。

  2. 「Name」欄にクラス名を入力し、「public static void main(String[] args)」にチェックをして「Finish」ボタンを押します。(ここでは、「Name」欄に「SampleMain」と入力します。)

  3. 「Package Explorer」上のツリーに、「sample」>「src」>「sample」>「SampleMain.java」ノードが追加されます。SampleMain.java のコードは以下のようになっています。

    SampleMain.java
    package sample;
    
    public class SampleMain {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
        }
    
    }
    
  4. SampleMain.java のソースコードに任意のコードに追加します。

    SampleMain.java
    package sample;
    
    public class SampleMain {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println("Hello");        // 追加
            System.out.println("こんにちは");  // 追加
        }
    
    }
    

ビルド

ビルドは、自動ビルドを有効にして、自動的にビルドするようにします。

  1. 「Package Explorer」上のツリーのトップノードを選択して右クリックし、ポップアップメニュー「Reflesh」をクリックすると、「Package Explorer」上のツリーのファイル構成が現在の状態に自動的に更新されます。

  2. メインメニュー「Project」>「Build Automatically」をチェックして、自動ビルドを有効にします。(ビルドが実施されない場合は、メインメニュー「Project」>「Clean」をクリックします。)

実行

ビルドができたら実行します。

  1. メインメニュー「Run」>「Run Configurations」をクリックします。

  2. 「Run Configurations」ダイアログのツリーの「Java Application」をダブルクリックすると、子ノード「New_Configuration」が追加されます。

  3. 「Name」欄をメインクラス名「SampleMain」に変更して、「Apply」ボタンを押すと、子ノード「New_Configuration」が「SampleMain」に更新されます。

  4. 「Main」タブを開いて、「Project」欄にプロジェクト名、「Main class」欄に main メソッドを含むクラス名を入力して「Apply」ボタンを押します。

  5. 「Run Configurations」ダイアログの「Run」ボタンを押すか、アイコンの「Run」ボタンを押します。

  6. 「Console」ウィンドウに、「Hello こんにちは」と表示されます。

デバッグ

デバッグしたい場合は、以下のようにします。

  1. メインメニュー「Run」>「Debug Configurations」をクリックします。

  2. 「Debug Configurations」ダイアログのツリーの「Java Application」をダブルクリックすると、子ノード「New_Configuration」が追加されます。

  3. 「Name」欄をメインクラス名「SampleMain」に変更して、「Apply」ボタンを押すと、子ノード「New_Configuration」が「SampleMain」に更新されます。

  4. 「Main」タブを開いて、「Project」欄にプロジェクト名、「Main class」欄に main メソッドを含むクラス名を入力し、「Stop in main」をチェックして「Apply」ボタンを押します。

  5. 「Debug Configurations」ダイアログのツリーの「Debug」ボタンを押すか、アイコンの「Debug」ボタンを押すとデバッグが開始されます。

  6. 「Stop in main」をチェックしているので、main メソッドの先頭でブレークします。

  7. Ctrl + Shift + b キーでブレークポイントを設定し、F8 キー(実行)を押すと、ブレークポイントを設定した場所でブレークします。また、F5 キーでステップイン、F6 キーでステップアウトできます。

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

Oracleから公開されたTribuoを気を取り直してやってみた。Tribuo - A Java prediction library (v4.0)

前置き

いろいろ思うところもあるが、文句だけ言っていても始まらない。
むしろ俺がこれ作るからOracleが俺を雇え。ください。

資料について

元ネタはこちらを参照してください。
日本語が必要ならこちらを参照してください。
データのダウンロードはこちらを参照してください。

どうにかして動かす

bezdekIris.dataをダウンロードします。

マニュアルGatting Startedに記載のサンプルコードをJavaファイルに転記します。
今に始まったことではないので、何も驚きません。そんな変数は定義されていないとエラーで怒られます。

image.png

DataSourceをirisDataと変数定義したのに、引数で渡すときには、irisesSourceに名称が変わってしまったのでしょう。おそらくirisDataのことでしょう。絶対テストやってない。irisDataを引数に渡します。ついでにそんなところにあるコメントは邪魔なので削除します。

image.png
MutableDataset(testData)には、MutableDataset#get()メソッドなど無いと怒られます。Oracleクラスともなれば、メソッドなど無くても呼び出せるのでしょう。絶対にテストry

image.png
testData#hoge#get(0)とつながりそうなのは、#getData()あたりでしょうか。とりあえずgetData()を使ってみます。

image.png

LabelEvaluation#evaluate(Model,MutableDataset)メソッドなど無いと怒られます。今に始まったことではry

image.png
しかし、引数でModel, MutableDatasetを受け取り、Evaluationを返却する似た名前のメソッドすらありません。これで呼び出せるとか天才だと思うわ。さすがオラクryおそらくですが、LabelEvaluationのインスタンスを生成するのではなく、LabelEvaluatorのインスタンスを生成しないといけないですね。きっと。勘ですけど。引数もメソッド名も返却値の型も合いますしおすし。

image.png

うーん。キャストしておけ適当
これで一応エラーは消えました。

TribuoSample
/**
 *
 */
package org.project.eden.adam;

import java.io.IOException;
import java.nio.file.Paths;

import org.tribuo.DataSource;
import org.tribuo.Model;
import org.tribuo.MutableDataset;
import org.tribuo.Prediction;
import org.tribuo.classification.Label;
import org.tribuo.classification.LabelFactory;
import org.tribuo.classification.dtree.CARTClassificationTrainer;
import org.tribuo.classification.evaluation.LabelEvaluation;
import org.tribuo.classification.evaluation.LabelEvaluator;
import org.tribuo.classification.sgd.linear.LogisticRegressionTrainer;
import org.tribuo.data.csv.CSVLoader;
import org.tribuo.evaluation.Evaluation;
import org.tribuo.evaluation.TrainTestSplitter;

/**
 * @author jashika
 *
 */
public class TribuoSample {

    /**
     * @param args mainメソッドの引数。
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        // ラベル付きアヤメ(アイリス)データを読み込む
        var irisHeaders = new String[] { "sepalLength", "sepalWidth", "petalLength", "petalWidth", "species" };
        DataSource<Label> irisData = new CSVLoader<>(new LabelFactory()).loadDataSource(Paths.get("/Users/admin/Downloads/bezdekIris.data"),irisHeaders[4],irisHeaders);

        // アヤメ(アイリス)データをトレーニングセット(70%)とテストセット(30%)に分割
        var splitIrisData = new TrainTestSplitter<>(irisData, 0.7, 1L);

        var trainData = new MutableDataset<>(splitIrisData.getTrain());
        var testData = new MutableDataset<>(splitIrisData.getTest());

        // 決定木を学習する
        var cartTrainer = new CARTClassificationTrainer();
        Model<Label> tree = cartTrainer.train(trainData);

        // ロジスティック回帰
        var linearTrainer = new LogisticRegressionTrainer();
        Model<Label> linear = linearTrainer.train(trainData);

        // 最終的には、目に見えないデータから予測を行う
        // 各予測は、出力名(ラベル)からスコア/確率へのマップ
        Prediction<Label> prediction = linear.predict(testData.getData().get(0));

        // 完全なテストデータセットを評価して、精度、F1などを計算してもよい。
        Evaluation<Label> evaluation = new LabelEvaluator().evaluate(linear, testData);

        // 手動での評価を検査する。
        double acc = LabelEvaluation.class.cast(evaluation).accuracy();

        // フォーマットされた評価文字列を表示する。
        System.out.println(evaluation.toString());
    }
}

実行してみます。
151行目が1エレメントしかない?
image.png

空行・・・。空行無視じゃないの?LF改行だから、何か恨みがあるの?別にこれは無視する対応でよくね?CSVの規約違反でもないし。

image.png

データファイルから消したよ。手動で。
image.png

再度実行。それっぽいのが出た。もう少し突っ込んだところは、後で追記します。多分。
image.png

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

Oracle公開されたTribuoを気を取り直してやってみた。Tribuo - A Java prediction library (v4.0)

前置き

いろいろ思うところもあるが、文句だけ言っていても始まらない。
むしろ俺がこれ作るからOracleが俺を雇え。ください。

資料について

元ネタはこちらを参照してください。
日本語が必要ならこちらを参照してください。
データのダウンロードはこちらを参照してください。

どうにかして動かす

bezdekIris.dataをダウンロードします。

マニュアルGatting Startedに記載のサンプルコードをJavaファイルに転記します。
今に始まったことではないので、何も驚きません。そんな変数は定義されていないとエラーで怒られます。

image.png

DataSourceをirisDataと変数定義したのに、引数で渡すときには、irisesSourceに名称が変わってしまったのでしょう。おそらくirisDataのことでしょう。絶対テストやってない。irisDataを引数に渡します。ついでにそんなところにあるコメントは邪魔なので削除します。

image.png
MutableDataset(testData)には、MutableDataset#get()メソッドなど無いと怒られます。Oracleクラスともなれば、メソッドなど無くても呼び出せるのでしょう。絶対にテストry

image.png
testData#hoge#get(0)とつながりそうなのは、#getData()あたりでしょうか。とりあえずgetData()を使ってみます。

image.png

LabelEvaluation#evaluate(Model,MutableDataset)メソッドなど無いと怒られます。今に始まったことではry

image.png
しかし、引数でModel, MutableDatasetを受け取り、Evaluationを返却する似た名前のメソッドすらありません。これで呼び出せるとか天才だと思うわ。さすがオラクryおそらくですが、LabelEvaluationのインスタンスを生成するのではなく、LabelEvaluatorのインスタンスを生成しないといけないですね。きっと。勘ですけど。引数もメソッド名も返却値の型も合いますしおすし。

image.png

うーん。キャストしておけ適当
これで一応エラーは消えました。

TribuoSample
/**
 *
 */
package org.project.eden.adam;

import java.io.IOException;
import java.nio.file.Paths;

import org.tribuo.DataSource;
import org.tribuo.Model;
import org.tribuo.MutableDataset;
import org.tribuo.Prediction;
import org.tribuo.classification.Label;
import org.tribuo.classification.LabelFactory;
import org.tribuo.classification.dtree.CARTClassificationTrainer;
import org.tribuo.classification.evaluation.LabelEvaluation;
import org.tribuo.classification.evaluation.LabelEvaluator;
import org.tribuo.classification.sgd.linear.LogisticRegressionTrainer;
import org.tribuo.data.csv.CSVLoader;
import org.tribuo.evaluation.Evaluation;
import org.tribuo.evaluation.TrainTestSplitter;

/**
 * @author jashika
 *
 */
public class TribuoSample {

    /**
     * @param args mainメソッドの引数。
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {

        // ラベル付きアヤメ(アイリス)データを読み込む
        var irisHeaders = new String[] { "sepalLength", "sepalWidth", "petalLength", "petalWidth", "species" };
        DataSource<Label> irisData = new CSVLoader<>(new LabelFactory()).loadDataSource(Paths.get("/Users/admin/Downloads/bezdekIris.data"),irisHeaders[4],irisHeaders);

        // アヤメ(アイリス)データをトレーニングセット(70%)とテストセット(30%)に分割
        var splitIrisData = new TrainTestSplitter<>(irisData, 0.7, 1L);

        var trainData = new MutableDataset<>(splitIrisData.getTrain());
        var testData = new MutableDataset<>(splitIrisData.getTest());

        // 決定木を学習する
        var cartTrainer = new CARTClassificationTrainer();
        Model<Label> tree = cartTrainer.train(trainData);

        // ロジスティック回帰
        var linearTrainer = new LogisticRegressionTrainer();
        Model<Label> linear = linearTrainer.train(trainData);

        // 最終的には、目に見えないデータから予測を行う
        // 各予測は、出力名(ラベル)からスコア/確率へのマップ
        Prediction<Label> prediction = linear.predict(testData.getData().get(0));

        // 完全なテストデータセットを評価して、精度、F1などを計算してもよい。
        Evaluation<Label> evaluation = new LabelEvaluator().evaluate(linear, testData);

        // 手動での評価を検査する。
        double acc = LabelEvaluation.class.cast(evaluation).accuracy();

        // フォーマットされた評価文字列を表示する。
        System.out.println(evaluation.toString());
    }
}

実行してみます。
151行目が1エレメントしかない?
image.png

空行・・・。空行無視じゃないの?LF改行だから、何か恨みがあるの?別にこれは無視する対応でよくね?CSVの規約違反でもないし。

image.png

データファイルから消したよ。手動で。
image.png

再度実行。それっぽいのが出た。もう少し突っ込んだところは、後で追記します。多分。
image.png

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

Oracleから公開されたTribuoをやってみた。Tribuo - A Java prediction library (v4.0)

実際にやってみた。
結論から言うととっても不安になった。あと、フレームワークを名乗るなら、英語でいいからjavadocはちゃんと書け。
それから、マニュアル直すのではなく、実装コードをきちんと直したほうがいいぞ。

セットアップ

依存性の解決にはmavenを使用しました。プロジェクト作成後、下記のようにtribuoをpomファイルに設定します。

<dependency>
    <groupId>org.tribuo</groupId>
    <artifactId>tribuo-all</artifactId>
    <version>4.0.0</version>
    <type>pom</type>
</dependency>

ロジスティック回帰モデルを学習し、評価するには下記のようにすればよいと書かれているため、下記のコードをjavaクラスに記述します。

var trainSet = new MutableDataset<>(new LibSVMDataSource("train-data",new LabelFactory()));
var model    = new LogisticRegressionTrainer().train(trainSet);
var eval     = new LabelEvaluator().evaluate(new LibSVMDataSource("test-data",trainSet.getOutputFactory()));

クラス名はTribuoSampleとしました。
は?ナニコレ?

コンストラクタがないエラー01.png

LibSVMDataSourceのコンストラクタ部分です。Stringを引数にとるコンストラクタはないです。

LibSVMDataSourceコンストラクタ.png

しかもこのjava.nio.file.Pathインターフェースって?
java.nio.file.Pathsのほうで実装ではないということは・・・。
もしかして、Path.ofとスタティックにアクセスしているからモダンなのか?

        // こう書けって言ってるの?
        var trainSet = new MutableDataset<>(new LibSVMDataSource<>(Path.of(new URL("file:train-data").getPath()),new LabelFactory()));

        // それともこう?
        var trainSet = new MutableDataset<>(new LibSVMDataSource<>(Paths.get("train-data"),new LabelFactory()));

        // だったらURLでいいんでない?冗長だし。
        var trainSet = new MutableDataset<>(new LibSVMDataSource<>(new URL("file:train-data"),new LabelFactory()));

マニュアル直すか、このクラス本体でStringを受け付けるかになるが、MutableDataset.classを直すべきでしょうね。こんな感じでとりあえずできるでしょうし。

MutableDataset
    public LibSVMDataSource(String url, OutputFactory<T> outputFactory) throws IOException {
        this(null,new URL(url),outputFactory,false,false,0);
    }

    public LibSVMDataSource(String url, OutputFactory<T> outputFactory, boolean zeroIndexed, int maxFeatureID) throws IOException {
        this(null,new URL(url),outputFactory,true,zeroIndexed,maxFeatureID);
    }

ただ、コンストラクタの処理で、urlも、pathもチェックしてないんですよね。
下記がMutableDataset.classの該当コード。

LibSVMDataSource
    private LibSVMDataSource(Path path, URL url, OutputFactory<T> outputFactory, boolean rangeSet, boolean zeroIndexed, int maxFeatureID) throws IOException {
        this.outputFactory = outputFactory;
        this.path = path;
        this.url = url;
        this.rangeSet = rangeSet;
        if (rangeSet) {
            this.zeroIndexed = zeroIndexed;
            this.minFeatureID = zeroIndexed ? 0 : 1;
            if (maxFeatureID < minFeatureID + 1) {
                throw new IllegalArgumentException("maxFeatureID must be positive, found " + maxFeatureID);
            }
            this.maxFeatureID = maxFeatureID;
        }
        read();
    }

どこまで行っちゃうのかっていうと、
コンストラクタから呼ばれるLibSVMDataSource#readの中。
下記が該当のコード。

LibSVMDataSource#read
    private void read() throws IOException {
        int pos = 0;
        ArrayList<HashMap<Integer,Double>> processedData = new ArrayList<>();
        ArrayList<String> labels = new ArrayList<>();

        // Idiom copied from Files.readAllLines,
        // but this doesn't require keeping the whole file in RAM.
        String line;
        // Parse the libsvm file, ignoring malformed lines.
        try (BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream(),StandardCharsets.UTF_8))) {
            for (;;) {
                line = r.readLine();
                if (line == null) {
                    break;
                }
                pos++;
                String[] fields = splitPattern.split(line);
                try {
                    boolean valid = true;
                    HashMap<Integer, Double> features = new HashMap<>();
                    for (int i = 1; i < fields.length && valid; i++) {
                        int ind = fields[i].indexOf(':');
                        if (ind < 0) {
                            logger.warning(String.format("Weird line at %d", pos));
                            valid = false;
                        }
                        String ids = fields[i].substring(0, ind);
                        int id = Integer.parseInt(ids);
                        if ((!rangeSet) && (maxFeatureID < id)) {
                            maxFeatureID = id;
                        }
                        if ((!rangeSet) && (minFeatureID > id)) {
                            minFeatureID = id;
                        }
                        double val = Double.parseDouble(fields[i].substring(ind + 1));
                        Double value = features.put(id, val);
                        if (value != null) {
                            logger.warning(String.format("Repeated features at line %d", pos));
                            valid = false;
                        }
                    }
                    if (valid) {
                        // Store the label
                        labels.add(fields[0]);
                        // Store the features
                        processedData.add(features);
                    } else {
                        throw new IOException("Invalid LibSVM format file");
                    }
                } catch (NumberFormatException ex) {
                    logger.warning(String.format("Weird line at %d", pos));
                    throw new IOException("Invalid LibSVM format file", ex);
                }
            }
        }

tryの中でurl.openStream()ってやっちゃってるし。キャッチするのNumberFormatExceptionだけだしなぁ。
メンバ変数のdescriptionを見ると、urlかpathのどちらかが必須になっているけど。

LibSVMDataSource
    // url is the store of record.
    @Config(description="URL to load the data from. Either this or path must be set.")
    private URL url;

    @Config(description="Path to load the data from. Either this or url must be set.")
    private Path path;

LibSVMDataSource#postConfigで両方nullだったらのチェックはしているけど、これじゃダメじゃん。

LibSVMDataSource#postConfig
    @Override
    public void postConfig() throws IOException {
        if (maxFeatureID != Integer.MIN_VALUE) {
            rangeSet = true;
            minFeatureID = zeroIndexed ? 0 : 1;
            if (maxFeatureID < minFeatureID + 1) {
                throw new IllegalArgumentException("maxFeatureID must be positive, found " + maxFeatureID);
            }
        }
        if ((url == null) && (path == null)) {
            throw new PropertyException("","path","At most one of url and path must be set.");
        } else if ((url != null) && (path != null) && !path.toUri().toURL().equals(url)) {
            throw new PropertyException("","path","At most one of url and path must be set");
        } else if (path != null) {
            // url is the store of record.
            try {
                url = path.toUri().toURL();
            } catch (MalformedURLException e) {
                throw new PropertyException(e,"","path","Path was not a valid URL");
            }
        }
        read();
    }

こんなコード書いたら誰も処理しないよね。

TribuoSample
public class TribuoSample {

    /**
     * @param args mainメソッドの引数。
     */
    public static void main(String[] args) {

        URL url = null;

        try {
            var trainSet = new MutableDataset<>(
                    new LibSVMDataSource<>(url, new LabelFactory()));
        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }
    }
}

これを実行すると・・・。

StackTrace
Exception in thread "main" java.lang.NullPointerException
    at org.tribuo.datasource.LibSVMDataSource.read(LibSVMDataSource.java:204)
    at org.tribuo.datasource.LibSVMDataSource.<init>(LibSVMDataSource.java:125)
    at org.tribuo.datasource.LibSVMDataSource.<init>(LibSVMDataSource.java:105)
    at org.project.eden.adam.TribuoSample.main(TribuoSample.java:28)

この場合、利用者に伝えたいメッセージは、「urlかpathのいずれかが設定されていることは必須の項目なんだけど、
あなたがurlに設定した値にはnullが設定されていたよ。」ってことを伝えなければならないはずなんだけど、想定していないエラーだかスタックトレース吐き出して処理が止まってしまうわけだ。業務アプリならまだしも、oracleの名前でだしたフレームワークなのだから、このような落ち方はいかがなものかと思う。pathの場合には、コンストラクタでpathオブジェクトにアクセスしちゃうから、その場でヌルポだしね。

これがその実装

LibSVMDataSource
    public LibSVMDataSource(Path path, OutputFactory<T> outputFactory) throws IOException {
        this(path,path.toUri().toURL(),outputFactory,false,false,0);
    }

サンプルを下記のようにして実行。

TribuoSample
public class TribuoSample {

    /**
     * @param args mainメソッドの引数。
     */
    public static void main(String[] args) {

        Path path = null;

        try {
            var trainSet = new MutableDataset<>(
                    new LibSVMDataSource<>(path, new LabelFactory()));

        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            //e.printStackTrace();
        }
    }
}

結果は見るまでもなく、NullPointerException。

Exception in thread "main" java.lang.NullPointerException
    at org.tribuo.datasource.LibSVMDataSource.<init>(LibSVMDataSource.java:97)
    at org.project.eden.adam.TribuoSample.main(TribuoSample.java:28)

ドキュメントのトップページに出てくる、1行目からこんなになってるとは思わなかった。
サンプル実行は別のページで。

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

Oracle公開されたTribuoをやってみた。Tribuo - A Java prediction library (v4.0)

実際にやってみた。
結論から言うととっても不安になった。あと、フレームワークを名乗るなら、英語でいいからjavadocはちゃんと書け。
それから、マニュアル直すのではなく、実装コードをきちんと直したほうがいいぞ。

セットアップ

依存性の解決にはmavenを使用しました。プロジェクト作成後、下記のようにtribuoをpomファイルに設定します。

<dependency>
    <groupId>org.tribuo</groupId>
    <artifactId>tribuo-all</artifactId>
    <version>4.0.0</version>
    <type>pom</type>
</dependency>

ロジスティック回帰モデルを学習し、評価するには下記のようにすればよいと書かれているため、下記のコードをjavaクラスに記述します。

var trainSet = new MutableDataset<>(new LibSVMDataSource("train-data",new LabelFactory()));
var model    = new LogisticRegressionTrainer().train(trainSet);
var eval     = new LabelEvaluator().evaluate(new LibSVMDataSource("test-data",trainSet.getOutputFactory()));

クラス名はTribuoSampleとしました。
は?ナニコレ?

コンストラクタがないエラー01.png

LibSVMDataSourceのコンストラクタ部分です。Stringを引数にとるコンストラクタはないです。

LibSVMDataSourceコンストラクタ.png

しかもこのjava.nio.file.Pathインターフェースって?
java.nio.file.Pathsのほうで実装ではないということは・・・。
もしかして、Path.ofとスタティックにアクセスしているからモダンなのか?

        // こう書けって言ってるの?
        var trainSet = new MutableDataset<>(new LibSVMDataSource<>(Path.of(new URL("file:train-data").getPath()),new LabelFactory()));

        // それともこう?
        var trainSet = new MutableDataset<>(new LibSVMDataSource<>(Paths.get("train-data"),new LabelFactory()));

        // だったらURLでいいんでない?冗長だし。
        var trainSet = new MutableDataset<>(new LibSVMDataSource<>(new URL("file:train-data"),new LabelFactory()));

マニュアル直すか、このクラス本体でStringを受け付けるかになるが、MutableDataset.classを直すべきでしょうね。こんな感じでとりあえずできるでしょうし。

MutableDataset
    public LibSVMDataSource(String url, OutputFactory<T> outputFactory) throws IOException {
        this(null,new URL(url),outputFactory,false,false,0);
    }

    public LibSVMDataSource(String url, OutputFactory<T> outputFactory, boolean zeroIndexed, int maxFeatureID) throws IOException {
        this(null,new URL(url),outputFactory,true,zeroIndexed,maxFeatureID);
    }

ただ、コンストラクタの処理で、urlも、pathもチェックしてないんですよね。
下記がMutableDataset.classの該当コード。

LibSVMDataSource
    private LibSVMDataSource(Path path, URL url, OutputFactory<T> outputFactory, boolean rangeSet, boolean zeroIndexed, int maxFeatureID) throws IOException {
        this.outputFactory = outputFactory;
        this.path = path;
        this.url = url;
        this.rangeSet = rangeSet;
        if (rangeSet) {
            this.zeroIndexed = zeroIndexed;
            this.minFeatureID = zeroIndexed ? 0 : 1;
            if (maxFeatureID < minFeatureID + 1) {
                throw new IllegalArgumentException("maxFeatureID must be positive, found " + maxFeatureID);
            }
            this.maxFeatureID = maxFeatureID;
        }
        read();
    }

どこまで行っちゃうのかっていうと、
コンストラクタから呼ばれるLibSVMDataSource#readの中。
下記が該当のコード。

LibSVMDataSource#read
    private void read() throws IOException {
        int pos = 0;
        ArrayList<HashMap<Integer,Double>> processedData = new ArrayList<>();
        ArrayList<String> labels = new ArrayList<>();

        // Idiom copied from Files.readAllLines,
        // but this doesn't require keeping the whole file in RAM.
        String line;
        // Parse the libsvm file, ignoring malformed lines.
        try (BufferedReader r = new BufferedReader(new InputStreamReader(url.openStream(),StandardCharsets.UTF_8))) {
            for (;;) {
                line = r.readLine();
                if (line == null) {
                    break;
                }
                pos++;
                String[] fields = splitPattern.split(line);
                try {
                    boolean valid = true;
                    HashMap<Integer, Double> features = new HashMap<>();
                    for (int i = 1; i < fields.length && valid; i++) {
                        int ind = fields[i].indexOf(':');
                        if (ind < 0) {
                            logger.warning(String.format("Weird line at %d", pos));
                            valid = false;
                        }
                        String ids = fields[i].substring(0, ind);
                        int id = Integer.parseInt(ids);
                        if ((!rangeSet) && (maxFeatureID < id)) {
                            maxFeatureID = id;
                        }
                        if ((!rangeSet) && (minFeatureID > id)) {
                            minFeatureID = id;
                        }
                        double val = Double.parseDouble(fields[i].substring(ind + 1));
                        Double value = features.put(id, val);
                        if (value != null) {
                            logger.warning(String.format("Repeated features at line %d", pos));
                            valid = false;
                        }
                    }
                    if (valid) {
                        // Store the label
                        labels.add(fields[0]);
                        // Store the features
                        processedData.add(features);
                    } else {
                        throw new IOException("Invalid LibSVM format file");
                    }
                } catch (NumberFormatException ex) {
                    logger.warning(String.format("Weird line at %d", pos));
                    throw new IOException("Invalid LibSVM format file", ex);
                }
            }
        }

tryの中でurl.openStream()ってやっちゃってるし。キャッチするのNumberFormatExceptionだけだしなぁ。
メンバ変数のdescriptionを見ると、urlかpathのどちらかが必須になっているけど。

LibSVMDataSource
    // url is the store of record.
    @Config(description="URL to load the data from. Either this or path must be set.")
    private URL url;

    @Config(description="Path to load the data from. Either this or url must be set.")
    private Path path;

LibSVMDataSource#postConfigで両方nullだったらのチェックはしているけど、これじゃダメじゃん。

LibSVMDataSource#postConfig
    @Override
    public void postConfig() throws IOException {
        if (maxFeatureID != Integer.MIN_VALUE) {
            rangeSet = true;
            minFeatureID = zeroIndexed ? 0 : 1;
            if (maxFeatureID < minFeatureID + 1) {
                throw new IllegalArgumentException("maxFeatureID must be positive, found " + maxFeatureID);
            }
        }
        if ((url == null) && (path == null)) {
            throw new PropertyException("","path","At most one of url and path must be set.");
        } else if ((url != null) && (path != null) && !path.toUri().toURL().equals(url)) {
            throw new PropertyException("","path","At most one of url and path must be set");
        } else if (path != null) {
            // url is the store of record.
            try {
                url = path.toUri().toURL();
            } catch (MalformedURLException e) {
                throw new PropertyException(e,"","path","Path was not a valid URL");
            }
        }
        read();
    }

こんなコード書いたら誰も処理しないよね。

TribuoSample
public class TribuoSample {

    /**
     * @param args mainメソッドの引数。
     */
    public static void main(String[] args) {

        URL url = null;

        try {
            var trainSet = new MutableDataset<>(
                    new LibSVMDataSource<>(url, new LabelFactory()));
        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }
    }
}

これを実行すると・・・。

StackTrace
Exception in thread "main" java.lang.NullPointerException
    at org.tribuo.datasource.LibSVMDataSource.read(LibSVMDataSource.java:204)
    at org.tribuo.datasource.LibSVMDataSource.<init>(LibSVMDataSource.java:125)
    at org.tribuo.datasource.LibSVMDataSource.<init>(LibSVMDataSource.java:105)
    at org.project.eden.adam.TribuoSample.main(TribuoSample.java:28)

この場合、利用者に伝えたいメッセージは、「urlかpathのいずれかが設定されていることは必須の項目なんだけど、
あなたがurlに設定した値にはnullが設定されていたよ。」ってことを伝えなければならないはずなんだけど、想定していないエラーだかスタックトレース吐き出して処理が止まってしまうわけだ。業務アプリならまだしも、oracleの名前でだしたフレームワークなのだから、このような落ち方はいかがなものかと思う。pathの場合には、コンストラクタでpathオブジェクトにアクセスしちゃうから、その場でヌルポだしね。

これがその実装

LibSVMDataSource
    public LibSVMDataSource(Path path, OutputFactory<T> outputFactory) throws IOException {
        this(path,path.toUri().toURL(),outputFactory,false,false,0);
    }

サンプルを下記のようにして実行。

TribuoSample
public class TribuoSample {

    /**
     * @param args mainメソッドの引数。
     */
    public static void main(String[] args) {

        Path path = null;

        try {
            var trainSet = new MutableDataset<>(
                    new LibSVMDataSource<>(path, new LabelFactory()));

        } catch (IOException e) {
            // TODO 自動生成された catch ブロック
            //e.printStackTrace();
        }
    }
}

結果は見るまでもなく、NullPointerException。

Exception in thread "main" java.lang.NullPointerException
    at org.tribuo.datasource.LibSVMDataSource.<init>(LibSVMDataSource.java:97)
    at org.project.eden.adam.TribuoSample.main(TribuoSample.java:28)

ドキュメントのトップページに出てくる、1行目からこんなになってるとは思わなかった。
サンプル実行は別のページで。

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

【IntelliJ IDEA】Javaファイル保存時にfinalを自動的につける方法

やりたいこと

  • Javaのコードを書くときに、immutableにしたい変数には、finalを付けておきたい
  • ただ毎回finalを書くのは大変なので、ファイル保存時に自動的にfinalを付けるようにしたい

環境

  • IntelliJ IDEA 2020.2.2
  • Mac OS Catalina 10.15.7

やり方

Save Actions プラグインを利用するので、「IntelliJ IDEA」 -> 「Preferences」 -> 「Plugins」 を押下して、Save Actionsをインストール
スクリーンショット 2020-09-27 0.19.02.png

「IntelliJ IDEA」 -> 「Preferences」 -> 「Other Settings」 -> 「Save Actions」を押下して、
以下の項目にチェックを入れ、「Apply」, 「OK」を押下

  • Activate save actions on save (...)
  • Add final modifier to field
  • Add final modifier to local variable or parameter
    • (もしくは Add final modifier to local variable or parameter except if it is implicit)

スクリーンショット 2020-09-27 0.20.34.png

これで、「Cmd+s」を実行するとfinalが自動的に付与されるようになります。
sample1.gif

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