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

Eclipseでビルドした.warファイルをEC2上に配置して実行してみるとUnsupportedClassVersionErrorが出力された

環境

実行環境

AWS EC2 t2.micro
Amazon Linux 2
Apache Tomcat/9.0.34

# java -version
openjdk version "1.8.0_252-debug"
OpenJDK Runtime Environment (build 1.8.0_252-debug-b09)
OpenJDK 64-Bit Server VM (build 25.252-b09-debug, mixed mode)

# javac -version
javac 1.8.0_252-debug

ビルド環境(ローカル環境)

Windows10
Eclipse 2020-03

Windowsコマンドプロンプトで確認したJavaのバージョン

> java -version
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)

> javac -version
javac 1.8.0_231

経緯

Webアプリ開発の勉強のため、Eclipse上で動的Webプロジェクト「example」を作成し、
サーブレット&JSPでサンプルアプリケーション「HealthCheck」を作成した。
ブラウザから「http://localhost:8080/example/HealthCheck」で正常動作を確認。
ローカルではちゃんと動く。

EC2にデプロイして動くか確認したかったため、Eclipse上でプロジェクトごと
.warファイルにエクスポートしEC2上へ配置。

[root@ip-10-0-0-50 webapps]# ls -l /opt/apache-tomcat/webapps
total 24
drwxr-x--- 16 tomcat tomcat  4096 Apr 24 15:28 docs
drwxr-x---  5 tomcat tomcat   123 Jun  3 12:55 example
drwxr-x---  6 tomcat tomcat    83 Apr 24 15:28 examples
-rwxr-xr-x  1 tomcat tomcat 18910 Jun  3 12:55 example.war        ←★
drwxr-x---  5 tomcat tomcat    87 Apr 24 15:28 host-manager
drwxr-x---  5 tomcat tomcat   103 Apr 24 15:28 manager
drwxr-x---  3 tomcat tomcat   283 Apr 24 15:28 ROOT

[root@ip-10-0-0-50 webapps]# ls -l /opt/apache-tomcat/webapps/example
total 16
drwxr-x--- 2 tomcat tomcat  31 Jun  3 12:55 css
-rw-r----- 1 tomcat tomcat 761 May 27 00:08 Ex5_2.jsp
-rw-r----- 1 tomcat tomcat 493 May 26 23:31 formSample.jsp
-rw-r----- 1 tomcat tomcat 140 May 25 21:06 hello.html
drwxr-x--- 2 tomcat tomcat  44 Jun  3 12:55 META-INF
-rw-r----- 1 tomcat tomcat 670 May 26 18:38 sample.jsp
drwxr-x--- 5 tomcat tomcat  43 Jun  3 12:55 WEB-INF

tomcat.serviceのリスタートを行い、ステータスチェック

[root@ip-10-0-0-50 webapps]# systemctl status tomcat.service
● tomcat.service - Apache Tomcat Web Application Container
   Loaded: loaded (/usr/lib/systemd/system/tomcat.service; enabled; vendor preset: disabled)
   Active: active (exited) since Wed 2020-06-03 13:12:27 UTC; 1h 4min ago
  Process: 4100 ExecStop=/opt/apache-tomcat/bin/shutdown.sh (code=exited, status=0/SUCCESS)
  Process: 4129 ExecStart=/opt/apache-tomcat/bin/startup.sh (code=exited, status=0/SUCCESS)
 Main PID: 4129 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/tomcat.service
           mq4144 /usr/bin/java -Djava.util.logging.config.file=/opt/apache-tomcat/conf/loggin...

Jun 03 13:12:27 ip-10-0-0-50.ap-northeast-1.compute.internal systemd[1]: Starting Apache Tomca...
Jun 03 13:12:27 ip-10-0-0-50.ap-northeast-1.compute.internal systemd[1]: Started Apache Tomcat...
Hint: Some lines were ellipsized, use -l to show in full.

発生事象

EC2にアタッチしたEIP宛てにブラウザから接続したところ「500 – Internal Server Error」が発生
http://[EIP]:8080/example/HealthCheck


HTTPステータス 500 – Internal Server Error

