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

【Java】URLを入力するとそのページが使っているWordPressのテーマ名を表示するプログラムを作ってみた

はじめに

28歳業界未経験、プログラミング素の人です。

Qiita初投稿!!

現在Javaの研修を受けているので学んだことの備忘録として、
あと同じプログラミング初心者の方々のお役に少しでも立てればと思いQiitaに投稿することにしました。

お見苦しい点多々あるかと思いますが、生暖かい目で見てやってください。

この記事について

webページ見てたら「このサイトwordpress使ってんのかな?」「何のテーマ使ってるんやろ?」って思うことが結構あるので、勉強がてら自分なりにJavaで書いてみました。
コマンド上で動く、ブラウザからURLをコピペして実行するだけで結果が表示される簡単な奴です。

方法を考える

HTMLソースコードの<head>内にwp-contentという文字列が含まれているかでWordPressを使っているかチェックします。
テーマ名はwp-content/themes/の後に書かれているので、ソースコード内の一番初めに出てくるwp-content/themes/の部分を取得すれば分かりそうです。

html取得 → Listに代入 → for文で一行ずつチェック

という流れでやってみました。

サンプルコード

Sample.java
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;

public class Sample {
    private List<String> contents;
    private final String CHARSET = "UTF-8";

    public static void main(String[] args) {
        String inputUrl = "";
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        Sample sample = new Sample();

        try {
            inputUrl = reader.readLine();
            sample.addContents(inputUrl);
            sample.checkWordpress();
        } catch (IOException e) {
            System.out.println(e);
        }
    }

    // 指定したページがWordpressを使っているか確かめるメソッド
    public void checkWordpress() {
        boolean WpCheck = false;

        for (String content : getContents()) {
            // headタグ内に "wp-content/themes/" が含まれているかで判定
            if (content.contains("wp-content/themes/") == true) {
                int idx = content.indexOf("wp-content/themes/");
                String line = content.substring(idx + 18); // "wp-content/themes/"以降の文字列
                idx = line.indexOf("/"); // テーマ名直後の "/" のインデックス番号
                System.out.println("\nテーマ名 : " + line.substring(0, idx)); // テーマ名を出力
                WpCheck = true;
                break;
            }
        }
        if (WpCheck == true) {
            System.out.println("Wordpressを使っています。\n");
        } else {
            System.out.println("\nWordpressは使われていません。");
        }
    }

    // 指定されたURLのソースコードを取得するメソッド
    public List<String> download(String url, String charset) throws Exception {

        URLConnection conn = new URL(url).openConnection();
        InputStream is = conn.getInputStream();

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset))) {
            ArrayList<String> lineList = new ArrayList<String>();
            String line = null;
            while ((line = reader.readLine()) != null) {
                lineList.add(line);
            }
            return lineList;
        }
    }

    // ダウンロードしたソースコードをListに代入するメソッド
    public void addContents(String url) {
        try {
            this.contents = download(url, getCharset());
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("無効なURLです。");
            System.exit(0);
        }
    }

    public List<String> getContents() {
        return this.contents;
    }
    public String getCharset() {
        return this.CHARSET;
    }
}



【実行結果】
qiita.png

試しに「lightning」の公式ページのURLを指定しました。
テーマ名が取得できました。

解説

標準入力でURLを指定して、http通信で取得したhtmlソースコードをListに代入しています。
checkWordpressメソッドで、Listに代入したhtmlソースコードを一行ずつfor文でチェックします。
if文で"wp-content/themes/"がソースコードに含まれているか判定。
"wp-content/themes/"のインデックス番号を取得。
テーマ名を取得したいので、"wp-content/themes/"の文字数(18文字)を足したインデックス番号以降の文字列を取得。
テーマ名直後の "/" のインデックス番号を取得して、0番目から"/"までの文字列を抜き出せばテーマ名が分かります。

最後に

もっと効率の良い方法があると思いますが、今の自分にはこれが精一杯でした笑
そもそもブラウザでページのソースを表示したらすぐに分かるんですけど、勉強のためにやってみました。
自分が欲しい機能を作るのは、楽しいし達成感あるしめっちゃ調べるので勉強になります。


終わり

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

Google App Engine Java 8 から Java11への移行 実践編

はじめに

この記事は、Google App Engine Java 8 から Java11 移行対策 の続きです。
Java11からサーブレット非対応になったGoogle App Engine / Java (GAE/J)でJava8のサーブレットをGAE/J Java11 でも稼働可能なSpring Bootアプリへ置き換える具体的な方法を記述している。

Java8 サーブレットから Java11 Spring Bootへ

今回はサンプルとして、GAE/J Java8のガイドに記載されているHello World サーブレットを Spring Boot化してGAE/J Java11にデプロイしてみることにする。

前提条件

この記事は、Java8版のGAE/JのソースをJava11版に書き換えることを主眼とするため、Java 11やCloud SDKのインストールは終了しているものとする。
さらにGAEへデプロイ可能なプロジェクトも作成済みとする。
また、WebフレームワークとしてSpring Bootを使用しているが、Spring FrameworkやSpring Bootの知識もあるものとする。

前提とする環境

  • Java 11 (OpenJDK 11)
  • Apache Maven 3.6
  • Google Cloud SDK 319
  • Spring Boot 2.4
  • Maven 3.6

