- 投稿日:2020-10-28T22:37:55+09:00
thymleaf備忘録
#{...}
#{...}
は、メッセージ式です。外部のテキストを読み込めます。
th:field
th:field
(タグのid・name・value属性を、HTMLに出力する機能)
th:errorclass= "..."
th:errorclass= "..."
エラーがある場合に、class属性にスタイルを追加- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、又はname属性から取得するため、これらと一緒に使う必要がある
spring-mvc3/src/main/resources/templates/confirm.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- webjars locatorあり(バージョン管理が不要)--> <link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}"> <title>Title</title> </head> <body class="bg-light"> <div class="container"> <div class="row justify-content-md-center"> <div class="col-md-8 order-md-1"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <h4 class="border-bottom my-3" th:text="#{invoice}"></h4> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <p class="text-danger" th:text="#{confirmationMessage}"></p> <div th:object="${invoice}"> <div class="form-group"> <!-- label要素のfor属性は、ラベル付け対象であるラベル付け可能フォーム関連要素のid属性値を指定する属性 --> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)--> <label for="name" th:text="#{name}"></label> <input type="text" class="form-control" th:field="*{name}" disabled> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)--> <label for="address" th:text="#{address}"></label> <input type="text" class="form-control" th:field="*{address}" disabled> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)--> <label for="phoneNumber" th:text="#{phoneNumber}"></label> <input type="tel" class="form-control" th:field="*{phoneNumber}" disabled> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)--> <label for="price" th:text="#{price}"></label> <input type="text" class="form-control" th:field="*{price}" disabled> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:field(タグのid・name・value属性を、HTMLに出力する機能)--> <label for="paymentDeadline" th:text="#{paymentDeadline}"></label> <input type="date" class="form-control" th:field="*{paymentDeadline}" disabled> </div> </div> </div> </div> </div> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script> <script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script> </body> </html>spring-mvc3/src/main/resources/templates/index.html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- webjars locatorあり(バージョン管理が不要)--> <link rel="stylesheet" th:href="@{/webjars/bootstrap/css/bootstrap.min.css}"> <title>Title</title> </head> <body class="bg-light"> <div class="container"> <div class="row justify-content-md-center"> <div class="col-md-8"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <h4 class="border-bottom my-3" th:text="#{invoice}"></h4> <form th:action="@{/}" th:object="${invoice}" method="post" novalidate> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 --> <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、 又はname属性から取得するため、これらと一緒に使う必要がある。 --> <label for="name" th:text="#{name}"></label> <input type="text" class="form-control" th:errorclass="is-invalid" th:field="*{name}"> <div class="invalid-feedback" th:errors="*{name}"></div> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 --> <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、 又はname属性から取得するため、これらと一緒に使う必要がある。 --> <label for="address" th:text="#{address}"></label> <input type="text" class="form-control" th:errorclass="is-invalid" th:field="*{address}"> <div class="invalid-feedback" th:errors="*{address}"></div> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 --> <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、 又はname属性から取得するため、これらと一緒に使う必要がある。 --> <label for="phone" th:text="#{phoneNumber}"></label> <input type="tel" class="form-control" th:errorclass="is-invalid" th:field="*{phoneNumber}"> <div class="invalid-feedback" th:errors="*{phoneNumber}"></div> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 --> <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、 又はname属性から取得するため、これらと一緒に使う必要がある。 --> <label for="price" th:text="#{price}"></label> <input type="text" class="form-control" th:errorclass="is-invalid" th:field="*{price}"> <div class="invalid-feedback" th:errors="*{price}"></div> </div> <div class="form-group"> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <!-- th:errorclass= "..." エラーがある場合に、class属性にスタイルを追加 --> <!-- エラーのチェックは、th:field(タグのid・name・value属性を、HTMLに出力する機能)、 又はname属性から取得するため、これらと一緒に使う必要がある。 --> <label for="paymentDeadline" th:text="#{paymentDeadline}"></label> <input type="date" class="form-control" th:errorclass="is-invalid" th:field="*{paymentDeadline}"> <div class="invalid-feedback" th:errors="*{paymentDeadline}"></div> </div> <!-- #{...}は、メッセージ式です。外部のテキストを読み込めます。 --> <button class="btn btn-primary btn-lg btn-block my-4" type="submit" th:text="#{register}"></button> </form> </div> </div> </div> <script th:src="@{/webjars/jquery/jquery.min.js}"></script> <script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script> <script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script> </body> </html>参考にした記事(いつもありがとうございます。)
- 投稿日:2020-10-28T22:00:12+09:00
メールの添付ファイル名が化ける
現象
Spring Boot 2.3.4 (+ spring-boot-starter-mail)で開発したアプリから送信したメールの添付ファイルの名前が文字化けする場合があることに気づきました。
これはOutlookで受信したメールの添付ファイルの表示です。
以下はその簡略化したメール送信コードです。import org.springframework.mail.javamail.JavaMailSender import org.springframework.mail.javamail.MimeMessageHelper import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.CommandLineRunner import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class SampleMailApplication : CommandLineRunner { @Autowired lateinit var mailSender: JavaMailSender override fun run(vararg args: String?) val message = mailSender.createMimeMessage() val helper = MimeMessageHelper(message, true) helper.setTo(username) helper.setFrom(username) helper.setSubject("送信") helper.setText("テスト") helper.addAttachment("あいうえおかきくけこ", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("あいうえおかきくけこさ", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("あいうえおかきくけこさし", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("あいうえおかきくけこさしす", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("あいうえおかきくけこさしすせ", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("1234567890", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("12345678901234567890", ByteArrayResource("あいうえお".toByteArray()), "plain/text") helper.addAttachment("123456789012345678901234567890", ByteArrayResource("あいうえお".toByteArray()), "plain/text") mailSender.send(message) } } fun main(args: Array<String>) { runApplication<SampleMailApplication>(*args) }ファイル名が全角13文字以上だと、文字化けというかエンコードされたままの表示になります。
メールの確認
メールのソースでContent-dispositionを確認すると
// あいうえおかきくけこさし Content-disposition: attachment; filename*=UTF-8''%E3%81%82%E3%81%84%E3%81%86%E3%81%88%E3%81%8A%E3%81%8B%E3%81%8D%E3%81%8F%E3%81%91%E3%81%93 // あいうえおかきくけこさしす Content-disposition: attachment; filename="=UTF-8B44GC44GE44GG44GI44GK44GL44GN44GP44GR44GT44GV44GX44GZ44Gb="全角12文字と13文字を境に、属性名がfilename*とfilenameだとか、エンコードされた文字列のフォーマットが変わっていました。
またOutlookで同じ添付ファイルを送ると、当然ファイル名が化けることなく、ソースを確認すると全角13字以上のファイル名でもfilename*のパターンが使われていました。ソースコードの調査
そこで調べていくとMimeMessageHelperに次のコードがありました。
public void addAttachment(String attachmentFilename, DataSource dataSource) throws MessagingException { Assert.notNull(attachmentFilename, "Attachment filename must not be null"); Assert.notNull(dataSource, "DataSource must not be null"); try { MimeBodyPart mimeBodyPart = new MimeBodyPart(); mimeBodyPart.setDisposition(MimeBodyPart.ATTACHMENT); mimeBodyPart.setFileName(isEncodeFilenames() ? MimeUtility.encodeText(attachmentFilename) : attachmentFilename);文字数で処理が分岐する訳ではないですが、フラグでMimeUtility.encodeTextするか制御しているので何かあるのかも?
そこでhelper.addAttachmentを呼び出す前に以下のコードを追加すると、全角13文字以上でもファイル名が化けなくなりました。helper.isEncodeFilenames = false
- なお、このフラグは丁度Spring Boot 2.3で使われているSpring Framework 5.2から追加されていました。
根本的な原因まで深堀りできませんでしたが、とりあえず解決できたので良しとします。
- 投稿日:2020-10-28T20:33:26+09:00
JavaでSSLデバッグするときの設定
- 投稿日:2020-10-28T15:33:33+09:00
〇〇env系ツールをanyenvで一括管理する
はじめに
この記事は フューチャーAdvent Calendar 2020 の20日目の記事です。
環境構築に便利な〇〇env系ツールを、もっと便利に扱える
anyenv
に感動したので、ブログ化しました。開発言語のバージョン管理
複数プロジェクトに関わる場合、開発言語のバージョン管理は必須スキルです。
他の開発メンバーと利用バージョンを一致させなければ、フォーマッタ適用 → PRでの差分爆発が生じてレビュアーに袋叩きにされます。なので、各エンジニアはそれぞれの創意工夫により、開発言語のバージョン管理問題を解決しています。
私の場合、環境構築ポリシーとして
- ローカル環境への直ダウンロードは厳禁
- 環境構築は基本的にDockerを使うという方針を、ずっと貫いています。
...
スミマセン。強がりです。
貫けていませんでした。〇〇env の多用
コロナ影響で在宅勤務へ移行した結果、NWの問題によりDockerがうまく使えない状況が増え、仕方なく〇〇env系ツールの利用を開始しました。
インストールした〇〇env系ツールは
- PJ開発はメインでgoを使う → goenv
- 環境Aはpythonのみ動く → pyenv
- 環境Bはrubyのみ動く → rbenv
- AWSのMFA(多要素認証)でet-otpを使う → jenvというもので、開発作業で新しい言語が必要になるたびに、対応するツール1つ1つ追加していました。
これらは同じコマンド操作で設定可能なため、ストレスになるのはパッケージのダウンロード時間くらいだと思っていましたが、知らぬ間に開発環境汚染が進んでいました。
bash_profileが汚れていた
担当業務で Vue.js のコードに触れる機会があり、新たに
nodenv
をインストールしようとしたところ
- 〇〇env系ツールの環境構築って、いっつも同じような環境変数を追加しているな
- bash_profileのメンテしてたっけ?ということに気づき、cat ~/.bash_profile すると、↓こんな感じになっていました
... # goenv export GOENV_ROOT=$HOME/.goenv export PATH=$GOENV_ROOT/bin:$PATH eval "$(goenv init -)" # pyenv export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" # jenv export JENV_ROOT=$HOME/.jenv export PATH="$HOME/.jenv/bin:$PATH" eval "$(jenv init -)" (rbenv, nodenv などの設定が続く...) ...あれ?
開発環境が汚れないよう注意していたはずなのに
所々で微妙な違いがあり、綺麗な状態とは言い難い...〇〇env系ツールが増えるごとに
bash_profileに追記し続けるのを止めたい...
anyenv が便利そう!
anyenvを使って綺麗にする
anyenv自体の説明前に、このツールを使うと何が綺麗になるか?から説明します。
すでにインストール済みのgoenv, pyenvなどはanyenv経由での再設定が必要になりますが、それを乗り越えれば、bash_profile に必要な
〇〇env系ツールの記述
は、以下のみで十分となります。将来的に新しい 〇〇env が必要になった場合でも、bash_profile への追記は不要です。... # anyenv eval "$(anyenv init -)" ...goenv, jenvごとにPATHを設定 & initする煩わしい記述から解放され、かつ、操作方法は従来の〇〇env系コマンドを利用可能なので、開発環境汚染を少しでも減らしたいエンジニアであれば、このツールを使わない理由は無いと思います。
anyenvとは?
anyenv - All in one for **env
This is a simple wrapper for rbenv style environment managers. You don't have to git clone or modify your shell profile for each **env anymore if you install anyenv.
https://github.com/anyenv/anyenv#anyenv---all-in-one-for-env
端的に言うと、複数の〇〇envを一括管理できる便利ツールです。
執筆時点(2020.12.10)では、以下〇〇envパッケージがanyenvでは利用可能です。
有名どころは網羅しているので、多くの開発プロジェクトで導入可能だと思います。$ anyenv install -l Renv #R crenv #Crystal denv #D erlenv #Erlang exenv #Elixir goenv #Go hsenv #Haskell jenv #Java jlenv #Julia luaenv #Lua nodenv #Node.js phpenv #PHP plenv #Perl pyenv #Python rbenv #Ruby sbtenv #Sbt scalaenv #Scala swiftenv #Swift tfenv #terraform上記から欲しい言語をインストールすれば、あとはいつも通りの〇〇env操作コマンドで環境構築可能です。
anyenvのインストール手順
macの場合
Homebrewを利用します。
$ brew install anyenv $ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile $ exec $SHELL -l $ anyenv install --init # enter "y"Linux環境の場合
GitHubから直接引っ張ってきます。
$ git clone https://github.com/anyenv/anyenv ~/.anyenv $ echo 'export PATH="$HOME/.anyenv/bin:$PATH"' >> ~/.bash_profile $ echo 'eval "$(anyenv init -)"' >> ~/.bash_profile $ exec $SHELL -l $ anyenv install --init # enter "y"最後に
$ anyenv install 〇〇env
で欲しいパッケージを取得すれば、これまで通りの〇〇envコマンドが利用できます。ex) node.jsのv15.0.1が欲しい場合
anyenvを利用し、nodenvを取得します。
$ anyenv install nodenv $ exec $SHELL -l $ anyenv versions nodenv: Warning: no Node detected on the system次は、nodenvでv15.0.1を取得します。
$ nodenv install 15.0.1 $ nodenv global(local) 15.0.1 $ node -v v15.0.1以上により、Node.js:v15.0.1 が利用可能となりました。
私の開発では goenv, pyenv, jenv, nodenv が必須なので、以下の設定を入れています。
$ anyenv versions
により、各envの設定状況が一覧できます。$ anyenv versions goenv: system 1.XX.0 * 1.YY.0 (set by /Users/<username>/.anyenv/envs/goenv/version) jenv: system * XX.0 (set by /Users/<username>/.anyenv/envs/jenv/version) nodenv: system * XX.0 (set by /Users/<username>/.anyenv/envs/nodenv/version) pyenv: system XX.0 * YY.0 (set by /Users/<username>/.anyenv/envs/pyenv/version)anyenvインストール以前に設定したgoenvやpyenvの環境変数が残っている場合は、bash_profileから削除することも忘れないようご注意ください。
まとめ
- 環境変数、
echo 'export hogehoge"' >> ~/.bash_profile
で追加したまま放置していませんか- 複数の〇〇env系ツールを使っているなら、anyenvの利用がオススメ
- anyenvでbash_profileがスッキリ
- 投稿日:2020-10-28T15:15:40+09:00
Exchange OnlineでOAuth 2.0認証でメールを受信する
前回の記事でメール受信できたけど、結局業務で日の目を見ませんでした。
約1年が経って、新たにメール受信するアプリ開発の話がでて、楽勝だと安請け合いしたら世の中はこんなことになってたのね。。。どうせ1年もしないうちに改造が必要なら、最初から対応しておきたいのが人情。
事前に調べたらExchange Online側を弄る必要があるとのことで、現運用環境を使うわけには行かず、1ヶ月間無料のExchange Onlineを申し込んで、OAuth 2.0に基づく先進認証なるものでメールの受信を試してみました。ちなみに開発目的のアプリは、定期的にアプリ専用のメールアカウントで受信したメールをテキストファイルに出力する常駐アプリで、認証方法としては"使用しない"ことを勧められているROPCフローを想定しているため、以下はその実装例となってます。
アプリケーションの登録
大まかな処理の流れと、この手順はチュートリアルを参考にしました。
Azure Active Directory 管理センターにアプリがメール受信で使用するアカウントでログインし、[Azure Active Directory]-[アプリの登録]-[新規登録]をクリック。
アプリの名前を入力。今回はアプリを使用するのは特定のアカウントのみなので[この組織ディレクトリのみに含まれるアカウント]を選択。リダイレクトURIは使用しないから触らない。そして[登録]をクリック。
[APIのアクセス許可]の[アクセス許可の追加]で、[Microsoft Graph]の[委任されたアクセス許可]から[IMAP.AccessAsUser.All]を追加。今回はログインしているアカウントが管理者でもあるので、そのまま[〜に管理者の同意を与えます]をクリック。
- プロトコルでPOPを使用するなら[POP.AccessAsUser.All]を許可する。
- ここでは別途、送信も試すので[SMTP.Send]を許可している。
- ログインしているアカウントが管理者でなければ(大抵はそうでしょうけど)、管理者に同意を依頼することになる。管理者アカウントでログインし、[アプリの登録]で[すべてのアプリケーション]を選択して該当のアプリを操作してもらうことになる。
基礎となるアプリケーションの作成
簡素化するためにSpring Boot 2.3.4とKotlinを使用しました。
spring initializerの依存関係で、Java Mail Sender (つまりspring-boot-starter-mail)のみ追加して、プロジェクト作成後のpom.xmlへMicrosoft Authentication Library for Javaを追加。pom.xml<dependency> <groupId>com.microsoft.azure</groupId> <artifactId>msal4j</artifactId> <version>1.7.1</version> </dependency>CommandLineRunnerを実装し、runメソッドの中に処理を書いて行きます。
@SpringBootApplication class SampleMailApplication : CommandLineRunner { override fun run(vararg args: String?) { // 実装 } }アクセストークン取得
上記で参照したチュートリアルのこの手順がイケてません。なんだ「App.xamlを開き」って、C#の記事の残りかよ。。。
しかたなくネットで調べて、以下のコードで動くようになりました。import com.microsoft.aad.msal4j.PublicClientApplication import com.microsoft.aad.msal4j.UserNamePasswordParameters // 割愛 override fun run(vararg args: String?) { // 登録したアプリの概要ページにあるアプリケーションIDを指定する val applicationId = "..." // ...部分に概要ページにあるディレクトリIDを指定する val authEndpoint = "https://login.microsoftonline.com/.../oauth2/v2.0/authorize" // 使用するプロトコルにあわせて指定する val scope = setOf("https://outlook.office365.com/IMAP.AccessAsUser.All", "https://outlook.office365.com/SMTP.Send") // アカウントのメールアドレスを指定する val username = "..." // アカウントのパスワードを指定する val password = "..." val pca = PublicClientApplication.builder(applicationId) .authority(authEndpoint) .build() val parameters = UserNamePasswordParameters .builder(scope, username, password.toCharArray()) .build() val result = pca.acquireToken(parameters).join() println("アクセストークン: ${result.accessToken()}")
- APIのアクセス許可で管理者の同意が得られていないと、acquireTokenメソッドで「com.microsoft.aad.msal4j.MsalInteractionRequiredException: AADSTS65001: The user or administrator has not consented to use the application」の例外が発生します。
メール受信
var props = Properties() // 認証にOAuth 2.0を使用 props["mail.imaps.auth.mechanisms"] = "XOAUTH2" var session: Session = Session.getInstance(props) val store: Store = session.getStore("imaps") // パスワードにアクセストークンを使用 store.connect("outlook.office365.com", 993, username, result.accessToken()) val folderInbox: Folder = store.getFolder("INBOX") folderInbox.open(Folder.READ_ONLY) folderInbox.messages.forEach { println("件名: ${it.subject}") }最初のポイントはmail.imaps.auth.mechanismsで、説明によるとデフォルト値はサポートされている認証からXOAUTH2を除く全てで、Exchange Onlineでは基本認証が使われます。そこでXOAUTH2を指定してOAuth 2.0認証が行われるようにします。
次にパスワードの代わりにアクセストークンを指定するだけで、OAuth 2.0認証でメールの受信が可能になりました。
(補足)メール送信
当面継続ということは、そのうち廃止となるであろうSMTPの基本認証についても、OAuth 2.0認証を試してみたいと思います。
しかし、OAuth 2.0認証どころか、基本認証でもjavax.mail.AuthenticationFailedException: 535 5.7.3 Authentication unsuccessfulになるではありませんか!メールボックスで認証済みSMTPはオンになってるのに。。。
そのページで「認証ポリシーによって SMTP の基本認証が無効になっている場合、この記事で説明されている設定を有効にしても、クライアントは SMTP 認証プロトコルを使用できません。」とあるので[セキュリティの既定値群の有効化]を[いいえ]したらメール送信が成功するようになったのですが、今度は[セキュリティの既定値群の有効化]を[はい]に戻しても、もうエラーは起きません。(他に弄った何かが影響している可能性は否定できません。。。)
なんだか良く判らない、、、
一応、成功した送信ロジックも記載します。
val props = Properties() props["mail.smtp.auth"] = "true" props["mail.smtp.auth.mechanisms"] = "XOAUTH2"; props["mail.smtp.starttls.enable"] = "true" val session = Session.getInstance(props) val transport = session.getTransport("smtp") transport.connect("smtp.office365.com", 587, username, result.accessToken()) val sendMessage = MimeMessage(session) sendMessage.addRecipients(Message.RecipientType.TO, username) sendMessage.setFrom(username) sendMessage.subject = "送信" sendMessage.setText("送信テスト") transport.sendMessage(sendMessage, sendMessage.allRecipients)なお、送信自体はできているのですが、送り先から「550 5.7.501 Service unavailable. Spam abuse detected from IP range. 」で最終的な配達まできていません。これは onmicrosoft.com ドメインからメールはスパムとしてフィルターされるからで、試しにOutlookから送信しても同じ結果になるので、ロジックとしては問題ないと思います。
- 投稿日:2020-10-28T14:52:55+09:00
MacにJavaを簡単に入れてみた
概要
MacにJavaをインストールします。
(注: Homebrewは用意されている前提です。)環境
- macOS Catalina 10.15.7
- Homebrew 2.5.7
- Java
openjdk 15.0.1 2020-10-20
手順
Javaのインストール
Homebrewを使ってインストールします。
brewの更新
brew updateバージョン指定なし(最新)の場合
AdoptOpenJDKをインストール
brew cask install adoptopenjdk
バージョン指定したいの場合
リポジトリをフォーミュラとしてHomebrewに追加
brew tap AdoptOpenJDK/openjdkAdoptOpenJDK15をインストール
brew cask install adoptopenjdk15
確認
以下のコマンドでjavaのバージョンが表示されたらOKです。
bash
java --version
参考
- 投稿日:2020-10-28T14:29:56+09:00
Javasilver勉強会 備忘録
- 投稿日:2020-10-28T11:09:33+09:00
Sentryの情報にアプリケーションのバージョン情報を追加する方法
前提
Sentryを使用してエラー情報を検知しているアプリケーションであること。
使用言語、フレームワーク
- Java 8
- Spring Boot(Maven)
概要
Sentryに送信されるエラー情報がいつのバージョンのアプリケーションで発生したエラーのものであるか確認できるようにしたい。
メリット
- 発生したエラーのアプリケーションのバージョンの特定が一目でわかる。
- Sentryの画面で特定のバージョンで発生したエラーの一覧を以下のように検索することができるようになる。
前提条件
- アプリケーションのバージョンが定義されていること。
- リリースの度にバージョンが更新されていること。
以下の定義ファイルの
<version>
内をSentryに送信します。
今回は、バージョン情報をSentryにタグとして送信します。定義ファイル
pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmls="~"> : <groupId>com.example</groupId> <artifactId>myproject</artifactId> <version>100.0.100</version> : </project>修正手順
以下の順番で修正を行います。
1. 依存関係の追加
2. エンドポイントの追加
3. エンドポイントに表示する情報の追加
4. Sentryへの送信処理の追加1. 依存関係の追加
pom.xml<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>2. エンドポイントの追加
application.ymlmanagement: endpoints: web: exposure: include: "info" base-path: "/"base-pathは環境に応じて変更してください。
3. エンドポイントに表示する情報の追加
application.ymlinfo: application: version: @project.version@ここまで設定すると
http://localhost:8080/info
にアクセスすることで以下のようにここまでの手順で設定したバージョン情報を確認することができます。{"application":{"version":"100.0.100"}}4. Sentryへの送信処理の追加
設定ファイル:@SpringBootApplicationアノテーションを設定しているクラスに以下の処理を追加します。
※ すでにSentryへの送信処理を実装している場合は、version
関連の処理を追加してください。
(引数の追加とtagの送信部分です。)
※ 戻り値の型は@Bean public HandlerExceptionResolver sentryExceptionResolver( @Value("${sentry.url:#{null}}") Optional<String> sentryUrl, @Value("${info.application.version:#{null}}") Optional<String> appVersion) { if (sentryUrl.isPresent()) { try { SentryClient sentryClient = Sentry.init(sentryUrl.get()); // ここで、タグとしてバージョン情報を追加しています。 appVersion.ifPresent(s -> sentryClient.addTag("version", s)); } catch (InvalidDsnException e) { LOGGER.warn(e.getMessage(), e); } } return new SentryExceptionResolverImpl(); }※
sentry.url
もinfo.application.version
と同様にapplication.yml
に定義しています。設定後
上記設定を完了後、アプリケーション内でエラーが発生した場合、以下のような
version
のタグが表示されるようになります。
versionの数値部分をクリックすることで、冒頭でメリットとして挙げた検索結果を確認できます。
- 投稿日:2020-10-28T11:06:34+09:00
【Java】Javaの全体像
Javaエディション(環境)
JavaSE(Java Platform, StandardEdition)
- JavaSEはJavaアプリを動作させるための基本的な実行/開発環境を提供
- 文字列/数値、日付の操作、入出力処理など、汎用的に利用できるライブラリ
- 基本的にこれしか使わない
JavaEE(Java Platform, Enterprise Edition)
- JavaEEは、このJavaSEの上で動作するサーバーアプリ開発のためのライブラリ
- 主に企業向けのシステム開発を想定した機能
JavaME(Java Platform, Micro Edition)
- リソースの限られた環境への組み込み用(携帯、家電など)
- 近年のIoTの流れの中で、JavaSEに統合
Javaの歴史
- Java2 (version1.2)で環境に応じてエディションが整理されたのち、JDKがJ2SE(Java2 Platform, Standard Edition)と呼ばれるようになった
- Ver6では、J2SEという呼称もJavaSEに改められる
- Java8、9でラムダ式、モジュールなどのモダンな機能が追加される
- Java11でOracleJDKが有償化(OpenJDKは無償)
- OpenJDKにはLTS提供なし(Long Term Support:長期サポート)
環境
JDK(Java Development Kit)
- JDKは、Javaアプリを開発/実行するために必要なソフトウェア
- Java仮想マシン、コンパイラー、クラスライブラリ、デバッガーを備えた開発キット
- OracleJDK、OpenJDK(オープンソース)、AdoptOpenJDK(IBM、マイクロソフトなど)
統合開発環境(IDE:Integrated Development Environment)
- コーディングを支援する諸機能を取りそろえたソフトウェア
- コードエディター、デバッガー、プロジェクト管理機能など
- ボタン1つでコンパイルから実行までを一気に実行
- Eclipse、NetBeans、IntelliJIDEAなど
Javaコードの全体像
Class
- Javaによるプログラムの基本
- クラスはアプリの中で特定の機能を担うかたまり
- String Class:文字列
- Math Class:数学機能
- FileReader/FileWriter:テキストファイルの読み書き
- これらのクラスを組み合わせアプリを作る
- Javaでは、ファイルとクラスを対応関係にする
- ex:HelloクラスはHello.javaというファイルで定義
メソッド
- class{...}配下に含まれる要素のことをメンバ(フィールド、メソッド、コンストラクターなど)
- メソッドは、クラスの「機能」を表すためのメンバー、アプリで実行すべき処理を表す
- Javaではアプリを起動したときに、まずmainメソッドを探して実行:エントリーポイントという
パッケージ
- クラスを分類するためのいれもの
package com.example.demo.trySpring;
配下にあるHelloController
クラスなど名前解決
- クラスはパッケージ+クラス名で識別:完全修飾名(FQCN:Fully Qualified Class Name)という
- FQCN:
package com.example.demo.trySpring.HelloController
ドキュメンテーションコメント
- あとでドキュメントの生成に利用できるコメントでアスタリスク2個で開始(複数行コメントは*1個)
- クラスやそのメンバーの説明を記述する
/***Reads text from a character input stream,... //クラスの概要 * *@see FileReader //関連項目 *@author MarkReinhold //作者 *@since1.1 //導入されたバージョン */ /***Create a buffering character-input stream from… //コンストラクタの概要 *@param in A Reader //引数 *@param sz Input-buffer size //引数 *@exception //例外 *@retutn //戻り値 */デバッグの基本
- ブレークポイント設置
- 実行中のプログラムを一時停止する機能
- エディターから行番号のひいだりクリック
- ブレークポイントでプログラムの状態確認する
- デバッグ実行
- ブレークポイントからは文単位でコード実行できる:ステップ実行
- ステップイン:1行実行
- ステップオーバー:途中メソッド呼び出し実行し次の行へ
- ステップリターン:現メソッド呼びだし元に戻るまで実行
- 実行再開/終了
- 通常実行に戻すときは再開,終了したいときは終了ボタン
- 投稿日:2020-10-28T10:13:45+09:00
手動で@ConfigurationProperties相当の処理をする
Spring Bootの
@ConfigurationProperties
はプロパティファイルの設定値を自動的に読み込み仕組みだが、それと似たような処理を手動で記述したい。たとえば、いま下記のような
kafka.properties
があり、これをorg.springframework.boot.autoconfigure.kafka.KafkaProperties
に読み込みたい、とする。kafka.propertiesspring.kafka.bootstrap-servers=localhost:32770 spring.kafka.consumer.group-id=java-consumer-groupKafkaProperties@ConfigurationProperties(prefix = "spring.kafka") public class KafkaProperties { ...これをするには、
Map
を設定値のソースとするMapConfigurationPropertySource
を使用する(Properties
はHashtable
を継承している)。import java.io.IOException; import java.nio.file.Files; import java.nio.file.Paths; import java.util.Properties; import org.springframework.boot.autoconfigure.kafka.KafkaProperties; import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; public class HogeMain { public static void main(String[] args) throws IOException { Properties properties = new Properties(); properties.load(Files.newInputStream(Paths.get("kafka.properties"))); ConfigurationPropertySource source= new MapConfigurationPropertySource(properties); Binder binder = new Binder(source); KafkaProperties kafkaProperties = binder.bind("spring.kafka", KafkaProperties.class).get(); } }yamlの場合、
YamlPropertiesFactoryBean
からProperties
が得られるので、これを経由させる。YamlPropertiesFactoryBean y = new YamlPropertiesFactoryBean(); y.setResources(new ClassPathResource("hoge.yml")); Properties object = y.getObject();参考
- 投稿日:2020-10-28T00:23:17+09:00
【Java】DBマイグレーション(Flyway)
DBマイグレーション(スキーマ管理)ライブラリであるFlywayを利用する。
以下のような目的で利用する。
・DBの定義やその変更に関する情報を一箇所で管理する。
・その情報に基づき最新のDBの状態を自動で再現する。MavenプロジェクトでFlywayを利用するための設定
pom.xml<dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-core</artifactId> </dependency>SpringBootの設定ファイルを修正(application.yml)
SpringBootはデフォルトで、flywayが起動時に自動実行される。
flyway: baseline-on-migrate: true baselineVersionAsString: 0.0.0 baseline-description: Initialテーブルの定義ファイルを追加
以下フォルダにsqlファイルを追加する。
src/main/resources/db/migration/・マイグレーションファイル
管理対象のスキーマに適用する変更内容を記述したSQLファイル
以下の規則で作成する。
V{version}__{description}.sql例)
V0_0_1__createTable.sql・リピータブルマイグレーションファイル
ビュー、プロシージャ、ファンクションなどのオブジェクトのマイグレーションを行いたい場合に使用する。
以下の規則で作成する。
R__{description}.sql例)
R__createView.sql公式サイト
- 投稿日:2020-10-28T00:19:57+09:00
【Java】パスワード生成(Pasay)
パスワードポリシー実施ライブラリであるPasayを利用する。
設定可能なルールセットを使用してパスワードを生成することができる。MavenプロジェクトでPasayを利用するための設定
pom.xml<dependency> <groupId>org.passay</groupId> <artifactId>passay</artifactId> <version>1.6.0</version> </dependency>大文字小文字英数字混合の8桁のパスワードを生成するコード
Password.javapublic class Password { public static String generate() { List<CharacterRule> rules = Arrays.asList( new CharacterRule(EnglishCharacterData.UpperCase, 1), new CharacterRule(EnglishCharacterData.LowerCase, 1), new CharacterRule(EnglishCharacterData.Digit, 1) ); PasswordGenerator generator = new PasswordGenerator(); String password = generator.generatePassword(8, rules); return password; } }テストコード
PasswordTest.javapublic class PasswordTest { @Test public void testGenerate() { String password = Password.generate(); assertTrue(password.length() == 8); assertTrue(password.matches(".*[a-z].*")); assertTrue(password.matches(".*[0-9].*")); assertTrue(password.matches(".*[A-Z].*")); } }