タイプ: 例外報告
メッセージ: Error instantiating servlet class [servlet.HealthCheck]
説明:サーバーは予期しない条件に遭遇しました。それはリクエストの実行を妨げます。
例外:
 javax.servlet.ServletException: Error instantiating servlet class [servlet.HealthCheck]
   (中略)
根本原因:
 java.lang.UnsupportedClassVersionError: servlet/HealthCheck has been compiled by a more recent version of the Java Runtime (class file version 55.0), this version of the Java Runtime only recognizes class file versions up to 52.0 (unable to load class [servlet.HealthCheck])
   (中略)
注意: 原因のすべてのスタックトレースは、サーバのログに記録されています

Apache Tomcat/9.0.34

調査

Javaのバージョン差異によるもの?

この「UnsupportedClassVersionError」は、

Java仮想マシンが、クラス・ファイルの読込み中に、そのファイルのメジャー・バージョン番号と
マイナー・バージョン番号がサポートされていないと判定した場合にスローされます。

というもの。

つまり、ビルド環境と実行環境のJavaのバージョン差異が原因と考えられる。

冒頭の「環境」に記載した通りビルド環境のjavacのバージョンはjavac 1.8.0_231
実行環境のjavaのバージョンはopenjdk version "1.8.0_252-debug"
となっているため、問題無さそう・・・

コンパイルされたclassファイルのバージョンを調べてみる

[root@ip-10-0-0-50 webapps]# javap -v ./example/WEB-INF/classes/servlet/HealthCheck.class
Classfile /opt/apache-tomcat-9.0.34/webapps/example/WEB-INF/classes/servlet/HealthCheck.class
  Last modified Jun 4, 2020; size 1988 bytes
  MD5 checksum 83f908e6070439cc6281b73baae5306d
  Compiled from "HealthCheck.java"
public class servlet.HealthCheck extends javax.servlet.http.HttpServlet
  minor version: 0
  major version: 55                                   ←★
  flags: ACC_PUBLIC, ACC_SUPER

メジャーバージョンが" 55 "になってる!!

一方で、実行環境のJREバージョンは1.8。こちらのサイト様によると、
JREバージョン1.8がサポートするクラスファイルバージョンは"52"とのこと。

エラーの内容が把握できました。

で、冒頭の「ビルド環境」のところで確認したJavacのバージョンは、Windows全体として確認した
(スタートメニューから開いたcmdで確認した)結果であって、Eclipse上でプロジェクトの
右クリックメニューに出てくる「コマンドプロンプト」で確認できる内容とは異なることが分かった。

>javac -version
javac 11.0.5

D:\31_Pleiades\workspace\example>java -version
openjdk version "11.0.5" 2019-10-15
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)

これを見ると、javacのバージョンは"11.0.5"となっている。
今使っているEclipseは「Pleiades All in One Eclipse」であり、このプラグインの中には複数バージョンのJDKが入っている。

Windowsには別口で導入したJDK1.8が存在し、パスはそちらに通っているため冒頭の確認では気づけなかった。

Eclipseでビルドする際に使用されるjavacは"11.0.5"だったために、今回の問題が発生したというわけ。
ビルドする際に使用するjavacのバージョンを"1.8"に下げることができれば今回の問題はクリアできそう。

対処

ビルド時のJDKバージョンを下げてみる

まずはEclipse全体の設定を確認

プロジェクト固有設定としてバージョンを下げてみる

リビルドしてクラスファイルのバージョンを確認する

>javap -v HealthCheck.class
Classfile /D:/31_Pleiades/workspace/example/build/classes/servlet/HealthCheck.class
  Last modified 2020/06/04; size 1988 bytes
  MD5 checksum 5deb7a7fe4b855e66be9d06261116bd6
  Compiled from "HealthCheck.java"
public class servlet.HealthCheck extends javax.servlet.http.HttpServlet
  minor version: 0
  major version: 52                       ←★成功
  flags: ACC_PUBLIC, ACC_SUPER

改めて.warファイルにエクスポート→サーバ配置→アクセスしてみる

・・・
・・・
・・・

成功!!

というわけで、Javaのバージョンはしっかり確認しましょう!というお話でした。。。

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