GAE/J Java8のHello World取得

GAE/J Java8のサンプルは、下記のGitレポジトリーから取得できる

git clone https://github.com/GoogleCloudPlatform/appengine-try-java

取得したファイルは以下のようになっている

一部省略
|--pom.xml
|--src
|  |--main
|  |  |--java
|  |  |  |--myapp
|  |  |  |  |--DemoServlet.java
|  |  |--webapp
|  |  |  |--index.html
|  |  |  |--WEB-INF
|  |  |  |  |--appengine-web.xml
|  |  |  |  |--web.xml

web.xmlの中身を見てみよう

web.xml
<!DOCTYPE web-app PUBLIC
 "-//Oracle Corporation//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
    <servlet>
        <servlet-name>demo</servlet-name>
        <servlet-class>myapp.DemoServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>demo</servlet-name>
        <url-pattern>/demo</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

DemoServletというサーブレットとindex.htmlというHTMLがあるだけということである。

DemoServlet.java
package myapp;

import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class DemoServlet extends HttpServlet {
  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp)
      throws IOException {
    resp.setContentType("text/plain");
    resp.getWriter().println("{ \"name\": \"World\" }");
  }
}
index.html
<!doctype html>
<html>
  <head>
    <title>App Engine Demo</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
  </head>
  <body>
    <div id="result">Loading...</div>

    <script>
$(document).ready(function() {
  $.getJSON('/demo', function(data) {
    $('#result').html("Hello, " + data.name);
  });
});
    </script>
  </body>
</html>

index.htmlがDemoServletを呼び出して{"name":"World"}というJSONを受け取り、Hello WorldとDIVに表示している。
最後にappengine-web.xmlを確認する。

appengine-web.xml
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <threadsafe>true</threadsafe>
    <runtime>java8</runtime>
</appengine-web-app>

ランタイムがJava8であるということが書かれている。
なお、GAE/JのJava11版のSpring Bootを使用したHello Worldは、下記Gitから取得できる。この中のappengine-java11/springboot-helloworldが、ほぼほぼ完成形となるので、手っ取り早くスケルトンを確認したい方は、こちらを参照していただきたい。

git clone https://github.com/GoogleCloudPlatform/java-docs-samples

GAE/J Java11 と Spring Bootの体裁を整える

まずは、Mavenのコンパイルが通る形にファイル構成の体裁を整えよう

pom.xml 修正

とりあえず、Mavenの依存関係やbuild用のpluginを GAE/JとSpring Bootに対応したものに書き換えてしまおう。
もちろん、本来は自分のアプリケーションに必要な依存関係を追加する必要があるので、適時追加していただきたい。

pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project>
    <modelVersion>4.0.0</modelVersion>
    <version>2.0</version>
    <groupId>com.google.appengine.demos</groupId>
    <artifactId>appengine-try-java</artifactId>

    <properties>
        <maven.compiler.target>11</maven.compiler.target>
        <maven.compiler.source>11</maven.compiler.source>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <!-- Import dependency management from Spring Boot -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.4.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR9</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
            <!-- Exclude the Tomcat dependency -->
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jetty</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.4.0</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>com.google.cloud.tools</groupId>
                <artifactId>appengine-maven-plugin</artifactId>
                <version>2.4.0</version>
                <configuration>
                    <skip>true</skip>
                    <version>2.0.0</version>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

<plugin>com.google.cloud.tools</plugin>の中の<version>2.0.0</version>の部分だが、これはGAEにデプロイされるバージョンを表す。このバージョンをJava8版と違う値にしておけば、旧バージョンのGAEに残ったままの状態となり、いざというときに切り戻しができるようになる。
ここで、とりあえずmvn compileしておけば、依存するJarファイルがダウンロードできるはずだ。

mvn compile

app.yaml 作成

GAE Java11では、appengine-web.xml は廃止になり、設定はyamlファイルに一本化されたので、app.yamlファイルを作成する。
ファイルの保存場所は以下の通り。
src/main/appengine/app.yaml
必要最小限のapp.yamlファイルの記述は下記の通り。とりあえず、Runtimeが java11であることが書かれていれば後はデフォルト値で稼働する。

app.yaml
runtime: java11

app.yamlの全設定は app.yaml 構成ファイルを参照。

エンドポイント作成

最後にSpring Bootのエンドポイントとなるクラスを作成する。
DemoServlet.javaと同じパッケージに新しいクラスDemoAppを作成しよう。

DemoApp.java
package myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApp {

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

}

スケルトン完成

とりあえず中身は全くないが、これで体裁は整った。
DemoServlet.javaとWEB-INF以下のファイルは不要なので消してしまおう。
結果的に下記のようなフォルダ構成になるはずだ。
なお、webapp以下のファイルはMavenでビルドすれば、Spring Bootでも静的ファイルとして扱われるのでそのまま放置しておいてよい。

|--pom.xml
|--src
|  |--main
|  |  |--appengine
|  |  |  |--app.yaml
|  |  |--java
|  |  |  |--myapp
|  |  |  |  |--DemoApp.java
|  |  |--webapp
|  |  |  |--index.html

これでSpring BootのアプリケーションとしてMavenのコンパイルも通るはずだ。

mvn install

Servlet移行

