- 投稿日:2019-07-19T21:41:12+09:00
ApplicationInsightsのjavaagentを仕込んで実行されたSQLをトレース
ドキュメントにある内容をやっただけです。
https://docs.microsoft.com/ja-jp/azure/azure-monitor/app/java-agent
こんな感じになります。
黒塗りばかりのこの画像は所要時間が5分かかった謎のリクエストを調べたときのものです。
4回SQLクエリが実行されている様子と、2個ほどクソクエリがある様子が分かります。クエリのどれかをクリックすると、右のコマンドにSQLが出てきます。SQLの内容はPreparedStatementを使っているためかPreparedなSQLになっており、パラメータは「?」になっているまでは見れないようです。(逆に埋められても機密情報が見えかねないのでこれはこれで良いと思います)
呼び出されたコードの位置もわかりません。でも実装者なら十分役立つことでしょう。もちろん、DBクエリの情報をApplicationInsightsに送るため、転送量がかかるのでその点は注意してください。
前提
- Javaアプリ
- ApplicationInsightsは導入済み
仕組み
JavaAgentを使って、クラスローダでクラスを読み込む際にクラスファイルに手を加え、テレメトリを書き出す仕組みになっているようです。
JavaAgentを組み込むだけでよいので、動いているコードには手を加えずに利用できます。
導入
- javaagentファイルをダウンロード
- 同じ場所にAI-Agent.xmlを作成
- JVM引数に
-javaagent:...を追加- アプリケーション再起動
導入手順はAzure WebApp上を想定していますが、JavaのWebアプリケーションならAzure WebAppに限らずちょっと読み替えるだけで導入できます。
ApplicationInsightsのjavaagentを導入する
https://github.com/microsoft/ApplicationInsights-Java/releases/ で公開されているので、導入済みのApplicationInsightsのバージョンにあったagentを入れます。
Azure WebAppなら、kudu上でこんな感じに操作。
D:\home\data辺りなら邪魔にならないかと思いますが、適宜変更してください。cd D:\home\data curl -LO https://github.com/microsoft/ApplicationInsights-Java/releases/download/2.1.1/applicationinsights-agent-2.1.1.jar
AI-Agent.xmlファイルを作成
AI-Agent.xmlというファイルを先ほどダウンロードしたjarファイルがある場所と同じ場所に作成してください。内容は以下をコピペで良いです。
AI-Agent.xml<?xml version="1.0" encoding="utf-8"?> <ApplicationInsightsAgent> <Instrumentation> <BuiltIn enabled="true"> <!-- ここのenabled属性も見ているため忘れずに。 --> <JDBC enabled="true" /> <!-- JDBCでクエリを投げた時にクエリの実行時間とPreparedなSQLのテレメトリを送信 --> </BuiltIn> </Instrumentation> </ApplicationInsightsAgent>今回はJDBCのみですが、設定でいくつかのHTTP呼び出しなどもトレースできるようです。
JVM引数に
-javaagent:...を追加JVM引数に以下を追加します。ファイルパスは適宜置き換えます。
-javaagent:D:/home/data/applicationinsights-agent-2.1.1.jarTomcatで動かすアプリなら環境変数
JAVA_OPTSやCATALINA_OPTSでよいです。Azure WebAppならアプリケーション設定かkudu上からweb.configで。ただし、アプリケーション設定の方が優先されるので、その点を考慮してください。
アプリケーション再起動
javaagentを読み込ませるために再起動が必要です。
以上。
その他
冗長なテレメトリを抑制したい
現状、時間がかかった場合だけ送る、といったことは出来ないようです。
例えば、十分に早いことが分かっているクエリや、コネクションプーリングを使っているとプールから取り出したときに腐ってないかチェックするときの
SELECT 1でもテレメトリが送られてしまいます。
もし防ぎたい場合は、フィルタを書く必要があります。以下のIssueにTelemetryProcessorの実装例があります。参考: https://github.com/microsoft/ApplicationInsights-Java/issues/837#issuecomment-471610584
- 投稿日:2019-07-19T18:40:15+09:00
JavaでLambda関数を書いてSAMでデプロイしてみた
はじめに
これまで、AWS Lambdaを主にPython、時々、Node.jsで作ってきましたが、気分転換にJavaでの開発方法を調べたのでまとめます。
今回は、Javaで書いたLambda関数のソースコードをGradleでビルドしてAWS SAMでデプロイしてみました。
Eclipseでの開発用に AWS Toolkit for Eclipse も提供されていますが、普段はVimで開発してCLIでデプロイしているので、そちらに合わせます。検証環境
- OS: macOS High Sierra 10.13.6
- aws-cli: 1.15.38
- aws-sam: 0.11.0
- Java 1.8.0_221
- JDK: OpenJDK 1.8.0_212 (Amazon Corretto 8.212)
- Gradle 5.5.1
JavaでLambdaを開発する上でのポイント
ランタイムはJava 8のみ
下の公式ドキュメントにあるように、提供されているJavaのランタイムは
Java 8(JDK 8)のみです。
それ以外のバージョンを使用する場合は、サポートされるまで待つかカスタムランタイムを使う必要があります。Java による Lambda 関数のビルド - AWS Lambda
zipファイルかjarファイルにしてでデプロイ
Javaで書かれたアプリケーションを、zipファイルまたはスタンドアロンjarにパッケージしてデプロイします。
公式ドキュメントではMavenを使ってスタンドアロンjarに、Gradleを使ってzipファイルにしてデプロイする方法が紹介されてます。
今回は、Gradleでzipファイルにパッケージングしてデプロイする方法で試しました。Java の AWS Lambda デプロイパッケージ - AWS Lambda
ハンドラー関数のリクエストの受け方・返し方が3種類
ハンドラー関数のリクエストの受け取り方、そして、レスポンスの返し方に、以下のように複数の方式があります。
- Java のシンプルな型
- POJO (Plain Old Java Object) 型
- ストリーム型
それぞれでハンドラー関数の書き方が変わります。つまり、ハンドラー関数の書き方に3種類あるということです。
「このイベントトリガーを使うならこのタイプ」というような決め方というよりも、どういったデータが送られてくるかという視点で使い分けるようです。
この状況にはこれという明確なものはないようなので、実際にJavaでLambda関数を開発する際はどの方式にするか要検討です。今回は、「POJO型」を採用しました。
ハンドラーの入出力タイプ (Java) - AWS Lambda
今回作ったもの
Lambda関数のトリガーとして
API Gatewayを使った、簡単なWebアプリケーションを作成しました。
POSTリクエストを送ると、"Hello"と返してくるだけのシンプルなAPIです。サンプルコード
作成したコードはここに置いてます。
https://github.com/mmclsntr/awslambda-javagradleプロジェクト構成
. ├── build/ │ ├── distributions/ │ │ └── awslambda-javagradle.zip # ビルドで生成されるデプロイパッケージ │ └── ... ├── build.gradle # Gradleビルド設定ファイル ├── src/main/java/awslambda/javagradle │ ├── Greeting.java # アプリの中核となる部分 │ ├── Handler.java # Lambdaのハンドラ関数を格納 │ └── Response.java # Lambdaのレスポンスを整形 └── template.yml # CloudFormationテンプレートファイル └── その他Gradle用ファイルコーディング
ハンドラクラス
Handler.javapackage awslambda.javagradle; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; public class Handler implements RequestHandler<Map<String, Object>, Response> { private static final Logger LOG = Logger.getLogger(Handler.class.getName()); @Override public Response handleRequest(Map<String, Object> input, Context context) { LOG.info("received: " + input); LOG.setLevel(Level.INFO); Greeting greetingBody = new Greeting("Hello"); return Response.builder() .setStatusCode(200) .setObjectBody(greetingBody) .build(); } }レスポンスクラス
Response.javapackage awslambda.javagradle; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; import java.util.Map; import org.apache.log4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class Response { private final int statusCode; private final String body; private final Map<String, String> headers; private final boolean isBase64Encoded; public Response(int statusCode, String body, Map<String, String> headers, boolean isBase64Encoded) { this.statusCode = statusCode; this.body = body; this.headers = headers; this.isBase64Encoded = isBase64Encoded; } ... }あいさつクラス
サンプルとしてHelloと返してくれる超シンプルなアプリケーションを作ります。
Greeting.javapackage awslambda.javagradle; public class Greeting { private String greetings; public Greeting(String greetings) { this.greetings = greetings; } public String getGreetings() { return greetings; } public void setGreetings(String greetings) { this.greetings = greetings; } }CloudFormationテンプレート
template.ymlAWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > AWS Lambda Java with Gradle Globals: Function: Timeout: 20 Resources: PostGreetingFunction: Type: AWS::Serverless::Function Properties: CodeUri: build/distributions/awslambda-javagradle.zip Handler: awslambda.javagradle.Handler::handleRequest Runtime: java8 Events: GetOrder: Type: Api Properties: Path: / Method: post Outputs: ApiEndpoint: Description: "API Gateway endpoint URL for Prod stage" Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" PostGreetingFunction: Description: "PostGreeting Lambda Function ARN" Value: !GetAtt PostGreetingFunction.Arnデプロイ
ビルド設定ファイル作成
build.gradleapply plugin: 'java' repositories { mavenCentral() } sourceCompatibility = 1.8 targetCompatibility = 1.8 dependencies { compile ( 'com.amazonaws:aws-lambda-java-core:1.1.0', 'com.amazonaws:aws-lambda-java-log4j:1.0.0', 'com.amazonaws:aws-lambda-java-events:1.1.0', 'com.fasterxml.jackson.core:jackson-core:2.8.5', 'com.fasterxml.jackson.core:jackson-databind:2.8.5', 'com.fasterxml.jackson.core:jackson-annotations:2.8.5' ) } // Task for building the zip file for upload task buildZip(type: Zip) { from compileJava from processResources into('lib') { from configurations.runtime } } build.dependsOn buildZipビルド
Gradle コマンドでビルドします。
gradle buildAWS SAMでデプロイ
デプロイ先S3バケット作成
aws s3 mb s3://<デプロイ先S3バケット> --aws-profile=<AWSプロファイル>パッケージ
sam packageで、上で作成したS3バケットへ実行ファイルをアップロード & デプロイ用テンプレートファイルを生成します。sam package \ --s3-bucket <デプロイ先S3バケット名> \ --s3-prefix <デプロイ先S3フォルダ名(プレフィクス) ※任意> \ --output-template-file output.yml \ --profile <AWSプロファイル>アウトプットとして、
output.ymlファイルが作られます。デプロイ
sam deployで、LambdaとAPI Gatewayをデプロイします。sam deploy \ --template-file output.yml \ --stack-name awslambda-javagradle-greeting \ --capabilities CAPABILITY_IAM \ --profile <AWSプロファイル>確認
AWS上にLambda関数とAPI Gatewayが作られたので、エンドポイントにPOSTリクエストを投げて見ます。
リクエストcurl -X POST https://xxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/レスポンス{"greetings":"Hello"}所感
私自身、普段はJavaを使わないのでちょっと時間がかかりましたが、簡単に作れたなという印象です。Pythonなどのスクリプト言語で作るよりも当然ながらコード量は増えますが、厳格なサーバーレス開発ができることは魅力的です。
ハンドラー関数の書き方に複数種類あるといったJavaで書く上での特有の仕様が、今回だけでは把握しきれませんでした。実際に使う際は、その辺りも考慮しながら詳細設計したいと思います。
また、噂通り、初期起動が遅いです (レスポンスされるまで5秒くらい)。
参照: https://cero-t.hatenadiary.jp/entry/20160106/1452090214まとめ
Javaを利用したLambda関数の開発の特徴やコーディング感覚をつかむことができました。
もし、サーブレットから移行するならそこそこ大規模な改修が必要と思いますが、代わりとして使えなくもない印象です。ランタイムがこのままJava8のみなのか、そこのサポートが少し心配です。。
API Gatewayをトリガーとしましたが、他のサービスも試して見たいと思います。参考
https://qiita.com/kamata1729/items/8d88ea10dd3bb61fa6cc
https://qiita.com/riversun/items/7fcc06617b469aed8f27
- 投稿日:2019-07-19T18:04:21+09:00
テスト対象のクラスの一部のメソッドをモック化する方法
最近noteに書くか、Qiitaに書くか迷い中です。
いつも忘れる、いつも一部をモック化したいときの書き方。
- mock化したいメソッド
@Slf4j @Service public class HighHolidayService { // 一月単位でまとめた休日リスト Map<String, List<LocalDate>> highHolidayCache = new HashMap<>(); // テスト対象のメソッド public List<LocalDate> getHolidayList(LocalDate targetDate) { String key = String.format("%d%02d", targetDate.getYear(), targetDate.getMonthValue()); if (highHolidayCache.containsKey(key)) { return highHolidayCache.get(key); } List<LocalDate> holidayList = selectRecords(targetDate); highHolidayCache.put(key, holidayList); return holidayList; } // このメソッドをモック化したい!!!! List<LocalDate> selectRecords(LocalDate targetDate) { return null; } }
- テストの書き方
package jp.co.hogehoge.service; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.doReturn; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.MockitoAnnotations; import org.springframework.boot.test.mock.mockito.SpyBean; public class HighHolidayServiceTest { @SpyBean // springboot の@spyの書き方 HighHolidayService highHolidayService; @Before public void init() { MockitoAnnotations.initMocks(this); } @Test public void 対象の休日リストを取得する() { List<LocalDate> returnHolidayList = new ArrayList<>(); returnHolidayList.add(LocalDate.of(2019, 07, 28)); returnHolidayList.add(LocalDate.of(2019, 07, 21)); returnHolidayList.add(LocalDate.of(2019, 07, 15)); returnHolidayList.add(LocalDate.of(2019, 07, 14)); returnHolidayList.add(LocalDate.of(2019, 07, 7)); // doReturn(返却するオブジェクト).when(spyしたオブジェクト).method() // この書き方忘れる!! doReturn(returnHolidayList).when(highHolidayService).selectRecords(anyObject()); LocalDate date = LocalDate.of(2019, 7, 15); List<LocalDate> holidayList = highHolidayService.getHolidayList(date); holidayList.forEach(System.out::println); } }クラスごとMock化するのはよくやるからいいんだけど、テスト対象の一部をモック化するのはたまにしか出てこないから忘れるんですよね。。
というか、クラス分けたほうがいいって話が多数で(そうなんだけど、そこまでするまでもないロジックだったりするし)
- 投稿日:2019-07-19T10:48:11+09:00
【Java】ActiveDirectoryの参照/更新
はじめに
ADのユーザーをメンテナンスする機能を提供するため、ユーザー一覧を取得したり、更新できるようにしたい。
JavaではJNDIを使ってADへアクセスできる。ユーザー一覧の取得
ユーザー一覧の取得private static void getUserList() throws NamingException { Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://xxxx"); // ADサーバーのアドレス env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "user@domain"); // ADサーバーにログオンするためのユーザーID@ドメイン env.put(Context.SECURITY_CREDENTIALS, "password"); // パスワード DirContext context = null; try { context = new InitialDirContext(env); // 検索条件の指定 String name = "OU=aaa, DC=bbb, DC=ccc"; // どの範囲を検索対象とするか String filter = "(objectCategory=user)"; SearchControls control = new SearchControls(); control.setSearchScope(SearchControls.SUBTREE_SCOPE); // 検索実行 NamingEnumeration<SearchResult> results = context.search(name, filter, control); // 結果取得 while (results.hasMoreElements()) { SearchResult result = results.next(); NamingEnumeration<? extends Attribute> attributes = result.getAttributes().getAll(); AdUserBean user = new AdUserBean(); while (attributes.hasMoreElements()) { Attribute attribute = attributes.next(); Object value = attribute.get(); if ("name".equals(attribute.getID())) { if (value instanceof String) { user.setName((String) value); } } else if ("displayName".equals(attribute.getID())) { if (value instanceof String) { user.setDisplayName((String) value); } } else if ("cn".equals(attribute.getID())) { if (value instanceof String) { user.setCn((String) value); } } else if ("userPrincipalName".equals(attribute.getID())) { if (value instanceof String) { user.setUserPrincipalName((String) value); } } } System.out.println(user); } } finally { try { if (context != null) { context.close(); } } catch (NamingException e) { } } }主な属性:
名前 説明 userPrincipalName ユーザーID@ドメイン displayName 表示名 メールアドレスは単体項目だが、電話番号は複数指定可能な項目。
参考にしたサイト:
https://www.earthlink.co.jp/engineerblog/intra-mart-engineerblog/3336/
(ただしこちらのサンプルは、AD認証するためにユーザーIDを指定した検索)Active Directory のユーザーの属性についての説明
https://docs.microsoft.com/en-us/windows/win32/ad/naming-properties
Active Directory の属性一覧
https://docs.microsoft.com/en-us/windows/win32/adschema/attributes-allLDAP の検索フィルター構文
http://software.fujitsu.com/jp/manual/manualfiles/M050000/B1WN4911/01/idmgr07/idmgr447.htm
https://www.ibm.com/support/knowledgecenter/ja/SSYJ99_8.5.0/admin-system/rbug_ldapfltrxprns.htmlユーザー属性の変更
ユーザー属性の変更private static void update() throws NamingException { Hashtable<String, String> env = new Hashtable<String, String>(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://server"); // ADサーバーのアドレス env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.SECURITY_PRINCIPAL, "user@domain"); // ADサーバーにログオンするためのユーザーID@ドメイン env.put(Context.SECURITY_CREDENTIALS, "password"); // パスワード DirContext context = null; try { context = new InitialDirContext(env); Attributes attrs = new BasicAttributes(); attrs.put(new BasicAttribute("pager", "123456")); attrs.put(new BasicAttribute("telephoneNumber", "1234567")); String name = "CN=username, OU=xxx, DC=yyy, DC=zzz"; // どの範囲を対象とするか context.modifyAttributes(name, DirContext.REPLACE_ATTRIBUTE, attrs); } finally { try { if (context != null) { context.close(); } } catch (NamingException e) { } } }更新条件をちゃんとユーザー1人になるようにしないと、複数ユーザーが一気に更新されてしまいそう。
- 投稿日:2019-07-19T07:11:01+09:00
【Programming News】Qiitaまとめ記事 July 18, 2019 Vol.4
筆者が昨日2019/7/18(木)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら。
Java
Python
- Beginner
- Tips
- K近傍法
Node.js
Nuxt.js
Swift
Laravel
MySQL
- Beginner
Git
- Beginner
Visual Studio
AWS
- Beginner
- Tips
- Cognito
- S3
- EC2
- Elasticsearch
Docker
Google Apps Script
- Tips
Develop
Raspberry
- Tips
- Apps
UML
- PlantUML
Go言語
- Beginner
- Tips
R言語
awk
LaTex
Redmine
- Tips
更新情報
