- 投稿日:2020-07-12T23:50:14+09:00
JavaとJavaScript 間違えられやすい問題
はじめに
ちょっとキニなることを叫びます。
たぶん、これを読む皆さんも
おそらく聞いたことがあると思います。
結論から言います
JavaとJavaScriptは違います!!!
なんで間違えてしまうん
単純に「勉強不足」と片づけてしまえれば
楽なのですが
なぜか、頑なにJavaScriptを「Java」と
発言してしまう方がチラホラいます。
なぜこうなるのか
JavaScriptという言語自体が
Java開発に参画していたSun Microsystems社とともに
開発された言語だった為
でもさあのさ
うんうん、わかるよわかるよその気持ち
ホンマのIT技術者ならばこれくらいの違いは
わかって当然だよね。
それでもわからない人がいる
なんでや!!
ITの一般常識だと思っていますが
そもそもITに詳しくない人がIT業界には
一定数存在しています。(それはそれでおかしい)
Javaの有償化による大きな勘違い 事例
もともと、職場には
「JavaScript」を使ったアプリがありました。
Javaが有償化された時
上記のアプリを活用するにあたり
有償化になるけど大丈夫か
などという質問をいただきました。
事例の前提
要件定義や仕様の段階で
Javaを使っていないことは既にお断り済
リテラシーの問題か?
それでも「Java」と入っているので
何をどう勘違いしたのか
たびたび質問される方がおります。
こればかりはもう勉強してもらうしかないですね。
真に日本でエンジニアと呼ばれる人たち
おそらく
毎日勉強してらっしゃる方々だと思います。
新しい情報にしっかりキャッチアップして
進化していくことがエンジニアに重要なことだと
思います。過激派っぽい発言をするのであれば
そうでなければエンジニアではない。
エンジニアと名乗る方がいても
私の思う定義から外れれば
自分と同じ土俵には立っていない。
そう思います。
おわり
- 投稿日:2020-07-12T23:24:58+09:00
【実践‼】MyBatisを使用する際の最低限の設定
1.事前知識
- 【初心者でもできる‼】Windows10にEclipseをインストールする方法(Java環境構築)
- 【実践‼】Spring Boot で Hello World を表示する
- 【実践‼】Javaデータベース連携(Connector/J 8.0.20)
事前知識として、上記リンクの内容が必要です。
2.事前準備
1. 検索ボックスにcmd
と入力し、コマンドプロンプト
を起動する。
2.mysql -u ユーザ名 -p
でログイン
する。
test.sqlcreate 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");
- 上記の
SQL文
をコピーし、コマンドプロンプト
内で実行する。- 画像のように
Query OK
が出れば成功。3.Spring Boot プロジェクトの作成
1.[ファイル(F)]→[新規(N)]→[Spring スターター・プロジェクト]
を選択する。
2. 名前にMyBatisTest
と入力し、Javaバージョン:8
を選択して次へ(N) >
ボタンをクリックする。
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.htmlEntity.javapackage com.example.demo; import lombok.Data; @Data public class Entity { private int id; private String name; }TestController.javapackage 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.propertiesspring.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 = updatemybatis-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>[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.125 s [INFO] Finished at: 2020-07-05T21:50:22+09:00 [INFO] ------------------------------------------------------------------------2.
コンソール
に 上記の文が表示されれば成功。
3.MyBatisTest [boot]
を右クリックして[実行(R)]→[9 Spring Boot アプリケーション]
選択する。
4. localhost:8080 にアクセスし、画像のように表示されれば成功。5.関連
- 【覚えておくと便利!!!】Eclipseにおけるコンストラクタとgetter/setterの簡易作成
- 【覚えておくと便利!!!】Eclipseにおける継承クラスの簡易作成
- 【覚えておくと便利!!!】MySQLの文字コード変更
- 【初心者でもできる‼】Javadocの書き方
- 【わかりやすく解説‼】Javaのオーバーロードの使い方
- 【わかりやすく解説‼】Javaのカプセル化の使い方
- 【わかりやすく解説‼】Javaの継承の使い方【オーバーライドの解説あり】
- 【わかりやすく解説‼】Javaにおける参照型の型変換
- 【わかりやすく解説‼】Javaのポリモーフィズムの使い方
- 【わかりやすく解説‼】ArrayListの使い方【Java】
- 【実践‼】JFrameの導入(画面作成まで解説)
- 【実践‼】Javaデータベース連携(Connector/J 8.0.20)
- 【実践‼】SQLステートメントの実行
- Javaプログラミングの全て
- 投稿日:2020-07-12T22:43:13+09:00
Gradleで任意のフォルダのプロジェクトを追加する
はじめに
React NativeのAndroidプロジェクトで変則的なフォルダ構成だったのでGradleでうまく扱う方法を紹介します。
サブフォルダや同じ階層のフォルダの場合はGradleでマルチプロジェクトで実現できます。フォルダ構成
myapp/ android/ ★rootプロジェクト build.gradle settings.gradle ★これを編集する app/ ★アプリプロジェクト build.gradle library/ android/ ★ライブラリプロジェクト build.gradle設定
settings.gradleinclude ':app' // ここから追加 include ':library' project(':library').projectDir = file('../../library/android') // ここまで追加参考サイト
- 投稿日:2020-07-12T21:07:43+09:00
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.jspbuild.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 buildWAR ファイルが生成されていることを確認する。
$ file build/libs/mywebapp-1.0.war build/libs/mywebapp-1.0.war: Zip archive data, at least v1.0 to extractApache Tomcat 9 をインストールする
インストールするディレクトリを用意する
今回は任意のディレクトリに tomcat9 というディレクトリを作成してその中にインストールする。
$ mkdir tomcat9 $ cd tomcat9Apache 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-UNNAMEDWAR ファイルを Apache Tomcat 9 にデプロイする
WAR ファイルを Apache Tomcat 9 の webapps ディレクトリに設置する
$ cp mywebapp/build/libs/mywebapp-1.0.war tomcat9/apache-tomcat-9.0.37/webapps/mywebapp.warApache Tomcat 9 を起動する
$ ./tomcat9/apache-tomcat-9.0.37/bin/startup.shJava 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>参考資料
- Apache Tomcat 9 (9.0.37) - Documentation Index
- Apache Tomcat® - Which Version Do I Want?
- The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 369: Java Servlet 4.0 Specification
- The Java Community Process(SM) Program - JSRs: Java Specification Requests - detail JSR# 245: JavaServer Pages 2.1
- Overview (Servlet 4.0 API Documentation - Apache Tomcat 9.0.37)
- Overview (JSP 2.3 API Documentation - Apache Tomcat 9.0.37)
- Java EE: XML Schemas for Java EE Deployment Descriptors
- 投稿日:2020-07-12T19:03:58+09:00
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
メソッドの中身を任意の処理に書き換える必要があります。
トリガー - 属性 に記載の通り、
WarmupTrigger
属性は現状 C# のみサポートされており、C# スクリプト / JavaScript / Python / Java ではサポートされていません。
どういうことかと言うと、
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
一方で、
WarmupTrigger
は Java ではサポートされていないため、必要な情報を持つfunction.json
はビルド時に生成されません。必要な情報というのは トリガー - 構成 に記載された以下の項目群のことです。
ワークアラウンド
Java でウォームアップトリガーを利用するためには以下の対応が必要です。
@FunctionName("Warmup")
アノテーションが付与された Java メソッドを作成- 必要な情報を持つ
function.json
を作成function.json
をビルド成果物に同梱するよう設定- アプリケーションをビルドして Azure Functions にデプロイ
以下のサンプルコードに沿って説明していきたいと思います。
https://github.com/nakazax/azure-functions-premium-warmup-java1.
@FunctionName("Warmup")
アノテーションが付与された Java メソッドを作成トリガー - 例 をそのまま流用する形で実装しています。
run
メソッドの中身を任意の処理に書き換えてご利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/java/com/functionp/Warmup.javaazure-functions-premium-warmup-java/src/main/java/com/functionp/Function.javapackage 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
を作成しておきます。scriptFile
やentryPoint
は自身のアプリケーションに合わせて書き換えて利用ください。
https://github.com/nakazax/azure-functions-premium-warmup-java/blob/master/src/main/resources/Warmup/function.jsonazure-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.json
をazure-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.xmlazure-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
Azure Functions
[デプロイ センター] に「production に正常にデプロイされました」と表示される
[関数] > [Warmup] をクリックすると以下のような詳細が表示される
[ログ] > [traces] を見ると Warmup が実行された旨、表示される
(参考) 必要な情報を持つ
function.json
が存在しない場合の挙動以下は Java のメソッドだけを実装して Azure Functions にデプロイした場合の挙動です。Azure Pipelines でのビルド & デプロイ自体は成功しますが、Azure Functions の [デプロイ センター] に「production にデプロイできませんでした」と表示され、デプロイに失敗します。
(最初、原因が分からずにハマりました。。。)Azure Pipelines
Azure Functions
[デプロイ センター] に「production にデプロイできませんでした」と表示される
以上です。
- 投稿日:2020-07-12T15:23:38+09:00
画像はアップロードされたのに、画面には表示されない!?
困ったこと
Spring Bootを使って、画像をアップロードする。
アップロード直後は画像がHTML上に表示されず、30秒程待って画面をリフレッシュすると表示される。画像のアップロード方法
画像ファイル -> /resource/static/images/配下
ファイル名(xxx-20200712151409.jpgなどのファイル名) -> DBに格納解決方法
ルートフォルダ直下にフォルダ(仮に、imagesとする)を作る
今回は、RamenKingdomというルートフォルダの直下にimagesを作成しています。
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配下には格納しない!!!
以上です。最後まで読んでくださり、ありがとうございました。
- 投稿日:2020-07-12T15:03:56+09:00
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-imageQUARKUS - 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は
tar
やunzip
ができないので色々入れておきます。
全部必要かわからないですが、これぐらい入れておきました。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-mavenmvnのバージョン確認
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.properties
のquarkus.lambda.handler
にて設定します。resources/application.propertiesquarkus.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
というファイルも生成されるので、ハンドラー名や環境変数はこちらを参考にしました。関数を新規作成します。
ランタイムをユーザー独自のブートストラップを提供する
とします。関数を作成したら、プログラムをアップロードします。
関数コードのところのアクション
から.zipファイルをアップロード
を選び、function.zip
を選択します。ハンドラーは
not.used.in.provided.runtime
となります。環境変数に
DISABLE_SIGNAL_HANDLERS
を追加し値をtrue
とします。ここまでで設定は完了です。以下のJSONをインプットにテスト実行してみましょう。
{ "name": "Bill", "greeting": "hello" }動作結果がこちら。無事動きました。
AWS SDK を追加
AWS SDKを使用するには単純にpom.xmlに追加するだけでなく、いくつか設定が必要です。
手順1. SSL通信の有効化
resources/application.properties
に以下の内容を追加します。(公式ドキュメント的にはデフォルトで有効って書いてある気もしますが)resources/application.propertiesquarkus.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.so
とcacerts
をコピーします。この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#事前準備した
bootstrap
cacerts
libsunec.so
が含まれているのがわかります。終わりに
通常のJavaランタイムよりも、コールドスタートが早くなる検証結果はこちらで確認ください。
QuarkusがJava Lambdaを救う!?
https://qiita.com/moritalous/items/4de31a66edac728ba088
- 投稿日:2020-07-12T14:35:30+09:00
ラズパイで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が、インストールされてない?よくわからない
うまく行ったら、また記載する
- 投稿日:2020-07-12T13:51:15+09:00
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の導入について
SequelPROの導入について
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;Query文を実行するためにCtrl + rを押すと・・・
↓この様にテーブルが作成されるはず。(Structureタブでカラムの仕様を確認できます。)
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が使える様になります!
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タブを押して空のレコードをダブルクリックすると入力できるので適当な値を入れてやります。
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というクラスを作成します。
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とかリストに入っている変数を呼び出すことで表示することができます!↓アプリを実行してうまくいけばこんな感じで表示されている筈です・・・・!
簡単なまとめ
今回行ったことは・・・
① TodoContorollerでTodoServiceのfindAllTodoを呼ぶ
② findAllTodoはTodoRepositoryのfindAllを呼ぶ
③ findAllはDBから全レコードを取ってくる
④ 全データはTodoEntityに入る
⑤ データがTodoControllerに戻ってきてそれをフロントに渡して表示!
という流れです!
一つのクラスにたくさんの処理を持たせるのでなく、様々なクラスを作って処理を分散させてエラーが起きた時に対応しやすくしているのでした!
続きはまた今度!
- 投稿日:2020-07-12T02:14:59+09:00
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.propertieshandlers=java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level=CONFIG com.google.api.client.http.HttpTransport.level=CONFIGシステムプロパティ
java.util.logging.config.file
へlogging.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
クラスからボディが出力されています。- ヘッダは、
HttpRequest
とHttpResponse
クラスから出力されています。- 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つがあるため、プログラムで出力するアプローチの方が良さそうです。
(記事中のコード例ではヘッダだけを出すようにしましたが、ヘッダとボディの両方を出力できるように変えることは難しくありません。)一方で、ヘッダとボディ両方を出したいというユースケースだけでしたら、設定ファイルアプローチで十分ですね。
環境
動作確認したバージョンです。