ここからが本番。いよいよ、サーブレットをSpring Boot対応のクラスに書き換えよう。

DemoServlet 書き換え

DemoAppクラスに下記のようなメソッドを書き加えれば、/demoというGETリクエストに対して、{"name":"World"}というJSON文字列を返すエンドポイントを作成することができる。
一目瞭然だが、@GetMapping アノテーションがメソッドがGetリクエストのエンドポイントであることを表しており、引数の"/demo"の部分がURLのマッピングである。
これで完成と言えば完成だ。

DemoApp.java(一部)
  @GetMapping("/demo")
  public String hello() {
    return "{ \"name\": \"World\" }";
  }

ただし、もっとSpring Bootっぽくするならば、戻り値にBeanにすべきである。Beanを戻せば、フレームワークで自動的にJSON文字列のレスポンスを生成してくれる。
これらを踏まえた最終的なDemoApp.javaの全ソースは下記のようになる。

DemoApp.java
package myapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApp {

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

  @GetMapping("/demo")
  public Hello hello() {
    Hello hello = new Hello();
    hello.name = "World";
    return hello;
  }

  public class Hello {
    public String name;
  }
}

テストサーバ起動

テストを行うためには、下記のコマンドを実行すれば良い。
ブラウザで http://localhost:8080 を開けば、Hello, Worldの文字が表示されるはずである。

mvn spring-boot:run

デプロイ

最後にデプロイを行おう。この部分はJava8版と変わりない。
デプロイを行うGclooudのMavenのゴールもプラグインによって読み込まれているので、下記コマンドを実行すればGAEサーバーにデプロイされるはずだ。

mvn appengine:deploy

最後に

最後のMavenコマンドで、GAEにデプロイできたはずである。
最後により実践的な参考情報を列挙する。

サーブレットマッピング

今回はサーブレットをSpring Boot形式に全面的に書き換えるたが、サーブレットクラスには手を加えず、Spring Bootから直接呼び出す方法もある。この方法はSpring Boot Servletマッピング などの記事を参考にしてほしい。
特にJava8の段階で、すでにWeb.xmlではなく@WebServletアノテーションでマッピングをしているのであれば、エンドポイントのクラスに@ServletComponentScanを付加するだけで移行は完了する。

リクエストパラメータ/ボディ

今回のサンプルはリクエストパラメータやボディ情報の必要ないサーブレットだったが、実際には必ずパラメータなどの受け取りが必要になるはずだ。
Spring Boot (厳密にはSpring MVCの機能となるが)で、リクエストパラメータやボディを受け取る方法は、Spring MVC コントローラの引数 といった記事が参考になるだろう。

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

SpringBootことはじめ 2.gradleで実行可能なwarファイルを作る

はじめに

前回、SpringMVCでHelloWorldのREST APIを作成したので
これを実行可能なwarにアーカイブし、実際に実行してみるところまでやってみる。

環境

ソフトウェア バージョン
OS Windows10 Pro
Java JDK12
Eclipse 2020-12 M1
SpringBoot 2.4.0
Spring Tool Suite (STS) 4.8.1

実施

0. 前回作成したHelloWorld REST API アプリ

HelloController.java
@RestController
public class HelloController {

    @RequestMapping("/")
    public String home() {
        return "Hello World!";
    }

    @RequestMapping("/sb")
    public String helloSp() {
        return "Hello SpringBoot!";
    }

}
HelloSpringApplication.java
package com.example.demo;

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

@SpringBootApplication
public class HelloSpringApplication {

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

}

warを起動してここのエンドポイントにアクセスするのがゴール。

1. Gradleにwarプラグインを追加、bootWarタスク実行

実行可能なwarを作るにはgradleにwarプラグインを追加すればいい。
ついでに生成されるwarファイル名を指定する。
bootWarタスクarchiveNameで指定できる。

build.gradle
plugins {
    id 'org.springframework.boot' version '2.4.0'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
    id 'eclipse'
    id 'war' //★ここを追加★
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '12'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

// ★warファイル名を指定★
bootWar {
  archiveName 'HelloSpringApp.war'
}

warプラグインを追加してgradle tasksでタスクを確認すると
bootWarが追加されるのでそれを実行。

image.png

bootWar実行
M:\develop\repository\git\practice2\HelloSpring>gradle bootWar

BUILD SUCCESSFUL in 41s
4 actionable tasks: 4 executed

成功すればプロジェクトのbuild/libs配下にwarファイルが生成される。

image.png

2. warファイルの起動

作成したwarファイルをjava -jar {warファイル名}で起動できる。

M:\develop\repository\git\practice2\HelloSpring\build\libs>java -jar HelloSpringApp.war

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

2020-11-25 23:19:40.149  INFO 13388 --- [           main] com.example.demo.HelloSpringApplication  : Starting HelloSpringApplication using Java 12.0.2 on xxxxx with PID 13388 (M:\develop\repository\git\practice2\HelloSpring\build\libs\HelloSpringApp.war started by xxxx in M:\develop\repository\git\practice2\HelloSpring\build\libs)
2020-11-25 23:19:40.151  INFO 13388 --- [           main] com.example.demo.HelloSpringApplication  : No active profile set, falling back to default profiles: default
2020-11-25 23:19:41.212  INFO 13388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-11-25 23:19:41.223  INFO 13388 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-11-25 23:19:41.223  INFO 13388 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.39]
2020-11-25 23:19:41.684  INFO 13388 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-11-25 23:19:41.685  INFO 13388 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1489 ms
2020-11-25 23:19:41.829  INFO 13388 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-11-25 23:19:41.968  INFO 13388 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-11-25 23:19:41.976  INFO 13388 --- [           main] com.example.demo.HelloSpringApplication  : Started HelloSpringApplication in 2.181 seconds (JVM running for 2.504)