Javaの参照の仕組み(スタックとヒープ)

自社の後輩にJavaの参照の仕組みをスタック領域とJavaヒープ領域を用いて説明する機会がありました。
自分自身、この領域同士の関係性をまだ知らなかった頃はJavaの参照について色々と混乱していた記憶があります。
良い機会なので記事にまとめてみようと思います。

Javaの参照を実現する仕組み

Javaの参照を実現する為にJVMにはスタック領域とJavaヒープ領域が存在します。
この2つの領域によりJavaの参照は実現されます。

領域名 概要
スタック領域 主にヒープ領域への参照情報を保持します。またプリミティブ型の値も保持します。
JAVAヒープ領域 オブジェクトの実際の値はこちらのメモリに保持します。

オブジェクトとプリミティブ型の値の保持方法の違い

Javaのオブジェクト(Classや配列など)はスタック領域にヒープ領域の参照情報を持ちます。
実際の値はヒープ領域に保持します。
オブジェクトの参照イメージ

プリミティブ型はオブジェクトとは異なるメモリ管理が行われます。
プリミティブ型の変数を作成するとスタック領域に値を保持します。
※ただしプリミティブ型の配列はオブジェクトとして扱われる為ヒープ領域に値を保持します。
プリミティブ型のメモリ管理イメージ.png

メソッドの引数に渡す時の挙動

メソッド引数へオブジェクトやプリミティブ型の変数を渡す際
スタック領域の内容が別のスタック領域にコピーされます。
引数へ渡した時の挙動.png
上記の様な場合、argAの参照は変数aと同じになる為
method1の内部で参照先の値を変更すると変数aにも影響を与えます。
※argBはプリミティブ型なので変数bとは切り離されています。

ソースコードサンプル

ここまで記載した内容が正しいか確認するため、ソースコードとその出力結果を照らし合わせて説明していきます。

例1

ソースコード
package test;

class Class1 {
    String id;
    int value;
}

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

        Class1 a = new Class1();
        a.id = "ID1";
        a.value = 1;

        // 操作【1】
        Class1 b = a;
        // 操作【2】
        b.id = "ID2";
        b.value = 2;

        // 操作【3】
        b = null;
        // ここで出力される値は何になるか?
        System.out.println("id:" + a.id);
        System.out.println("value:" + a.value);
    }
}

【操作内容】
例1.png

操作【2】により変数aの参照先の値も書き換えていることになります。
操作【3】により変数bの参照は削除されますがスタックが異なる変数aには影響はありません。
よって出力内容は下記の通りとなります。

出力結果
id:ID2
value:2

例2

ソースコード
package test;

class Class1 {
    String id;
    int value;
}

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

        Class1 a = new Class1();
        a.id = "ID1";
        a.value = 1;

        // 操作【1】
        method1(a);
        // ここで出力される値は何になるか?
        System.out.println("id:" + a.id);
        System.out.println("value:" + a.value);
    }

    private static void method1(Class1 argA) {
        // 操作【2】
        argA.id = "ID2";
        argA.value = 2;
    }
}

【操作内容】
例2.png
操作【2】により変数aの参照先の値も書き換えていることになります。
よって出力内容は下記の通りとなります。

出力結果
id:ID2
value:2

例3

ソースコード
package test;

class Class1 {
    String id;
    int value;
}

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

        Class1 a = new Class1();
        a.id = "ID1";
        a.value = 1;

        // 操作【1】
        method1(a);
        // ここで出力される値は何になるか?
        System.out.println("1回目=========");
        System.out.println("id:" + a.id);
        System.out.println("value:" + a.value);

        // 操作【2】
        method2(a);
        // ここで出力される値は何になるか?
        System.out.println("2回目=========");
        System.out.println("id:" + a.id);
        System.out.println("value:" + a.value);
    }

    private static void method1(Class1 argA) {
        argA = null;
    }

    private static void method2(Class1 argA) {
        argA = new Class1();
        argA.id = "ID2";
        argA.value = 2;
    }
}

