20200712のJavaに関する記事は10件です。

JavaとJavaScript 間違えられやすい問題

はじめに

ちょっとキニなることを叫びます。
たぶん、これを読む皆さんも
おそらく聞いたことがあると思います。


結論から言います

JavaとJavaScriptは違います!!!


なんで間違えてしまうん

単純に「勉強不足」と片づけてしまえれば
楽なのですが
なぜか、頑なにJavaScriptを「Java」と
発言してしまう方がチラホラいます。


なぜこうなるのか

JavaScriptという言語自体が
Java開発に参画していたSun Microsystems社とともに
開発された言語だった為


でもさあのさ

うんうん、わかるよわかるよその気持ち
ホンマのIT技術者ならばこれくらいの違いは
わかって当然だよね。


それでもわからない人がいる

なんでや!!
ITの一般常識だと思っていますが
そもそもITに詳しくない人がIT業界には
一定数存在しています。(それはそれでおかしい)


Javaの有償化による大きな勘違い 事例

もともと、職場には
「JavaScript」を使ったアプリがありました。
Javaが有償化された時
上記のアプリを活用するにあたり
有償化になるけど大丈夫か
などという質問をいただきました。


事例の前提

要件定義や仕様の段階で
Javaを使っていないことは既にお断り済


リテラシーの問題か?

それでも「Java」と入っているので
何をどう勘違いしたのか
たびたび質問される方がおります。
こればかりはもう勉強してもらうしかないですね。


真に日本でエンジニアと呼ばれる人たち

おそらく
毎日勉強してらっしゃる方々だと思います。
新しい情報にしっかりキャッチアップして
進化していくことがエンジニアに重要なことだと
思います。

過激派っぽい発言をするのであれば
そうでなければエンジニアではない。
エンジニアと名乗る方がいても
私の思う定義から外れれば
自分と同じ土俵には立っていない。
そう思います。


おわり

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

【実践‼】MyBatisを使用する際の最低限の設定

1.事前知識

事前知識として、上記リンクの内容が必要です。

2.事前準備

22.png
23.png
1. 検索ボックスに cmd と入力し、 コマンドプロンプト を起動する。
07.png
2. mysql -u ユーザ名 -pログイン する。
08.png
09.png

test.sql
create database test DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

use test;

CREATE TABLE test1( 
    id TINYINT ZEROFILL NOT NULL AUTO_INCREMENT,
    name VARCHAR(50),
    PRIMARY KEY(id));

INSERT INTO `test1`(`name`) VALUES ("test1");
INSERT INTO `test1`(`name`) VALUES ("test2");
INSERT INTO `test1`(`name`) VALUES ("test3");
INSERT INTO `test1`(`name`) VALUES ("test4");
  1. 上記の SQL文 をコピーし、 コマンドプロンプト 内で実行する。
  2. 画像のように Query OK が出れば成功。

3.Spring Boot プロジェクトの作成

06.png
1. [ファイル(F)]→[新規(N)]→[Spring スターター・プロジェクト] を選択する。
02.png
2. 名前に MyBatisTest と入力し、Javaバージョン:8 を選択して 次へ(N) > ボタンをクリックする。
03.png
3. Spring Boot バージョン:2.3.1 , Lombok , JDBC API , MyBatis Framework , MySQL Driver , Thymeleaf , Spring Web を選択し、完了 ボタンをクリックする。

4.Spring Boot プロジェクトの実行

フォルダ構成
MyBatisTest
└─ src
     └─ main
          ├─ java
          │   └─ com
          │        └─ example
          │             └─ demo
          │                  ├─ Entity.java
          │                  └─ TestController.java
          └─ resources
               ├─ application.properties
               ├─ mybatis-config.xml
               ├─ sample_mapper.xml
               │  
               ├─ static
               └─ templates
                    └─ index.html
Entity.java
package com.example.demo;

import lombok.Data;

@Data
public class Entity {
    private int id;
    private String name;
}
TestController.java
package com.example.demo;

import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class TestController {
    // ルートとなる設定ファイルを読み込む
    InputStream in = TestController.class.getResourceAsStream("/mybatis-config.xml");

    // 設定ファイルを元に SqlSessionFactory を作成する
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

    // SqlSessionFactory から SqlSession を生成する
    SqlSession session = factory.openSession();

    @GetMapping("/")
    public String index(Model model) {

        // SqlSession を使って SQL を実行する
        List<Entity> result = session.selectList("sample.mybatis.selectTest");

        model.addAttribute("Test", result);
        return "index";
    }
}
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=JST
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto = update
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="sample_id">
    <environment id="sample_id">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost/test?serverTimezone=JST"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="sample_mapper.xml"/>
  </mappers>
</configuration>
sample_mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sample.mybatis">
  <select id="selectTest" resultType="com.example.demo.Entity">
    select * from test1
  </select>
</mapper>
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org/">
  <head>
    <title>Test</title>
    <meta charset="utf-8" />
  </head>
  <body>
    <ul>
      <li th:each="entity : ${Test}">
        [[${entity.getId()}]]
        [[${entity.getName()}]]
      </li>
    </ul>
  </body>
</html>
  1. 上記のフォルダ構成のようにファイルを配置し、 MyBatisTest [boot] を右クリックして [実行(R)]→[5 Maven install] 選択する。 15.png
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.125 s
[INFO] Finished at: 2020-07-05T21:50:22+09:00
[INFO] ------------------------------------------------------------------------

2.コンソール に 上記の文が表示されれば成功。 
16.png
3.MyBatisTest [boot] を右クリックして [実行(R)]→[9 Spring Boot アプリケーション] 選択する。
01.png
4. localhost:8080 にアクセスし、画像のように表示されれば成功。

5.関連

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

Gradleで任意のフォルダのプロジェクトを追加する

はじめに

React NativeのAndroidプロジェクトで変則的なフォルダ構成だったのでGradleでうまく扱う方法を紹介します。
サブフォルダや同じ階層のフォルダの場合はGradleでマルチプロジェクトで実現できます。

フォルダ構成

myapp/
  android/ ★rootプロジェクト
    build.gradle
    settings.gradle ★これを編集する
    app/ ★アプリプロジェクト
      build.gradle
library/
  android/ ★ライブラリプロジェクト
    build.gradle

設定

settings.gradle
include ':app'
// ここから追加
include ':library'
project(':library').projectDir = file('../../library/android')
// ここまで追加

参考サイト

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

Apache Tomcat 9 にデプロイする Java Servlet と JSP の WAR ファイルを Gradle で作成する

概要

  • Java Servlet と JSP を含む WAR ファイルを Gradle で作成する
  • Apache Tomcat 9 にデプロイする