この状態で以下にアクセスしてみる。

  • http://localhost:8080/
    image.png

  • http://localhost:8080/sb
    image.png

やったぜ。:vulcan:

tomcatを内包してるのでこのwarアーカイブをそこらへんのサーバにデプロイすれば
そのままWebアプリとして公開できる。
(こんな単純なアプリは無いだろうけど)

mainクラスを指定するmanifestはgradleに書かなかったけど
よしなに@SpringBootApplicationのクラスを探してくれるっぽい?

まとめ

とりあえずアーカイブ化の方法は取得完了。
後々のCI/CDのCD部分で役に立つ・・・といいなぁ。

参考

Spring Boot + Gradleでwarファイルを作成する方法

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

【Java】インターフェイスを実装するimplementsについて

プログラミング勉強日記

2020年11月25日
インターフェイスを継承してクラスを定義するときに必要なimplementsについてまとめる。

implementsとは

 implementsの説明をする前にインターフェイスの説明を簡単にする。インターフェイスは、メソッドの戻り値とメソッド名、引数といった使用のみを定義したもので、実際の処理を持たないクラスのようなものである。intefaceを使って型宣言を行うことはできるが、メソッドの定義がないとプログラムは実行できないので、そこでimplementsを使う。つまり、インターフェイスに定義されているメソッドを定義しているクラスであると指定する。
 仕様を言い換えると約束事で、約束通りの使い方をすると約束通りの結果になるということをあらかじめ決めておくことで、そのメソッドを使う側は実際の作りを気にしなくてよく、そのメソッドを作る側は仕様さえ守ればいつでも好きなように実装を変えることができる。

implementsの書き方

 あるクラスにインターフェイスを実装するには下記のように、クラス名の後ろにimplementsを書き、さらにその後ろにインターフェイス名を書く。インターフェイスに抽象メソッドが定義されているならクラスではその抽象メソッドの内容を実際に作る抽象メソッドの実装をしないといけない。(実装しないと、コンパイルエラーになる。)
 抽象クラスにインターフェイスを実装する場合は、インターフェイスの抽象メソッドの実装は必要ない。

implementsの書き方
class クラス名 implements インターフェイス名 {
}

// 抽象メソッドが定義されている場合
class クラス名 implements インターフェイス名 {
 インターフェイスの抽象メソッド() {
 抽象メソッドの実装;
 }
}

 インターフェイスを複数implementsすることもでき、その場合はimplementsの後ろに実装したいインターフェイスを,(カンマ)で続ける。この場合は、クラスで実装したいインターフェイスにあるすべての抽象メソッドを作成しないといけない

インターフェイスを複数implementsする場合
class クラス名 implements インターフェイス名1, インターフェイス名2, ... {
 インターフェイス1の抽象メソッド() {
 抽象メソッドの実装;
 }

 インターフェイス2の抽象メソッド() {
 抽象メソッドの実装;
 }

}

サンプルコード

 ボタンをクリックするごとにボタンのテキストの表示が変わるプログラムを作成した。

 アクションやイベントの処理に関連するクラスでは、ActionListenerというインターフェイスを実装する。そうしたクラスによって作成されたオブジェクトは、アクションやイベントが発生するとコンポーネントのaddActionListenerメソッドが呼び出される。

package Sample;

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;

public class Sample extends JFrame implements ActionListener{
    private static final long serialVersionUID = 1L; 
    private JLabel L1;
    private JButton b1;

    public Sample() {
        setLayout(null); 
        this.setSize(200, 200);
        this.setDefaultCloseOperation(EXIT_ON_CLOSE); 
        L1 = new JLabel("テスト用ボタン");
        b1 = new JButton("Click me");
        L1.setBounds(50, 10, 100, 20);
        b1.setBounds(new Rectangle(50, 80, 90, 30));
        b1.addActionListener(this);

        this.add(L1);
        this.add(b1);
        this.setVisible(true);
    }

    public void actionPerformed(ActionEvent ev) {
        if(b1.getText() == "ON") 
            b1.setText("OFF");
        else 
            b1.setText("ON");

        System.out.println("you clicked");
    }

    public static void main(String[] args) {
        new Sample();
    }

}

参考文献

よくわかる! Javaのインターフェイスを実装する”implements”
インターフェースを実装!Javaでimplementsを使う方法【初心者向け】

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

CASE式を使って検索項目が空欄の場合に条件を無視する

if文を使わず、SQLのCASE式でnullを無視した検索を実行する

以下のような形で検索に使用する入力項目がいくつもあった場合に、コントローラー側でif文を使って空欄時とそうでないときの場合分けをするとif/elseがずらーっと並ぶ上、同じようなメソッドをいくつも使ってコードが汚くなるので、SQLでどうにかできないかといろいろ調べてみたところ、SQLのCASE式を使えばif文を使わずに、よりスマートにコードがかけることがわかったので備忘録として残します。
スクリーンショット 2020-11-25 20.12.23.png