【操作内容】
例2と同じ様にメソッドの引数として変数aを渡しています。
操作【1】のメソッドはnullを代入してargAの参照を削除していますがスタックが異なる変数aに影響はありません。
操作【2】のメソッドは新たにClass1をnewしているため参照先が変数aと変わっています。
参照先が変わった後にidとvalueに値を設定しても変数aに影響はありません。
よって出力内容は下記の通りとなる。

出力結果
1回目=========
id:ID1
value:1
2回目=========
id:ID1
value:1

例4

ソースコード
package test;

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

        int a = 1;

        // 操作【1】
        int b = a;
        // 操作【2】
        b = 99;

        // ここで出力される値は何になるか?
        System.out.println("1回目=========");
        System.out.println("a:" + a);
        System.out.println("b:" + b);

        // 操作【3】
        method1(a);

        // ここで出力される値は何になるか?
        System.out.println("2回目=========");
        System.out.println("a:" + a);
    }

    private static void method1(int argA) {
        // 操作【4】
        argA = 99;
    }
}

【操作内容】
スクリーンショット 2020-06-02 0.58.52.png
プリミティブ型の変数はそれぞれ独立してスタック領域に値を保持します。
それぞれの操作が別の変数に影響を及ぼす事はありません。
よって出力内容は下記の通りとなる。

出力結果
1回目=========
a:1
b:99
2回目=========
a:1

例5

ソースコード
package test;

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

        int a = 0;

        // 操作【1】
        int[] b = {a};

        // 操作【2】
        method1(b);

        // ここで出力される値は何になるか?
        System.out.println("a:" + a);
        System.out.println("b[0]:" + b[0]);
    }

    private static void method1(int[] argB) {
        // 操作【3】
        argB[0] = 99;
    }
}

【操作内容】
例5.png
プリミティブ型でも配列として保持するとその値はヒープ領域に保存されます。
操作【3】で配列argBに変更を行うと同じ参照先を持つ配列bに影響を与えます。
よって出力内容は下記の通りとなる。

出力結果
a:0
b[0]:99
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GoogleMapsAPIのJavaSDKを使って、逆GeoCodingの結果を日本語で取得する。

はじめに

GoogleMapsAPIを使って、緯度経度から住所を取得する処理を書きました。
GoogleからJavaのSDKが提供されています。
日本語で住所を返す設定のメモ書きです。

JavaのSDK

  • Java Client for Google Maps Services

https://github.com/googlemaps/google-maps-services-java

ソースコード

    public String reverceGeocoding(double lat, double lon) {
        LatLng latlng = new LatLng(lat, lon);

        GeoApiContext context = new GeoApiContext.Builder().apiKey(apikey).build();
        GeocodingApiRequest request = GeocodingApi.reverseGeocode(context, latlng).language("ja");
        GeocodingResult[] results;
        String address = null;
        try {
            results = request.await();
            Gson gson = new GsonBuilder().setPrettyPrinting().create();
            address = gson.toJson(results[0].formattedAddress);
        } catch (ApiException | InterruptedException | IOException e) {
            e.printStackTrace();
        } finally {
            context.shutdown();
        }
        return address;
    }

おわりに

作ってしまえばなんてことないのですが、languageの指定方法は調査しました。覚え書きとして残しておきます。

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

Gradleでdocker環境を構築する方法 for intelliJ

spring-boot-doma2-sampleリポジトリを参考にしました。
build.gradleを新しい書き方にしました。

Spring Initializr

まずは下記のサイトでspringアプリケーションを作成します。
https://start.spring.io/

アプリケーションで使用するプラグインをDependenciesに追加します。

  • Spring boot DevTools
  • Spring Web
  • Thymeleaf
  • Spring Data JDBC
  • Flyway Migration
  • MySQL Driver

最後に下部にあるGENERATEをクリックし、ダウンロードされzipファイルを解凍して適当なディレクト名に変更します。

スクリーンショット 2020-06-01 22.51.44.png

initializrではdocker-composeが出てこないので、build.gradleに追加します。

intelliJでプロジェクトを開く

オープンまたはインポートで、前述の名前を変えたディレクトリを選択します。
スクリーンショット 2020-06-01 23.10.05.png

build.gradleを変更する