今回の環境

  • Apache Tomcat 9.0.37 (Java Servlet 4.0, JSP 2.3)
  • Java 14 (AdoptOpenJDK 14.0.1)
  • Gradle 6.5.1
  • macOS 10.15 Catalina

Java Servlet と JSP を含む WAR ファイルを Gradle で作成する

ソースコードを置くディレクトリを用意する

今回は任意のディレクトリに mywebapp というディレクトリを作成してその中にシンプルなサンプルコードを用意する。

$ mkdir mywebapp

$ cd mywebapp

ファイル一覧

mywebapp
├── build.gradle
└── src
    └── main
        ├── java
        │   └── com
        │       └── example
        │           └── MyServlet.java
        └── webapp
            ├── WEB-INF
            │   └── web.xml
            └── myjsp.jsp

build.gradle

plugins {
  id 'war'
}

repositories {
  mavenCentral()
}

dependencies {
  // Java Servlet 4.0 API
  // https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api
  providedCompile 'javax.servlet:javax.servlet-api:4.0.1'
}

// Java 14
sourceCompatibility = 14
targetCompatibility = 14

// Application
version = '1.0'

src/main/java/com/example/MyServlet.java

package com.example;

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

@WebServlet("/myservlet")
public class MyServlet extends HttpServlet {

  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
    res.setContentType("text/html; charset=utf-8");
    try (PrintWriter out = res.getWriter()) {
      out.println("<html><body>");
      out.println("サーブレット: Hello Servlet World!<br>");
      out.println(getServletContext().getServerInfo());
      out.println("</body></html>");
    }
  }
}

src/main/webapp/myjsp.jsp

<%@ page contentType="text/html; charset=utf-8" %><html><body>
ジェイエスピー: Hello JSP World!<br>
<%= pageContext.getServletContext().getServerInfo() %><br>
java.vm.name: <%= System.getProperty("java.vm.name") %><br>
java.vm.vendor: <%= System.getProperty("java.vm.vendor") %><br>
java.vm.version: <%= System.getProperty("java.vm.version") %><br>
</body></html>

src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>

<!-- Web Application Deployment Descriptor (Java Servlet 4.0) -->
<web-app
  xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0">

  <servlet>
    <servlet-name>myjsp</servlet-name>
    <jsp-file>/myjsp.jsp</jsp-file>
  </servlet>

  <servlet-mapping>
    <servlet-name>myjsp</servlet-name>
    <url-pattern>/myjsp</url-pattern>
  </servlet-mapping>

</web-app>

WAR ファイルを作成する

Gradle の build タスクで WAR ファイルを作成する。

$ gradle build

WAR ファイルが生成されていることを確認する。

$ file build/libs/mywebapp-1.0.war
build/libs/mywebapp-1.0.war: Zip archive data, at least v1.0 to extract

Apache Tomcat 9 をインストールする

インストールするディレクトリを用意する

今回は任意のディレクトリに tomcat9 というディレクトリを作成してその中にインストールする。

$ mkdir tomcat9

$ cd tomcat9

Apache Tomcat 9 をダウンロードして展開する

Apache Tomcat® - Apache Tomcat 9 Software Downloads

$ wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.37/bin/apache-tomcat-9.0.37.tar.gz

$ tar xf apache-tomcat-9.0.37.tar.gz

起動用の Java VM を用意する

環境変数 JAVA_HOME を設定する。

$ export JAVA_HOME=/Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home

$ PATH=${JAVA_HOME}/bin:${PATH}

$ java -version
openjdk version "14.0.1" 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 14.0.1+7)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 14.0.1+7, mixed mode, sharing)

Apache Tomcat 9 が起動することを確認する

startup.sh で Apache Tomcat 9 を起動する。

$ ./apache-tomcat-9.0.37/bin/startup.sh
Using CATALINA_BASE:   /Users/foo/tomcat9/apache-tomcat-9.0.37
Using CATALINA_HOME:   /Users/foo/tomcat9/apache-tomcat-9.0.37
Using CATALINA_TMPDIR: /Users/foo/tomcat9/apache-tomcat-9.0.37/temp
Using JRE_HOME:        /Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home
Using CLASSPATH:       /Users/foo/tomcat9/apache-tomcat-9.0.37/bin/bootstrap.jar:/Users/foo/tomcat9/apache-tomcat-9.0.37/bin/tomcat-juli.jar
Tomcat started.

curl でアクセスして起動していることを確認する。

$ curl --include -s http://localhost:8080/ | head -15
HTTP/1.1 200 
Content-Type: text/html;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 12 Jul 2020 10:38:03 GMT




<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Apache Tomcat/9.0.37</title>
        <link href="favicon.ico" rel="icon" type="image/x-icon" />
        <link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />

shutdown.sh で Apache Tomcat 9 を停止する。

$ ./apache-tomcat-9.0.37/bin/shutdown.sh
Using CATALINA_BASE:   /Users/foo/tomcat9/apache-tomcat-9.0.37
Using CATALINA_HOME:   /Users/foo/tomcat9/apache-tomcat-9.0.37
Using CATALINA_TMPDIR: /Users/foo/tomcat9/apache-tomcat-9.0.37/temp
Using JRE_HOME:        /Library/Java/JavaVirtualMachines/adoptopenjdk-14.jdk/Contents/Home
Using CLASSPATH:       /Users/foo/tomcat9/apache-tomcat-9.0.37/bin/bootstrap.jar:/Users/foo/tomcat9/apache-tomcat-9.0.37/bin/tomcat-juli.jar
NOTE: Picked up JDK_JAVA_OPTIONS:  --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED

WAR ファイルを Apache Tomcat 9 にデプロイする

WAR ファイルを Apache Tomcat 9 の webapps ディレクトリに設置する

$ cp mywebapp/build/libs/mywebapp-1.0.war tomcat9/apache-tomcat-9.0.37/webapps/mywebapp.war

Apache Tomcat 9 を起動する

$ ./tomcat9/apache-tomcat-9.0.37/bin/startup.sh

Java Servlet と JSP にアクセスする

curl で Java Servlet にアクセスして動作を確認する。

$ curl --include http://localhost:8080/mywebapp/myservlet
HTTP/1.1 200 
Content-Type: text/html;charset=utf-8
Content-Length: 94
Date: Sun, 12 Jul 2020 13:06:56 GMT

<html><body>
サーブレット: Hello Servlet World!<br>
Apache Tomcat/9.0.37
</body></html>

curl で JSP にアクセスして動作を確認する。

$ curl --include http://localhost:8080/mywebapp/myjsp.jsp
HTTP/1.1 200 
Set-Cookie: JSESSIONID=EE7C8309A86FBBEDBCEB618759FF6022; Path=/mywebapp; HttpOnly
Content-Type: text/html;charset=utf-8
Content-Length: 204
Date: Sun, 12 Jul 2020 13:07:08 GMT