repository.java
    @Query(value = "SELECT * FROM mst_asset" 
                 + " WHERE id = CASE WHEN :id = 0 THEN id ELSE :id END" 
                 + " AND category_id = CASE WHEN :categoryId = 0 THEN category_id ELSE :categoryId END" 
                 + " AND admin_name LIKE concat('%', CASE WHEN :adminName = '' THEN admin_name ELSE :adminName END, '%')"
                 + " AND asset_name LIKE concat('%', CASE WHEN :assetName = '' THEN asset_name ELSE :assetName END, '%')" 
                 + " AND delete_flag = false ORDER BY id ASC"
                 +  , nativeQuery = true)
    List<Asset> findByIdAndCategoryIdAndAdminNameAndAssetName(
        @Param("id") Integer id,
        @Param("categoryId")Integer categoryId,
        @Param("adminName") String adminName,
        @Param("assetName") String assetName);

どうやら、WHERE句の[カラム名]=[カラム名]の場合には条件としてカウントしないという性質があるらしくそれを、CASE式に応用することで画面で検索キーワードが入力されなかったパラメータを無視してSQLを実行することができるらしい。
上記のコードでは、Integer型の入力欄のパラメーターに関しては空欄のままだとnullとして受け取ってしまうので、その場合はサービスの中でif文を使って0にセットしてます。String型のパラメーターに関しては、入力された際にはそれをサービスの中でトリミングしてconcatを使って部分一致検索にしてます。

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

CASE式を使って検索項目が空欄の場合の条件無視にする

if文を使わず、SQLのCASE式でnullを無視した検索を実行する

以下のような形で検索に使用する入力項目がいくつもあった場合に、コントローラー側でif文を使って空欄時とそうでないときの場合分けをするとコードが汚くなるので、SQLでどうにかできないかといろいろ調べてみたところ、SQLのCASE式を使えばif文を使わなくてよりスマートにコードがかけることがわかったので備忘録として残します。
スクリーンショット 2020-11-25 20.12.23.png

repository.java
    @Query(value = "SELECT * FROM mst_asset" 
                 + " WHERE id = CASE WHEN :id = 0 THEN id ELSE :id END" 
                 + " AND category_id = CASE WHEN :categoryId = 0 THEN category_id ELSE :categoryId END" 
                 + " AND admin_name LIKE concat('%', CASE WHEN :adminName = '' THEN admin_name ELSE :adminName END, '%')"
                 + " AND asset_name LIKE concat('%', CASE WHEN :assetName = '' THEN asset_name ELSE :assetName END, '%')" 
                 + " AND delete_flag = false ORDER BY id ASC"
                 +  , nativeQuery = true)
    List<Asset> findByIdAndCategoryIdAndAdminNameAndAssetName(
        @Param("id") Integer id,
        @Param("categoryId")Integer categoryId,
        @Param("adminName") String adminName,
        @Param("assetName") String assetName);

どうやら、WHERE句の[カラム名]=[カラム名]の場合には条件としてカウントしないという性質があるらしくそれを、CASE式に応用することで画面で検索キーワードが入力されなかったパラメータを無視してSQLを実行することができるらしい。
上記のコードでは、Integer型の入力欄のパラメーターに関しては空欄のままだとnullとして受け取ってしまうので、その場合はサービスの中でif文を使って0にセットしてます。String型のパラメーターに関しては、入力された際にはそれをサービスの中でトリミングしてconcatを使って部分一致検索にしてます。

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

【Java】アスタリスクのピラミッド

アスタリスクのピラミッド

Javaを使って入力した数の団だけアスタリスクでピラミッドを作成する、「 アスタリスクのピラミッド 」プログラムを作ったメモ。

完成品はこちら

【10段分のピラミッド】
         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************

共通点を探す

このピラミッドがどのように作られているかを考えるために、「ピラミッドが何段になっても変わらない共通点」や、どういう規則で作られているかを考えるため、上記の10段のものに加え、3段で作ったピラミッドを作成して比較してみます。

【3段分のピラミッド】
  *
 ***
*****

ピラミッドの段数が10段でも、3段でも変わらない部分を考えてみると

  • アスタリスクの数は何段になっても奇数
  • 最上段は アスタリスク が必ず1個になる
  • 最下段は 空白 が必ず0になる
  • 一段増えるごとにアスタリスクの数は2つずつ増える
  • 一段増えるごとに 空白 の数は 1つずつ減る。
  • 一段に入力される空白の数は、(段数 - 1)

という部分が考えられます。

【3段の場合のピラミッドの場合】

  *    ← 最上段 ( "*" が必ず1個になる)
 ***   ← (一段増えるごとに 空白は1つずつ減り、アスタリスクは2つずつ増える)
*****  ← 最下段 ( " " が必ず0になる)
12345  ← 横マスの数 

やりたいことを文章化

やるべきことが見えたら、それを文章化して

  1. 空白の数を ( 段数 - 1 ) 個表示させる
  2. その横に アスタリスクを ( 段数 * 2 - 1 )個表示させる
  3. 改行する
  4. 1〜3の流れを段数分繰り返す ※(ただし、段数は前回のループに1つずつ足した数にする。)