プロジェクト内のbuild.gradleを開きます。
以下のようになっているので、// 追加の部分を追記してください。

build.gradle
plugins {
    id 'java'
    id 'org.springframework.boot' version '2.3.1.BUILD-SNAPSHOT'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
    id 'com.avast.gradle.docker-compose' version '0.12.1' // 追加
    id 'org.flywaydb.flyway' version '6.4.3'
}

// ...略

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.flywaydb:flyway-core'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}

gradleのプラグインを調べるサイト→Search Gradle plugins

スクリーンショット 2020-06-01 22.45.33.png

スクリーンショット 2020-06-01 22.46.05.png

dockerの設定

dockerディレクトリをプロジェクト内に作成し、docker-compose.ymlを作成します。

docker-compose.yml
version: '2'
services:
    todo-db:
        build: ./mysql  # Dockerfileがあるディレクトリを指定する
        environment:
            - MYSQL_DATABASE=todo
            - MYSQL_ROOT_USER=root
            - MYSQL_ROOT_PASSWORD=passw0rd
            - TZ=Japan
        image: docker_todo-db
        container_name: todoDb
        ports:
            - "33068:3306"  # 33068をすでに使っている場合は別の番号に変更する

mysqlディレクトリを作成し、Dockerfilemy.cnfを作成します。

Dockerfile
FROM mysql:5.7

RUN /bin/cp -f /etc/localtime /etc/localtime.org
RUN /bin/cp -f /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

COPY ./my.cnf /etc/mysql/conf.d/

RUN mkdir -p /var/log/mysql
RUN chown mysql.mysql /var/log/mysql
my.cnf
[mysqld]
default-storage-engine=InnoDB
user=mysql
character-set-server=utf8mb4
skip-character-set-client-handshake

;general_log=1
;general_log_file=/var/log/mysql/query.log
long_query_time=3
slow_query_log_file=/var/log/mysql/slow-query.log

max_allowed_packet=16MB
innodb_file_per_table
innodb_buffer_pool_size=64M
sort_buffer_size=2M
read_buffer_size=2M
read_rnd_buffer_size=2M
group_concat_max_len=2048

[mysqld_safe]
log-error=/var/log/mysqld.log

[mysql]
default-character-set=utf8mb4

Dockerを起動

intelliJでターミナルを開き./gradlew composeUpを実行します。
下記のようなログが表示されれば完了です。

> Task :composeUp
Building todo-db

...省略

TCP socket on localhost:33068 of 'todoDb' is ready
+--------+----------------+-----------------+
| Name   | Container Port | Mapping         |
+--------+----------------+-----------------+
| todoDb | 3306           | localhost:33068 |
+--------+----------------+-----------------+

BUILD SUCCESSFUL in 15s

テーブルの確認

dockerコンテナ内のMySQLにテーブルが作成されているか確認します。

IntelliJのターミナルを使用します。
docker psを実行します。以下のように起動しているコンテナが表示されていることを確認します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                NAMES
149c67cc70d5        docker_todo-db      "docker-entrypoint.s…"   17 seconds ago      Up 15 seconds       33060/tcp, 0.0.0.0:33068->3306/tcp   todoDb

docker exec -it todoDb /bin/bashを実行して、コンテナの中に入ります。
root@<CONTAINER ID>:/#となり、コンテナの中にいることがわかります。

$ docker exec -it blogDb /bin/bash
root@149c67cc70d5:/#

mysql -u root -pでMySQLにアクセスします。
パスワードを求められるので、ここではdocker-compose.ymlで指定した「passw0rd」を入力します。

MYSQL_ROOT_PASSWORD=passw0rd

$ mysql -u root -p
Enter password: 

※macだとカーソルなどは表示されません

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| todo               |
+--------------------+
5 rows in set (0.00 sec)

todoというテーブルがちゃんと作成されています。

次の記事ではflyway-coreを使ったmigrationの自動化をしたいと思います。

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

java 何度も検索してしまうもの【java初心者】

随時追加予定.

1, length の取り方