<html><body>
ジェイエスピー: Hello JSP World!<br>
Apache Tomcat/9.0.37<br>
java.vm.name: OpenJDK 64-Bit Server VM<br>
java.vm.vendor: AdoptOpenJDK<br>
java.vm.version: 14.0.1+7<br>
</body></html>

参考資料

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

Azure Functions & Java のウォームアップトリガー実装時の注意点

Azure Functions の Premium プランでは ウォームアップトリガー を用いてインスタンスのスケールアウト時に任意のウォームアップ処理を実行できます。Java で実装する際には注意するポイントがありますので、本記事で簡単にまとめたいと思います。

tl;dr

Azure Functions の Java は現状では WarmupTrigger 属性をサポートしていないため、ウォームアップ用の function.json を自前で準備しておき、ビルド成果物のパッケージに同梱する必要があります。

ドキュメントの解説

Azure Functions のウォームアップトリガーについて以下のドキュメントにまとまっています。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-bindings-warmup?tabs=java

以下はドキュメントからの抜粋です。
トリガー - 例 のコードをコピーして自身の Java プログラムに組み込み、run メソッドの中身を任意の処理に書き換える必要があります。

image.png

トリガー - 属性 に記載の通り、WarmupTrigger 属性は現状 C# のみサポートされており、C# スクリプト / JavaScript / Python / Java ではサポートされていません。

image.png

どういうことかと言うと、HttpTrigger 属性を例に説明します。例えば HTTP トリガーの Function を実装する場合、以下ドキュメントのように HttpTrigger のアノテーションを Java のメソッドに付与します。azure-functions-maven-plugin でビルドを実施する際に、自動的に function.json が生成されます。
https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-reference-java?tabs=consumption#triggers-and-annotations

image.png

一方で、WarmupTrigger は Java ではサポートされていないため、必要な情報を持つ function.json はビルド時に生成されません。必要な情報というのは トリガー - 構成 に記載された以下の項目群のことです。

image.png

ワークアラウンド

Java でウォームアップトリガーを利用するためには以下の対応が必要です。

  1. @FunctionName("Warmup") アノテーションが付与された Java メソッドを作成
  2. 必要な情報を持つ function.json を作成
  3. function.json をビルド成果物に同梱するよう設定
  4. アプリケーションをビルドして Azure Functions にデプロイ

以下のサンプルコードに沿って説明していきたいと思います。
https://github.com/nakazax/azure-functions-premium-warmup-java

1. @FunctionName("Warmup") アノテーションが付与された Java メソッドを作成

トリガー - 例 をそのまま流用する形で実装しています。run メソッドの中身を任意の処理に書き換えてご利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/java/com/functionp/Warmup.java

azure-functions-premium-warmup-java/src/main/java/com/functionp/Function.java
package com.functionp;

import com.microsoft.azure.functions.ExecutionContext;
import com.microsoft.azure.functions.annotation.FunctionName;

public class Warmup {

    @FunctionName("Warmup")
    public void run( ExecutionContext context) {
       context.getLogger().info("Function App instance is warm ???");
    }
}

2. 必要な情報を持つ function.json を作成

トリガー - 構成 を参考に必要な情報を定義した function.json を作成しておきます。scriptFileentryPoint は自身のアプリケーションに合わせて書き換えて利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/resources/Warmup/function.json

azure-functions-premium-warmup-java/src/main/resources/Warmup/function.json
{
  "scriptFile": "../azure-functions-premium-warmup-java-1.0-SNAPSHOT.jar",
  "entryPoint": "com.functionp.Warmup.run",
  "bindings": [ {
    "type": "warmupTrigger",
    "direction": "in",
    "name": "Warmup"
  } ]
}

3. function.json をビルド成果物に同梱するよう設定

先に作成した azure-functions-premium-warmup-java/src/main/resources/Warmup/function.jsonazure-functions-premium-warmup-java/target/azure-functions/${applicatioName}/Warmup に格納するように設定します。

ビルドツールとして Maven を利用している場合は pom.xml の中で当該処理を実装するのが手軽かと思います。以下は pom.xml の抜粋で、<id>copy-warmup-function-json</id> のブロックで当該処理を定義しています。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/pom.xml

azure-functions-premium-warmup-java/pom.xml(抜粋)
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-resources-plugin</artifactId>
    <executions>
        <execution>
            <id>copy-resources</id>
            <phase>package</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <overwrite>true</overwrite>
                <outputDirectory>${stagingDirectory}</outputDirectory>
                <resources>
                    <resource>
                        <directory>${project.basedir}</directory>
                        <includes>
                            <include>host.json</include>
                            <include>local.settings.json</include>
                        </includes>
                    </resource>
                </resources>
            </configuration>
        </execution>
        <execution>
            <id>copy-warmup-function-json</id>
            <phase>package</phase>
            <goals>
                <goal>copy-resources</goal>
            </goals>
            <configuration>
                <overwrite>true</overwrite>
                <outputDirectory>${stagingDirectory}/Warmup</outputDirectory>
                <resources>
                    <resource>
                        <directory>src/main/resources/Warmup</directory>
                        <includes>
                            <include>function.json</include>
                        </includes>
                    </resource>
                </resources>
            </configuration>
        </execution>
    </executions>
</plugin>

4. アプリケーションをビルドして Azure Functions にデプロイ

後はお好みの方法でアプリケーションのビルドと Azure Functions へのデプロイを行います。以下 URL は Azure Pipelines の定義のサンプルです。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/azure-pipelines.yml

デプロイ後の挙動 (正常時)

上記の流れに沿ってデプロイを行った後の Azure リソースの状態は以下のようになるはずです。

Azure Pipelines

ビルド & デプロイともに成功
image.png

デプロイの詳細でエラーメッセージは出ない
image.png

Azure Functions

デプロイ先の Azure Functions
image.png

[デプロイ センター] に「production に正常にデプロイされました」と表示される
image.png

[関数] に Warmup トリガーが表示される
image.png

[関数] > [Warmup] をクリックすると以下のような詳細が表示される
image.png

[ログ] > [traces] を見ると Warmup が実行された旨、表示される
image.png

(参考) 必要な情報を持つ function.json が存在しない場合の挙動

以下は Java のメソッドだけを実装して Azure Functions にデプロイした場合の挙動です。Azure Pipelines でのビルド & デプロイ自体は成功しますが、Azure Functions の [デプロイ センター] に「production にデプロイできませんでした」と表示され、デプロイに失敗します。
(最初、原因が分からずにハマりました。。。)

Azure Pipelines

ビルド & デプロイともに成功
image.png

デプロイの詳細でエラーメッセージは出ない
image.png

Azure Functions

[デプロイ センター] に「production にデプロイできませんでした」と表示される
image.png