これらを実現するために ループ文 を使いました。
つまり上記のリストの 1~3の処理を、段数の数だけループしてあげれば実現できそうです。
またループ内の処理も for文を使うことで効率良く書くことができそうなので、そちらの処理も 「多重for文」を使うと良さそうです。

つまり、4のfor文で、1~3の処理をくるむ形で作ることが出来ます。

①. ②~③の処理を 段の数だけループさせるためのループ分を作る。

  • n段のピラミッドを作る場合、iは 0から1段のため「 i < n 」と記述。
  • ①~②の処理 を内側に入れてループさせる。
for (int i = 0; i < lineCount; i++) {
    // ①~②の処理...
    ....
    ....
    }

②. 空白の数を ( 段数 - 1 ) 個表示させる

見るとピラミッドは 余白( 以下: space ) の数が、上から1つずつ減っていってます。
space は先程の3段ピラミッドを見てみると、「( 段数 - 1 )個出力されている」ことが見えてきます。
上のコードで使用した 「ループを重ねるごとに一つづつ増えていく i 」を使ってやれば、1ずつ減るというわけです。

  • space の初期値は 0 のため、予め1を足しておく。
  • 空白 (" ")は上から一つずつ減る

【Javaでのコード】

for (int space = 0; space < dan - i ; space++) {
    System.out.print(" ");
}

③. その横に アスタリスクを ( 段数 * 2 - 1 )個表示させ、改行する

【書き方】

  • その横に アスタリスクを ( 段数 * 2 - 1 )個表示させる
  • アスタリスクの数は何段になっても奇数
  • 一段増えるごとにアスタリスクの数は2つずつ増える

【Javaでのコード】

for (int j = 0; j <i * 2 - 1; j++) {
    System.out.print("*"); 
}

System.out.println(); //改行

を段数分繰り返す ※(ただし、段数は前回のループに1つずつ足した数にする。)

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

【Effective Java】抽象概念に適した例外をスローする

Effective Javaの独自解釈です。
第3版の項目73について、自分なりにコード書いたりして解釈してみました。

概要

  • 下位レイヤーの例外はそのまま出力させるのではなく、上位レイヤーの概念に応じた例外に翻訳することで以下のメリットが得られる。
    • ログ調査しやすくなる
    • さらに上位のレイヤーでの例外ハンドリングがしやすくなる
  • 実装で例外発生を確実に防げるなら、翻訳は考えず例外発生そのものを防ぐ。

説明

以下の環境で実装したコードで説明する。

  • Java 11
  • Doma 2.19.2
  • Spring Boot 2.3.3
  • MySQL 5.7

DB挿入時における例外出力の例

DBにUSERテーブルがあるとし、レコードを新規追加することを考える。
USERテーブルには主キーとしてidカラムを持っている。

Java側の関連クラスは以下の通り。(importなど略)

Entity

public class User {

    private String id;

    private String name;
}

Dto

public class CreateUser {

    private String id;

    private String name;
}

Daoインタフェース

public interface UserDao {

    @Insert
    int insert(User user);
}

下位レイヤーの例外をそのまま出力させた場合

既にUSERテーブルに存在するidを持つcreateUserを引数に入れ、以下のサービス層のメソッドでinsertを試み例外を発生させる。

public User createUser(CreateUser createUser) {

    User user = new User();
    user.setId(createUser.getId());
    user.setName(createUser.getName());

    userDao.insert(user);

    return user;
}

このとき、標準エラーには以下が出力される。

〜略〜
nested exception is org.springframework.dao.DuplicateKeyException: [DOMA2004] 一意制約違反により更新処理が失敗しました。
SQLファイルパス=[null]。
ログ用SQL=[]。
詳しい原因は次のものです。{2}; nested exception is org.seasar.doma.jdbc.UniqueConstraintException: [DOMA2004] 一意制約違反により更新処理が失敗しました。
SQLファイルパス=[null]。
ログ用SQL=[]。
詳しい原因は次のものです。{2}] with root cause

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'A01' for key 'PRIMARY'
〜略〜

Daoレイヤーから出力されたDuplicateKeyExceptionがそのままcreateUserメソッドを突き抜けてスローされている。

弊害としては以下の通り。

  • スタックトレースをたどればcreateuserメソッドで主キー重複例外が出てるから、USERのID重複エラーだなと一応わかるが、ひと目ではわかりにくい。
  • 上位レイヤーのコントローラ層で、Entityに応じたエラーハンドリング処理をしたい場合、DuplicateKeyExceptionが投げられると、どのEntityに対しての例外なのか判別するために、ややこしい文字列解析などする必要が出てくる。

上位レイヤーの例外に置き換えた場合

createUserメソッドの主な関心事はDBの制約ではなく、Userそのものである。ということでUserに着目した独自例外を作ってやり、それに翻訳する処理を施す。

独自例外

public class UserDuplicatedIdException extends Exception {

    public UserDuplicatedIdException(String message, Throwable cause) {
        super(message, cause);
    }
}

Primary KeyというDB寄りの言葉は使わず、あくまでUseridというUser寄りの言葉で例外クラスを作る。
createUserメソッドは以下のように、DuplicateKeyExceptionからUserDuplicatedIdExceptionへの翻訳処理を実装する。