int 配列
int [] intArray = {1,2,3,4,5};
System.out.println(intArray.length);
出力: 5
String 文字列
String text = "text";
System.out.println(text.length());
出力: 4
StringBuilder型 文字列
StringBuilder sb = new StringBuilder();
sb.append("text");
System.out.println(sb.length());
出力: 4

2, 型変換

String >> int 変換
String StringA = "0";
int intA = Integer.parseInt(StringA);
int >> String 変換
int intB = 0;
String stringB = String.valueOf(intB);
int >> Integer 変換
int intC = 0;
Integer integerC = intC;        
Integer >> int 変換
Integer integerD = 0;
int intD = integerD.intValue();
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CVE-2019-3799 Spring-Cloud-Config-Server 路径穿越/任意文件读取

复现

idea创建一个spring项目,然后pom.xml中加入

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-config-server</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

配置application.properties

server.port=8888
spring.cloud.config.server.git.uri=https://github.com/SukaraLin/awesome-cve-poc.git

然后访问
http://127.0.0.1:8888/aaa/bbb/master/..%252F..%252F..%252F..%252F..%252F..%252Fwindows/win.ini
image.png

分析

Spring Cloud Config Server是Spring为了分布式管理的一个组件,在Server端中负责存储配置,Client可以通过http的形式获取配置值。payload对应的路由存在于org.springframework.cloud.config.server.resource.ResourceController#retrieve()中,其代码

    @RequestMapping({"/{name}/{profile}/{label}/**"})
    public String retrieve(@PathVariable String name, @PathVariable String profile, @PathVariable String label, HttpServletRequest request, @RequestParam(defaultValue = "true") boolean resolvePlaceholders) throws IOException {
        String path = this.getFilePath(request, name, profile, label);
        return this.retrieve(name, profile, label, path, resolvePlaceholders);
    }

{name}/{profile}/{label}:name对应仓库名,profile对应配置文件,label为git分支名,一般上都有一个master分支。实际测试中name、profile值无所谓,但是label分支名必须存在。调试跟进getFilePath
image.png
这边已经把%25urldecode为%了,继续跟retrieve(),这边path传入的是我们的payload

image.png

继续跟下findOne,位于org.springframework.cloud.config.server.resource.GenericResourceRepository#findOne

image.png
locations目录file:/C:/Users/icu/AppData/Local/Temp/config-repo-6608031716294156148/是配置文件中git仓库被clone的临时位置,然后拼接../造成跨目录文件读取。

修复

https://github.com/spring-cloud/spring-cloud-config/commit/3632fc6f64e567286c42c5a2f1b8142bfde505c2

image.png

通过判断是否存在%来urldecode,然后匹配../../等特殊字符。

参考

  1. Spring-Cloud-Config-Server-任意文件读取分析
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java  Hack

Java Hackとは

Javaのいわゆる案件に入るための準備

Javaはお金になるし、面白い案件に入れると(信じる)

← 作ってからkotlinにすることもできると思われる。

コンテンツ

・Javaそのもの
・Spring
・SQL
・DB周り 
・ネットワーク
・シラバス作成

Javaそのもの

教材を使う。
https://jflute.hatenadiary.jp/entry/20200301/javatry

Spring

APIから作り上げていく。

SQL

Progate道場コースまで完了
https://prog-8.com/languages/sql
・ポートフォリオの事前準備中に強化と情報収集を行う。

DB周り その他

シラバス

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

日付の期間判定・過去未来判定について

結論

ミリ秒にして大小判定するのマジおすすめ

※Javaを例にしていますが、どの言語でも同じ話です

※良い子のみんなは、java.util.Dateなんて使わずにJoda-Timeつかうか、Java 1.8以降ならTime APIを使おうネ)

※ただの参考コードです。Dateクラスの非推奨コンストラクタ使うのやめようね

Date nowDate= new Date();
// 2020/4/1 00:00:00 ~2020/4/30 23:59:59 の場合に「キャンペーン期間中だよ」と出力
if (nowDate.getTime() >= new Date(2020, 3, 1, 0, 0, 0).getTime()
    && nowDate.getTime() < new Date(2020, 4, 1, 0, 0, 0).getTime()) {
    System.out.println("キャンペーン期間中だよ");
}

言い訳