[関数] に Warmup トリガーが表示されない
image.png

以上です。

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

画像はアップロードされたのに、画面には表示されない!?

困ったこと

Spring Bootを使って、画像をアップロードする。
アップロード直後は画像がHTML上に表示されず、30秒程待って画面をリフレッシュすると表示される。

画像のアップロード方法

画像ファイル -> /resource/static/images/配下
ファイル名(xxx-20200712151409.jpgなどのファイル名) -> DBに格納

 解決方法

  1. ルートフォルダ直下にフォルダ(仮に、imagesとする)を作る
    今回は、RamenKingdomというルートフォルダの直下にimagesを作成しています。
    スクリーンショット 2020-07-12 15.17.29.png

  2. WebConfigurationクラス(仮に、AdditionalResourceWebConfigurationとする)を作成

@Configuration
public class AdditionalResourceWebConfiguration implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/images/**").addResourceLocations("file:images/");
    }
}

あとは、画像を格納していたコードをルートフォルダ直下のimagesに変更するだけ。
ただし、HTML上では、下記のようにする必要があります。

<img th:src="@{'/images/' + ${picture.filepath}}"/>

まとめ

今回のように動的に変化するファイルはstatic配下には格納しない!!!

以上です。最後まで読んでくださり、ありがとうございました。

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

QuarkusでAWS Lambdaを作る

Quarkus(https://quarkus.io/) とはSUPERSONIC SUBATOMIC JAVAで、

A Kubernetes Native Java stack tailored for OpenJDK HotSpot and GraalVM, crafted from the best of breed Java libraries and standards.

だそうです。
何言ってるかわかりませんがすごそうです。

GraalVMの機能でJavaのプログラムをネイティブに変換することが可能なので、AWS Lambdaのカスタムランタイムと組み合わせることで AWS Lambda上で動作するJavaアプリケーションの特性である コールドスタートが遅い問題 の解決に期待ができます。

公式サイトの手順に従い試してみましたので、手順を残します。

QUARKUS - BUILDING A NATIVE EXECUTABLE
https://quarkus.io/guides/building-native-image

QUARKUS - AMAZON LAMBDA
https://quarkus.io/guides/amazon-lambda

環境

  • Docker Desktop (Mac) 2.3.0.4
  • VSCode + Visual Studio Code Remote - Containers extension
  • Amazon Linux 2 (on Docker)

雛形アプリのデプロイ手順

手順1. Amazon Linux 2の環境設定

DockerイメージのAmazon Linux 2はtarunzipができないので色々入れておきます。
全部必要かわからないですが、これぐらい入れておきました。

yum install -y sudo shadow-utils procps tar.x86_64 gzip xz unzip witch git python3 tree

手順2. GraalVMのインストール

公式サイトからダウンロードして展開します。

curl -s -L -o /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.1.0/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz
tar zxf /tmp/graalvm-ce-java11-linux-amd64-20.1.0.tar.gz -C /opt/
ln -s /opt/graalvm-ce-java11-20.1.0 /opt/graalvm

JAVA_HOMEをGraalVMにして、MavenでのビルドにGraalVMを使用します。

export GRAALVM_HOME=/opt/graalvm
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH

最後にNativeビルド時に必要なnative-imageをインストールします。コマンドはgu(GraalVM Updater)です。

gu install native-image

手順3. Mavenのインストール

Quarkusのビルドで使用するMavenは3.6.2以上のバージョンが必要です。yumでインストールできるバージョンが古かったので、公式サイトからダウンロードしてインストールしました。

curl -s -L -o /tmp/apache-maven-3.6.3-bin.tar.gz https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
tar zxf /tmp/apache-maven-3.6.3-bin.tar.gz -C /opt/
ln -s /opt/apache-maven-3.6.3 /opt/apache-maven

mvnのバージョン確認

bash-4.2# mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: /opt/apache-maven
Java version: 11.0.7, vendor: GraalVM Community, runtime: /opt/graalvm-ce-java11-20.1.0
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.19.76-linuxkit", arch: "amd64", family: "unix"
bash-4.2# 

GraalVMのJVMで動作していることがわかります。

手順4. Mavenでプロジェクト作成

mvnコマンドでプロジェクトを生成します。

mvn archetype:generate \
    -DarchetypeGroupId=io.quarkus \
    -DarchetypeArtifactId=quarkus-amazon-lambda-archetype \
    -DarchetypeVersion=1.6.0.Final

しばらくするとプロンプトで質問されますので答えます。
[]で囲んだ部分がユーザー入力です。

Define value for property 'groupId': [myGroup]
Define value for property 'artifactId': [myArtifact]
Define value for property 'version' 1.0-SNAPSHOT: : []
Define value for property 'package' myGroup: : [example]
Confirm properties configuration:
groupId: myGroup
artifactId: myArtifact
version: 1.0-SNAPSHOT
package: example
 Y: : [Y]

artifactId(myArtifact)でディレクトリが作成され、プロジェクトが生成されます。

作成直後のプロジェクト構成はこんな感じ。

bash-4.2# tree myArtifact/
myArtifact/
├── build.gradle
├── gradle.properties
├── payload.json
├── pom.xml
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── example
    │   │       ├── InputObject.java
    │   │       ├── OutputObject.java
    │   │       ├── ProcessingService.java
    │   │       ├── StreamLambda.java
    │   │       ├── TestLambda.java
    │   │       └── UnusedLambda.java
    │   └── resources
    │       └── application.properties
    └── test
        ├── java
        │   └── example
        │       └── LambdaHandlerTest.java
        └── resources
            └── application.properties

9 directories, 14 files
bash-4.2# 

手順5. Lambdaで起動するHandlerの設定

Lambdaで呼び出されるHandlerはresources/application.propertiesquarkus.lambda.handlerにて設定します。

resources/application.properties
quarkus.lambda.handler=test

上記設定の場合は、以下のtestと名前をつけたクラスが呼び出されます。

main/java/example.TestLambda.java
@Named("test")
public class TestLambda implements RequestHandler<InputObject, OutputObject> {
}

あとは通常のLambdaと同様にコーディングします。
雛形にはtestの他にstreamなども用意されています。

手順6. デプロイパッケージの作成

一旦雛形のままデプロイパッケージを作成してみます。

mvn clean package -Pnative

めちゃくちゃ時間がかかります。10分以上かかると思います。

手順7. デプロイパッケージの内容の確認

無事デプロイパッケージができると、target/function.zipが生成されいます。試しに展開してみると、中身はbootstrapのみでした。

bash-4.2# unzip function.zip 
Archive:  function.zip
  inflating: bootstrap               
bash-4.2# 

手順8. Lambdaのデプロイ

targetディレクトリ内には、manage.shというファイルも生成されており、ここからAWS上にデプロイしたりできるようです。
私はカスタムランタイムが初めてだったこともあり、マネジメントコンソールから試してみました。
デプロイパッケージ作成後に、target/sam.native.yamlというファイルも生成されるので、ハンドラー名や環境変数はこちらを参考にしました。

関数を新規作成します。
ランタイムをユーザー独自のブートストラップを提供するとします。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen).png

関数を作成したら、プログラムをアップロードします。
関数コードのところのアクションから.zipファイルをアップロードを選び、function.zipを選択します。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (1).png

ハンドラーはnot.used.in.provided.runtimeとなります。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (2).png

環境変数にDISABLE_SIGNAL_HANDLERSを追加し値をtrueとします。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (4).png

ここまでで設定は完了です。以下のJSONをインプットにテスト実行してみましょう。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (3).png

{
  "name": "Bill",
  "greeting": "hello"
}

動作結果がこちら。無事動きました。

ap-northeast-1.console.aws.amazon.com_lambda_home_region=ap-northeast-1(Laptop with MDPI screen) (5).png

AWS SDK を追加

AWS SDKを使用するには単純にpom.xmlに追加するだけでなく、いくつか設定が必要です。

手順1. SSL通信の有効化

resources/application.propertiesに以下の内容を追加します。(公式ドキュメント的にはデフォルトで有効って書いてある気もしますが)

resources/application.properties
quarkus.ssl.native=true

手順2. 依存関係の追加

まずは、quarkus-jaxbが必要とのことで、これを追加します。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-jaxb</artifactId>
</dependency>

次にAWS SDKのライブラリーを追加するのですが、

For native image, however the URL Connection client must be preferred over the Apache HTTP Client when using synchronous mode, due to issues in the GraalVM compilation (at present).

翻訳すると
ただし、ネイティブイメージの場合、GraalVMコンパイルの問題(現在)のため、同期モードを使用するときは、Apache HTTPクライアントよりもURL接続クライアントを優先する必要があります。
だそうです。そのため、以下のような記述となります。

pom.xml
<properties>
      <aws.sdk2.version>2.10.69</aws.sdk2.version>
  </properties>

  <dependencyManagement>
      <dependencies>

          <dependency>
              <groupId>software.amazon.awssdk</groupId>
              <artifactId>bom</artifactId>
              <version>${aws.sdk2.version}</version>
              <type>pom</type>
              <scope>import</scope>
          </dependency>

      </dependencies>
  </dependencyManagement>
  <dependencies>

      <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>url-connection-client</artifactId>
      </dependency>

      <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>apache-client</artifactId>
          <exclusions>
              <exclusion>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
              </exclusion>
          </exclusions>
      </dependency>

      <dependency>
          <groupId>software.amazon.awssdk</groupId>
          <artifactId>s3</artifactId>
          <exclusions>
              <!-- exclude the apache-client and netty client -->
              <exclusion>
                  <groupId>software.amazon.awssdk</groupId>
                  <artifactId>apache-client</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>software.amazon.awssdk</groupId>
                  <artifactId>netty-nio-client</artifactId>
              </exclusion>
              <exclusion>
                  <groupId>commons-logging</groupId>
                  <artifactId>commons-logging</artifactId>
              </exclusion>
          </exclusions>
      </dependency>

      <dependency>
          <groupId>org.jboss.logging</groupId>
          <artifactId>commons-logging-jboss-logging</artifactId>
          <version>1.0.0.Final</version>
      </dependency>
  </dependencies>

手順3. Javaコードの作成

S3にアクセスする例ですが、S3Clientを生成する際に、httpClientにて明示的にUrlConnectionHttpClientを指定します。

S3Client s3 = S3Client.builder()
  .region(Region.AP_NORTHEAST_1)

 .httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build())
  .build();

手順4. SSL通信に必要な設定

SSL通信を行うためにデプロイパッケージに以下を含める必要があります。

  • カスタムBootstrap
  • libsunec.so
  • cacerts

まず、src/main/zip.native/ディレクトリを作成し、bootstrapを作成します。

zip.native/bootstrap
#!/usr/bin/env bash

./runner -Djava.library.path=./ -Djavax.net.ssl.trustStore=./cacerts

次にlibsunec.socacertsをコピーします。この2つのファイルはGraalVMに含まれています。

cp $GRAALVM_HOME/lib/libsunec.so $PROJECT_DIR/src/main/zip.native/
cp $GRAALVM_HOME/lib/security/cacerts $PROJECT_DIR/src/main/zip.native/

手順5. デプロイパッケージの作成

デプロイパッケージの作成手順は変わりません。

mvn clean package -Pnative

手順6. デプロイパッケージの内容の確認

SSL有効化した状態でデプロイパッケージを作成すると、target/function.zipの中身が変わります。

bash-4.2# unzip function.zip 
Archive:  function.zip
  inflating: bootstrap               
  inflating: cacerts                 
  inflating: libsunec.so             
  inflating: runner                  
bash-4.2# 

事前準備したbootstrapcacertslibsunec.soが含まれているのがわかります。

終わりに

通常のJavaランタイムよりも、コールドスタートが早くなる検証結果はこちらで確認ください。

QuarkusがJava Lambdaを救う!?
https://qiita.com/moritalous/items/4de31a66edac728ba088

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

ラズパイでjavafxを動かす方法 2020/07/12投稿

恐らく、かなり多数の方がラズパイでJAVA FXを実行する方法を探しているかと思うが日本語の情報が少ない為、ここに略記する。

Google先生で検索すると以下の記事が最も気になる。
https://qiita.com/sh1k1ya/items/72d40e6ef8b8cd51e68c
「RaspberryPiでJavaFXとGPIO制御を組み合わせてみる…が、まだ完成していないorz」

参考にしたが、なかなか動作させるに至らなかった

開発ツールはEclipseを使用
EclipseにJAVAFXを導入し、開発する方法は他サイトにゆずる。
<やりたいこと>
〇ラズベリーパイにsambaを導入してwindows上のeclipseでクロス開発環境を構築する
 windows10の場合は、プログラムと機能でsambaクライアントを有効にする必要もある
〇シーンビルダーで編集した実行可能jarを自動実行させる

<実行手順略記>
1.SDカードにRasbian OS イメージを書き込み
2.初期設定
3.無線LANの設定
4.sambaのインストール

$/sudo apt-get install samaba

5.smb.conf編集

sudo nano /etc/samba/smb.conf

最終行に追記


[raspberry_pi]
comment = Share
path = /home/pi/samba
public = yes
read only = no
browsable = yes
force user = pi


6./home/piにsambaフォルダを作成

$ sudo mkdir /home/pi/samba


7. サービス起動 ※次回以降ラズパイ起動時にも自動起動される
 

$ sudo systemctl restart smbd

8.javafxの配置
https://openjfx.io/
 ・JavaFX armv6hf SDKをダウンロードしZIPを解凍
 ・解凍したファイル群をsambaフォルダに配置
 ・armv6hf-sdkを右クリック 端末で開く
・パーミッション確認
・パスを通す ※パスを通すことでjavaFXのsoファイルが実行できる

\$ export PATH="$PATH:/home/pi/samba/armv6hf-sdk"


  
・パスの確認

/$ echo PATH


/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games:/home/pi/samba/armv6hf-sdk

9.sambaに適当なJAVAFXを含む実行可能jarを配置
(例)
・コマンドラインに以下を入力すると実行できる

$ java --module-path /home/pi/samba/armv6hf-sdk/lib --add-modules=javafx.controls,javafx.fxml -jar /home/pi/samaba/test.jar

EclipseでjavaFXプロジェクトを組む方法

1.javaFX - javaFXプロジェクトを作成
  2.プロジェクト名を入力
3.ライブラリ追加でユーザーライブラリのJAVAFX11を配置
4.JAVAFX11のネイティブライブライブラリロケーションを設定
(例)C:\javafx-sdk-11.0.2\bin
5.宣言的UIの言語をFXMLを選択
名前などは適宜変更
 6.完了
 7.XXX.fxmlをシーンビルダーで編集
  XXXXController.javaにコードを記載
 8.実行環境hはjava8を使用 java13を使っても良いがコマンドライン引数にモジュール化の宣言必要
9.実行可能jarを作成 ユーザーライブラリのJAVAFX11はパッケージ化必要
10./home/pi/samba に配置し実行権限付与 ※sambaで共有させたラズベリーパイ側のフォルダに直接出力
11.以下、例に従いテスト実行

$ java --module-path /home/pi/samba/armv6hf-sdk/lib --add-modules=javafx.controls,javafx.fxml -jar /home/pi/samaba/test.jar


12.自動起動の設定

以下コマンドを実行して、autostartファイルの雛形を、ホームディレクトリのコンフィグにコピー

$ mkdir -p ~/.config/lxsession/LXDE-pi
$ cp /etc/xdg/lxsession/LXDE-pi/autostart ~/.config/lxsession/LXDE-pi/

13.autostartを編集

$ sudo nano ~/.config/lxsession/LXDE-pi/autostart


 設定ファイル末尾に追記

@java --module-path /home/pi/samba/armv6hf-sdk/lib --add-modules=javafx.controls,javafx.fxml -jar /home/pi/samaba/test.jar


 
 14.再起動し自動起動を確認

最後に

ポイントはJavaFXの*.soファイルがある場所にパスを通すことと、実行時にモジュール化の引数を入れること

今回の、方法だとmediaplayerが例外が出て動かない。
ffmpegが、インストールされてない?よくわからない
うまく行ったら、また記載する

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

JavaでTODOアプリを制作しよう3 MySQLに仮のデータを保存 -> 全取得 -> topに表示する

こんにちは。
JavaでTODOアプリを制作しよう2 Spring Initializrで雛形を作ってHello worldしたいの続きの記事です。

今回はデータベース(MySQL)に仮のデータを保存し、それらを全取得してtop画面に表示してみようと思います。

TODOアプリ作成リンク集

1: MVCの簡単な説明
2: Spring Initializrで雛形を作ってHello worldしたい
3: JavaでTODOアプリを制作しよう3 MySQLに仮のデータを保存 -> 全取得 -> topに表示する(今ここ)

MySQL及びSequelProのインストール

まずはDBと便利なGUIを導入します。

今回ここでは詳しい導入については明記しませんが、下記のリンクが参考になるかと思います。

MySQLの導入について

MySQL環境構築手順
MySQL 入門

SequelPROの導入について

Mac MySQL Sequel Proの導入方法

DB・テーブルの作成 -> 仮のデータを作る

上記のリンクに従ってDBを作成します!

この記事ではqiita_todoというDB名にすることにします。

DBができたらSequelProを起動してDBと接続します。

そしてQueryタブに下記を入力。

CREATE TABLE `todo` (
  `id` bigint(11) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `title` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'タイトル',
  `deadline` date NOT NULL COMMENT '期日',
  `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'ステータス',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '作成日時',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新日時',
  PRIMARY KEY (`id`),
  UNIQUE KEY `title` (`title`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

↓画像だとこんな感じです。
Screen Shot 0002-07-06 at 16.34.44.png

Query文を実行するためにCtrl + rを押すと・・・

↓この様にテーブルが作成されるはず。(Structureタブでカラムの仕様を確認できます。)

Screen Shot 0002-07-06 at 16.35.15.png

JPAの導入

DBが終わったらお次はJPAを導入しましょう。

JPAとはSpringとDBを繋いでくれる翻訳家みたいなものです。SpringからのリクエストをDBへと渡して色々処理してくれます。

例えばJPAのfindAllというメソッドを使うとDBのデータを全取得してくれたりします。このメソッドについては後述します。

build.gradleにJPAとMySQLを追加

app/build.gradleのdependenciesに下記を追加します。

dependencies {
   〜〜〜省略〜〜〜
   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
     runtimeOnly 'mysql:mysql-connector-java'
   〜〜〜省略〜〜〜
}

build.gradleを更新する

下の画像に従ってbuild.gradleの変更部分を更新するとJPAが使える様になります!
Screen Shot 0002-07-06 at 16.58.34.png

application.properties(もしくはapplication.yaml)の編集

続いて作成したDBとSpringを接続するためにapplication.properties(or yaml)を編集します。

ファイルの配置場所は app/src/main/application.properties となります。

下記を追加します。

spring.datasource.url=jdbc:mysql://localhost:3306/qiita_todo # ①
spring.datasource.username=root # ②
spring.datasource.password=     # ②
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.initialization-mode=never # ③
spring.datasource.sql-script-encoding=utf-8
spring.jpa.hibernate.ddl-auto=none # ③


3306はデフォルトのポート番号の筈ですが変えてしまっている場合は変更されたものにしてください。
qiita_todoの部分はDB名となります。


こちらもusernameとpasswordはご自身で設定されたものがある場合はそちらで。この記事ではusernameがrootでパスワードは無しとしています。
(コピペするとpasswordがスペース扱いされるかもしれないのでご注意を。)


ここら辺の設定は任意で変えて問題ないので気になる方はググってみてください(投げやり

データを入れてみる

設定が終わったので次は仮のデータを入れてみます。

SequelProを開いてContentタブを押して空のレコードをダブルクリックすると入力できるので適当な値を入れてやります。

Screen Shot 0002-07-12 at 11.41.19.png

ControllerからDBのレコードを全取得する

お次はいよいよInteliJの出番です。

まずはDBからデータを取ってくる際に使う入れ物(Entity)を用意します。Lombokを導入すると色々便利なのでまずはそこから。

Lombokの導入

Lombokを使うことでGetterやSetterなどの入力が楽になります。

build.gradleのdependenciesに下記を追加します。

dependencies {
   〜〜〜省略〜〜〜
    compileOnly 'org.projectlombok:lombok:1.18.12'
    annotationProcessor 'org.projectlombok:lombok:1.18.12'
   〜〜〜省略〜〜〜
}

追記したらJPAを入れた時と同じ様にgradleを再読み込みしましょう。

Entityクラスの作成

お次はEntityの作成です。

TodoContorollerがある階層にまずはパッケージ(フォルダ)を作ります。

main/java/com.example.todo <-この階層を右クリックしてNew -> Packageとクリックすると作成できます。

今回はパッケージ名をdaoとつけることにします。

作ったdaoを右クリック -> New -> Java Classと押してTodoEntityというクラスを作成します。

Screen Shot 0002-07-12 at 11.54.12.png

Entityクラスを編集する

@Entity
@Getter
@Setter
@Table(name="todo")
public class TodoEntity {
    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="title")
    private String title;

    @Column(name="deadline")
    private LocalDate deadline;

    @Column(name="status")
    private boolean status;

    @CreationTimestamp
    @Column(name="create_time")
    private LocalDateTime createTime;

    @UpdateTimestamp
    @Column(name="update_time")
    private LocalDateTime updateTime;
}

こんな感じでEntityクラスを編集します。

@Columnとすることでテーブルに対応する変数を作成することができます。

@CreationTimestamp@UpdateTimestampは作成時・更新時に時間を自動で入れてくれるアノテーションです。

これでEntityクラスが出来たので、データベースからデータを貰ったり送ったりする準備が出来ました。

Repositoryの作成

次はRepositoryクラスを作成します。このクラスが実際にアプリケーションとデータベースとのやりとりをするクラスになります。
(全取得したり編集したり削除したりするのがこのクラスの役割です)

先ほど作ったdaoパッケージにTodoRepositoryクラスを作成します。

@Repository
public interface TodoRepository extends JpaRepository<TodoEntity, Long> {
}

編集内容はこれだけでOKです。(importは省略してます。)

extendsでJpaRepositoryクラスを継承しています。

こちらのサイトで詳しいRepositoryの説明をされているので気になる方はチェックしてみましょう。

これでTodoRepositoryを介して取ってきたデータをTodoEntityにぶち込む用意ができました!

Serviceクラスの作成

さて次はServiceクラスの作成です。

今回のTodoアプリにおけるServiceクラスの役割はRepositoryクラスにDB関係の処理をお願いすることです。

つまりはビジネスロジックをこのクラスに書きます。

main/java/com.example.todo配下にTodoServiceクラスを作成します。

@Service
@RequiredArgsConstructor
public class TodoService {
    private final TodoRepository todoRepository;

    public List<TodoEntity> findAllTodo() {
        return todoRepository.findAll();
    }
}

こんな感じになります。(importは省略してます。)

@RequiredArgsConstructorはデフォルトコンストラクに関するLombokのアノテーションになります。

finalで宣言されたメンバ(この場合todoRepository)に対する引数付きのデフォルトコンストラクタを自動生成してくれるアノテーションです。

こちらこちらのサイトを参考にしてみると良いでしょう。

Controllerの編集

さてService, Entity, Repositoryと作成が済んだので次にコントローラの編集を行います。

@Controller
@RequiredArgsConstructor
public class TodoController {

    private final TodoService todoService;

    @GetMapping("/top")
    public String top(Model model){
        List<TodoEntity> todoEntityList =  todoService.findAllTodo(); //①
        model.addAttribute("todoList", todoEntityList); //②
        return "top";
    }
}

こんな感じです。


先ほど作ったServiceクラスからDBのレコードを全取得して変数 todoEntityListに戻り値Listとして返す。


todoEntityListをtodoListという変数にしてフロントに渡す。

top.htmlの編集

<!DOCTYPE html>
<!--↓①-->
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>hello</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>
<!--↓②-->
<div th:each="todo: ${todoList}" class=" w-75 h-25 my-1 mx-auto pt-5">
    <div class="container">
        <div  class="row">
            <div class="col-5 pl-5">
                <p th:text="${todo.title}" class="mb-1"></p>
                <p class="mb-1">期限:<span  th:text="${todo.deadline}"></span></p>
                <p class="mb-1">作成日時:<span  th:text="${todo.createTime}"></span></p>
            </div>
            <div class="col-2 d-flex justify-content-start align-items-center px-0">
                <a class="h-100 w-75 btn btn-info pt-4">
                    編集
                </a>
            </div>
        </div>
    </div>
</div>

<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" 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>

簡単に綺麗に表示するためにBootStrapを使用しています。あまり綺麗な書き方出ない点はご了承ください!


まずはここでThymeleafの使用を宣言します。これでth:〜〜みたいなメソッドを使うことができる様になります!
下でも使っているようにth:eachとかth:textとか色々あるのでこちらを参考にしてみてはどうでしょうか。


このeachで先ほどコントローラに記述したリストを分解して使っています。
todoListとして渡ってきたものをHTML内ではtodoという名前にして処理しています。
あとはtodo.titleとかdeadlineとかリストに入っている変数を呼び出すことで表示することができます!

↓アプリを実行してうまくいけばこんな感じで表示されている筈です・・・・!

Screen Shot 0002-07-12 at 13.40.26.png

簡単なまとめ

今回行ったことは・・・

① TodoContorollerでTodoServiceのfindAllTodoを呼ぶ

② findAllTodoはTodoRepositoryのfindAllを呼ぶ

③ findAllはDBから全レコードを取ってくる

④ 全データはTodoEntityに入る

⑤ データがTodoControllerに戻ってきてそれをフロントに渡して表示!

という流れです!

一つのクラスにたくさんの処理を持たせるのでなく、様々なクラスを作って処理を分散させてエラーが起きた時に対応しやすくしているのでした!

続きはまた今度!

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

google-http-clientのHTTPヘッダをログに出力する

GCPクライアントライブラリが発行しているHTTPリクエストヘッダ/レスポンスヘッダをログに出したい、というニーズにお答えします。

追記: 本家に記載があることに気が付きました。内容は今のところ当メモとだいたい同じですが、本家サイトを見ていただくのがいいと思います。
https://googleapis.github.io/google-http-java-client/http-transport.html

リクエストヘッダだけをログに出したい(ボディは出したくない)方は、当記事が参考になると思います。
(追記終わり)


下調べ

google-http-clientでは、JUL(java util logging)を使っているようです。

  • ログ出力に使っているLoggerは com.google.api.client.http.HttpTransport クラスのLOGGER定数です。
    • クラスごとにLoggerを持つのではなく、HttpTransport定数を各クラスから参照するという指針のようです。
  • ログレベルをCONFIGにすればログが出そうです。

このことから、HttpTransportのLoggerに対してログレベルをCONFIGに設定すれば、出力できそうだとアタリを付けました。

ログに出してみる

ログに出すアプローチは2種類です。

アプローチ1: logging.propertiesで出力する

JULはファイルで設定できるので、まずはそのアプローチを試してみます。

参考: https://docs.oracle.com/javase/jp/8/docs/api/java/util/logging/LogManager.html

設定ファイル logging.properties を作ります。

logging.properties
handlers=java.util.logging.ConsoleHandler
java.util.logging.ConsoleHandler.level=CONFIG
com.google.api.client.http.HttpTransport.level=CONFIG

システムプロパティ java.util.logging.config.filelogging.properties へのパス(ファイルシステム上のパス) を指定して、実行してみます。

実行するのはこんなコードです。(Cloud Storageのバケットを一覧取得します)

final GoogleCredentials credentials = createCredentials();
final StorageOptions storageOptions = StorageOptions.newBuilder().setCredentials(credentials).build();
final Storage storage = storageOptions.getService();
final Page<Bucket> buckets = storage.list();
for (final Bucket bucket : buckets.iterateAll()) {
    System.out.println("bucket: " + bucket);
}

出力結果

コンソールにリクエスト/レスポンスが出力されました。(下調べは当たっていたことが分かりました)

7月 12, 2020 12:14:09 午前 com.google.api.client.http.HttpRequest execute
構成: -------------- REQUEST  --------------
GET https://storage.googleapis.com/storage/v1/b?project=xxxx&projection=full
Accept-Encoding: gzip
Authorization: <Not Logged>
User-Agent: (略)


7月 12, 2020 12:14:09 午前 com.google.api.client.http.HttpRequest execute
構成: curl -v --compressed -H 'Accept-Encoding: gzip' -H 'Authorization: <Not Logged>' -H 'User-Agent: (略)
7月 12, 2020 12:14:10 午前 com.google.api.client.http.HttpResponse <init>
構成: -------------- RESPONSE --------------
HTTP/1.1 200 OK
Alt-Svc: (略)
Server: UploadServer
Cache-Control: private, max-age=0, must-revalidate, no-transform
X-GUploader-UploadID: (略)
Vary: X-Origin
Vary: Origin
Expires: Sat, 11 Jul 2020 15:14:10 GMT
Content-Length: 3228
Date: Sat, 11 Jul 2020 15:14:10 GMT
Content-Type: application/json; charset=UTF-8


7月 12, 2020 12:14:10 午前 com.google.api.client.util.LoggingByteArrayOutputStream close
構成: Total: 3,228 bytes
7月 12, 2020 12:14:10 午前 com.google.api.client.util.LoggingByteArrayOutputStream close
構成: {
  "kind": "storage#buckets",
  "items": (略)

このように試してみると、狙い通りだったことと、想定していなかったことの両面に気が付きます。

意図したとおりだったこと

無事にリクエストとレスポンスのヘッダが出力されました。

意図していなかったこと

  • ヘッダだけでなくボディも出力されました。
    • LoggingByteArrayOutputStream クラスからボディが出力されています。
    • ヘッダは、 HttpRequestHttpResponse クラスから出力されています。
  • Authorizationヘッダの値が Authorization: <Not Logged> と省略されています。
    • ログレベルをCONFIG → ALLに変えると、値が表示されます。
  • curlコマンドで同じリクエストを発行するための文字列が出力されています。
  • (JULデフォルトのログを久しぶりに目にしましたが、読みづらいですね。 logging.properties へformatterを指定したいところです)

ボディを出さないようにしたいのですが、設定ファイルでは表現できないように思います。

アプローチ2: プログラムで出力する

次はプログラムで設定するアプローチを試してみます。

JULのLoggerへ、先程の設定ファイルと同じような指示をします。

import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpTransport;

(略)

final Logger logger = Logger.getLogger(HttpTransport.class.getName());
logger.setLevel(Level.CONFIG);
// TODO addしたHanderを戻す(remove)ようにする
logger.addHandler(new Handler() {
    @Override public void publish(final LogRecord record) {
        // 今回はヘッダだけを出したい(ボディは出したくない)ので、HttpRequestとHttpResponseだけにします。
        if (HttpRequest.class.getName().equals(sourceClassName) || HttpResponse.class.getName().equals(sourceClassName)) {
            final String date = DateTimeFormatter.ISO_INSTANT.format(Instant.ofEpochMilli(record.getMillis()));
            final String msg = "[" + logger.getLevel() + "] " + date + " " + record.getMessage();
            // TODO 自身のロガーへどうぞ
            System.out.println(msg);
        }
    }

    @Override public void flush() {
    }

    @Override public void close() throws SecurityException {
    }
});

出力結果

先ほどと同じ(Cloud Storageのバケットを取得する)コードを実行してみると、
今度はボディが出力されなくなりました。(curl用の文字列は出力されるままですけれど)

[CONFIG] 2020-07-11T13:37:42.844Z -------------- REQUEST  --------------
GET https://storage.googleapis.com/storage/v1/b?project=xxxx&projection=full
Accept-Encoding: gzip
Authorization: <Not Logged>
User-Agent: (略)


[CONFIG] 2020-07-11T13:37:42.844Z curl -v --compressed -H 'Accept-Encoding: gzip' -H 'Authorization: <Not Logged>' -H 'User-Agent: (略)
[CONFIG] 2020-07-11T13:37:43.536Z -------------- RESPONSE --------------
HTTP/1.1 200 OK
Alt-Svc: (略)
Server: UploadServer
Cache-Control: private, max-age=0, must-revalidate, no-transform
X-GUploader-UploadID: (略)
Vary: X-Origin
Vary: Origin
Expires: Sat, 11 Jul 2020 13:37:43 GMT
Content-Length: 3228
Date: Sat, 11 Jul 2020 13:37:43 GMT
Content-Type: application/json; charset=UTF-8

所感

2種類のアプローチを試しました。私のユースケースは

  • ヘッダだけを出したいケース
  • ヘッダとボディ両方を出したいケース

の2つがあるため、プログラムで出力するアプローチの方が良さそうです。
(記事中のコード例ではヘッダだけを出すようにしましたが、ヘッダとボディの両方を出力できるように変えることは難しくありません。)

一方で、ヘッダとボディ両方を出したいというユースケースだけでしたら、設定ファイルアプローチで十分ですね。

環境

動作確認したバージョンです。

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