public User createUser(CreateUser createUser) throws UserDuplicatedIdException {

    User user = new User();
    user.setId(createUser.getId());
    user.setName(createUser.getName());

    try {
        userDao.insert(user);
    } catch (DuplicateKeyException e) {
        throw new UserDuplicatedIdException("Can't create already existing id user.", e);
    }

    return user;
    }

先程の例と同じようにid重複のUSERテーブルレコードを挿入しようとすると、以下の標準エラー出力が出力される。

〜略〜
nested exception is com.demo.domain.exceptions.UserDuplicatedIdException: Can't create already existing id user.] with root cause

java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'A01' for key 'PRIMARY'
〜略〜

createUserからは、このレイヤーの関心事であるUserに関する例外UserDuplicatedIdExceptionが出力される。

利点は先程とは逆で以下の通り。

  • 例外クラスを見ただけでどのEntityで何が起こったかがひと目で分かる。
  • コントローラ層では、catchした例外クラスに応じて楽に分岐処理を実装できる。

また、障害やデバッグ時にログ調査をする際も、目的の例外クラスでgrepをかけられるので捗る。

例外翻訳は乱用しない

先述の例で、USERidNullで登録しようとすると、以下の標準エラーが出力される。

〜略〜
nested exception is org.springframework.dao.DataIntegrityViolationException: [DOMA2009] SQLの実行に失敗しました。
SQLファイルパス=[null]。
ログ用SQL=[]。
原因は次のものです。java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null。
根本原因は次のものです。java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null; SQL [insert into USER (id, name) values (?, ?)]; Column 'id' cannot be null; nested exception is java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null] with root cause

java.sql.SQLIntegrityConstraintViolationException: Column 'id' cannot be null
〜略〜

なるほど、この場合はDataIntegrityViolationExceptionを別の例外に翻訳すればいいんだなと思うかもしれないが、それはNGである。

insert時のDBの中身はクライアント側で制御できないが、insertしようとするEntityは制御可能である。
コントローラ層でリクエストのid指定がない場合はバリデーションで弾くなり、代替値を入れるなりすれば、この例外は100%発生しないので、翻訳処理も必要なくなる。

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

State Pattern

あるオブジェクトの状態をクラス化する。サンプルでは、水クラスの状態を、氷、液体、気体という状態を想定し、水クラス、液体クラス、気体クラスとする

Design Pattarm MENU

以下のクラス構成で確認します

クラス 説明
Water.class 水を表すクラス
水の状態は、固体、液体、気体の3つとする
waterState.interface 水の状態を共通化するインターフェイス
Ice.class 状態が固体であることを表すクラス
Liquid.class 状態が液体であることを表すクラス
Air.class 状態が液体であることを表すクラス
user(Main.class) 乱数で水の状態を変化させる

*user 他の開発者がこのパターンを利用する、という意味合いを含みます

Water.class
class Water{
  waterState state = new Liquid();
  void setState(waterState state){
       this.state=state;
  }
  void show(){System.out.println(state.showState());}
}
waterState_interface
interface waterState{String showState();}
Ice.class
class Ice implements waterState{
  public String showState() {return "Ice";}
}
Liquid.class
class Liquid implements waterState{
  public String showState() {return "Liquid";
  }
}
Air.class
class Air implements waterState{
  public String showState() {return "Air";}
}
user(Main.class)
public static void main(String[] args){
  Water water = new Water();
  Random rand = new Random();
  for(int i=0;i<10;i++){
    int r = rand.nextInt(30);
    if(r%5==0){water.setState(new Ice());}else{
    if(r%3==0){water.setState(new Liquid());}else{
       water.setState(new Air());
    }}
    water.show();
  }
}}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CFRでclassファイルをデコンパイル

経緯

javaでカバレッジ上げろと言われてもどこを通ってないのかさっぱりわからなかったので、コンパイル時にどう展開されているのか調べたかった。

解決方法

デコンパイルしてclassファイルを確認すればいい。日本語のサイトだとJADというツールが引っかかるが、これは古いのでCFRというデコンパイラを使う。

試してみる

シンプルなtry-with-resourcesがどう展開されているのかを確認する。

  • Hoge.java
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.stream.Collectors;

public class Hoge {
    public String config;
    private Hoge() {
        try (BufferedReader br = new BufferedReader(new FileReader("config"))) {
            config = br.lines().collect(Collectors.joining("\n"));
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}
  • デコンパイルする
$ javac Hoge.java
$ java -jar cfr-0.150.jar --tryresources false --decodefinally false Hoge.class > HogeHoge.java
  • 結果
/*
 * Decompiled with CFR 0.150.
 */
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.stream.Collectors;

public class Hoge {
    public String config;

    private Hoge() {
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader("config"));
            try {
                this.config = bufferedReader.lines().collect(Collectors.joining("\n"));
            }
            catch (Throwable throwable) {
                try {
                    bufferedReader.close();
                }
                catch (Throwable throwable2) {
                    throwable.addSuppressed(throwable2);
                }
                throw throwable;
            }
            bufferedReader.close();
        }
        catch (IOException iOException) {
            iOException.printStackTrace();
        }
    }
}

余談