皆さんは、Javaの日付の判定、どうされていますか?

java.util.Dateクラスを利用する場合、afterメソッドやbeforeメソッドを使っていますか?

Joda-TimeやTime APIでも、似たようなメソッドありますよね?

でも、正直私はそのメソッドを見ただけでは「引数が未来なんだっけ?過去なんだっけ?指定された日付は含むんだっけ?含まないんだっけ?」というのを即時に判断出来ません。(アホですね)

JavaDocを見るとそれぞれこう書かれています。

Java 1.8のJavaDocから引用)

  • after

この日付が、指定された日付より後にあるかどうかを判定します。

  • before

この日付が、指定された日付より前にあるかどうかを判定します。

つまり、どちらも「指定された日そのもの」は含まないわけです。

英語が得意な方は、afterやbeforeが「含まない」という意味だと理解しているかもしれないですが、

僕は知りませんでした。

よくやるポカとして、

「2020年4月1日の00:00:00~2020年4月30日の23:59:59までをキャンペーンの申し込み期間としよう!」とした場合に

Date nowDate = new Date();

if (nowDate.after(new Date(2020, 3, 1, 0, 0, 0)) 
    && nowDate.before(new Date(2020, 4, 1, 0, 0))) {
    System.out.println("キャンペーン期間中だよ");
}

こんな風にしちゃって、「日付変わった瞬間は申し込み出来ねーじゃん!!!!!」っていうバグを作り込んじゃいます。

(まあ、その1秒を良しとするかどうかはプロジェクト次第ですが)

だから、数値型にしちゃって大小比較したほうが間違いが無いよね。っていう話でした。

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

[Java] 天パの人による適当な入門 Java編 その0 (コードの決まり事)

コードの決まり

その0は、番外編というか、なんとなくわかっていくだろうけどめっちゃ大切なことを書いています。
本編はその1からです。シリーズの説明なども書いています。
https://qiita.com/Shodai-Kurasaki/items/99cf6b0ec1a8e80640b1

 { } について

Sample1.java
class Sample1
{
        public static void main(String[] args)
        {
            System.out.println("Hello World!");
        }
}

このようにコード内には{ }が存在しています。たくさんコードをうつしていくと大体どこにあるかがわかってきますが、覚えてほしいのは、
{と} の数が必ず同じになるようにする
ということです(例外:文字としての { は数えません)。すぐにこのセットをたくさん使うプログラムを書くことになるとおもいますが、その時1つでも足りなければもう動きません。
それの防止のために{ }のセットごとに階段状にしています。
tabキーを一回押すのが普通です。

 ; について

System.out.println("Hello World!");←これ
ひとつの文の終わりには基本的に必ずつけます。例外は直後に{ }がついてるものです。

class Sample1
{ ←このように{}がうしろについてくるやつ以外は ; をつけます。

これがないと動きません。
因みに、これの後ろに改行を入れないと動かないわけではなく、

System.out.println("Hello World!"); System.out.println("Hello World! 2");

としても問題ありません。ただ、基本的にはみにくくなるので改行しちゃってください。

 ファイル名とクラス名

Sample1.java
class Sample1 これがクラス名
{
        public static void main(String[] args)
        {
            System.out.println("Hello World!");
        }
}

クラス名はファイル名と同じにしてください。違うものにすることもあるのですが、最初は何も考えずに同じにしてください。ちなみにファイルの拡張子は.javaで保存するようにします。

 コメント文

プログラム内で、だんだんメモを残したいところが出てくるようになります。「この文はHello World!と表示するための文だ」などです。
そういう時は // をかいてそのあとにメモを書きます。// のあとはプログラム実行時には完全に無視されるので好きにかいて構いません。しかし、// はその行の//以降の内容しかメモにしません。
複数行にわたってメモをしたい場合は/*  */でメモをはさみます。

Sample1.java
class Sample1
{
        public static void main(String[] args)
        {
            System.out.println("Hello World!"); //ここはメモ

            /*ここも~~~~~~~~~~~~~~~~~~~~~~~~~~~
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~メモ*/
        }
}

おわり

もし訂正すべき点などありましたらご指摘よろしくお願い致します。

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