カバレッジの確認でjacocoを利用していたが、jacocoのバージョンを上げたら余計な分岐の確認をしなくなったのか、無理してでコンパイルしなくても網羅できるようになった。

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

【Java・SpringBoot・Thymeleaf】バリデーションエラーメッセージを変更(SpringBootアプリケーション実践編4)

ログインをして、ユーザー一覧を表示するアプリケーションを作成し、
Springでの開発について勉強していきます?
前回のエラーメッセージ実装に引き続き、メッセージプロパティファイルを複数用意して切り替える方法を実装します

前回の記事?
【Java・SpringBoot・Thymeleaf】エラーメッセージを実装(SpringBootアプリケーション実践編3)

ユーザー登録画面用のフォームクラスにバリデーション実装

  • フォームクラスの各フィールドにアノテーションを付けるだけ!で入力チェックが出来る
  • NotNull・NotEmpty・NotBlankの違い
アノテーション null 空文字 空白
@NotNull NG OK OK
@NotEmpty NG NG OK
@NotBlank NG NG NG
SignupForm.java
package com.example.demo.login.domain.model;
import java.util.Date;
import javax.validation.constraints.AssertFalse;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;
import org.springframework.format.annotation.DateTimeFormat;
import lombok.Data;

@Data
public class SignupForm {
    //入力形式、メールアドレス形式
    @NotBlank
    @Email
    private String userId; //ユーザーID

    //入力必須、長さ4-100桁まで、半角英数字のみ
    @NotBlank
    @Length(min= 4 ,max = 100)
    @Pattern(regexp = "^[a-zA-Z0-9]+$")
    private String password; //パスワード

    //入力必須
    @NotBlank
    private String userName; //ユーザー名

    //入力必須
    @NotNull
    @DateTimeFormat(pattern = "yyyy/MM/dd")
    private Date birthday; //誕生日

    //値は20-100
    @Min(20)
    @Max(100)
    private int age; //年齢

    //falseのみ可能
    @AssertFalse
    private boolean marriage; //結婚ステータス

}

コントローラークラス修正

バリデーションの実施

  • 引数のフォームクラスに@Validatedアノテーションを付ける
    • バリデーションのチェック結果はBindingResultクラスに入る
    • バリデーションを使う場合、BindingResultクラスを引数に設定する必要がある
public String postSignUp(@ModelAttribute @Validated SignupForm form,
                         BindingResult bindingResult,
                         Model model) {...}

@Validated@Valid

  • @Validated:Spring標準のアノテーション。今回は@Validatedを使用
  • @Valid:J2EEから標準で搭載されているBeanValidator(バリデーション用のアノテーション)
    メッセージプロパティファイル名、エラーメッセージの書き方が違う
SignupController.java
package com.example.demo.login.controller;

import java.util.LinkedHashMap;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.demo.login.domain.model.SignupForm;

@Controller
public class SignupController {

    //ラジオボタン用変数
    private Map<String, String> radioMarriage;

    //ラジオボタンの初期化メソッド
    private Map<String, String> initRadioMarrige() {
        Map<String, String> radio = new LinkedHashMap<>();
        // 既婚、未婚をMapに格納
        radio.put("既婚", "true");
        radio.put("未婚", "false");
        return radio;
    }

    //ユーザー登録画面のGET用コントローラ
    @GetMapping("/signup")
    public String getSignUp(@ModelAttribute SignupForm form, Model model) {

        // ラジオボタンの初期化メソッド呼び出し
        radioMarriage = initRadioMarrige();
        // ラジオボタン用のMapをModelに登録
        model.addAttribute("radioMarriage", radioMarriage);
        // signup.htmlに画面遷移
        return "login/signup";
    }

    //ユーザー登録画面のPOST用コントローラ
    //バリデーション実装
    @PostMapping("/signup")
    public String postSignUp(@ModelAttribute @Validated SignupForm form,
                             BindingResult bindingResult,
                             Model model) {

        // 入力チェックに引っかかった場合、ユーザー登録画面に戻る
        if (bindingResult.hasErrors()) {
            // GETリクエスト用のメソッドを呼び出して、ユーザー登録画面に戻る
            return getSignUp(form, model);
        }

        // formの中身をコンソールで確認
        System.out.println(form);
        // login.htmlにリダイレクト
        return "redirect:/login";
    }
}

SpringBootを起動、新規登録画面を確認!

  • http://localhost:8080/login
  • ユーザー登録画面で何も入力せずユーザー登録ボタンを押すと、エラーメッセージが表示されました〜〜
  • 今のままでは英語のメッセージなので、次回は入力チェックのエラーメッセージを編集して日本語化します^o^

バリデーション.png

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

Java テキストファイル(.txt .csv)からの入力 java.io.FileReader

1,データを文字列としてテキストファイルへ保存する方法

➊FileReader fr = new FileReader("ファイル名");

➋Buffered br = BufferedReader(br);

➌String rec; rec = br.readLine();

➍fr.close()

2,ソースコード(テキストファイルからの入力)

テキストファイルから文字の入力をおこなうためには、read()メソッドを使う

04.png

レコード単位(行単位)による効率のよい入力処理 readLine()メソッドを使う

テキストファイル
05.png
csvファイル
06.png

split()メソッドを使って、csvファイルのレコードを配列に格納する方法

07.png

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