- 作者: 高野研一
- 出版社/メーカー: かんき出版
- 発売日: 2016/08/26
- メディア: Kindle版
- この商品を含むブログを見る
印象に残ったところを自分なりにまとめるとこんな感じでした。
印象に残ったところを自分なりにまとめるとこんな感じでした。
Spring Bootでリクエストログを出力するための方法については、以下の記事で紹介されています。
以下の内容はSpring Boot 1.4.3で確認してます。また、Jetty以外のアプリケーションサーバの場合は当然ながら全く違った設定方法になります。
ELB(AWS)やHerokuなど、前段でSSLが終端されてから(SSL Termination)後続のSpring Bootアプリがリクエストを処理するようなケースでは、本来の送信元IPをアプリが知るためにX-Forwarded-For他、X-Forwarded系のヘッダを解釈させるようにする必要があります。
JettyではForwardedRequestCustomizer
というクラスが提供されているので、それを追加してあげればよいです。
// 途中略 for (Connector connector : server.getConnectors()) { if (connector instanceof ServerConnector) { HttpConnectionFactory connectionFactory = connector.getConnectionFactory(HttpConnectionFactory.class); connectionFactory.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer()); } } }
元々用意されている NCSARequestLog
を継承し、 logExtended
メソッドを上書きした拡張ログクラスを作ることで、元の出力内容に加えて独自の項目を出力することができます。
例えばAuthorizationヘッダの内容を最後の項目に出力させたい場合は、以下のような感じです。
public class NCSARequestLogExt extends NCSARequestLog { @Override protected void logExtended(StringBuilder b, Request request, Response response) throws IOException { super.logExtended(b, request, response); String authToken = request.getHeader("Authorization"); if (authToken == null) { b.append(" \"-\" "); } else { b.append(" \""); b.append(authToken); b.append("\" "); } } }
気が付いたら2017年も1週間が過ぎてしまいました。まさか7月のイベントが2016年の最後のエントリになるとは思いませんでした。
年々顕著にエントリが減ってきているので、今年は週1くらいのペースで書く習慣をつけていきないなと思います。
ネタが少ない時なんかは、読んだ本の簡単な感想で、エントリ数だけは積んでいきたいと思います(読書ログでいいじゃん、というツッコミはあるんですが…)
Cloud First Architecture 設計ガイド(日経BP Next ICT選書)
すぐに読めちゃうボリュームの割には広く浅く書かれていて、復習になりました。
AWSにおける具体的なアーキテクチャ例、みたいなものは書かれておらず、アーキテクチャ設計の考え方を知るための本、という印象です。 Salesforceの名も所々出てきます。
第5章「アーキテクチャー設計ガイド」はまた読み返したいと思います。
Doma勉強会に行ってきました。
最近はJPAを使っていてDomaを使えていないのですが、以前そこそこ長い間(3年以上)Doma(1, 2両方)を使ったシステム開発をしてたので、そのあたりからの進化とか色々知ることができました。
スライドは公開されていますので、個人的に知らなかっこと、気付きなどについて超簡単なメモ。
IN (NULL)
となり、SQLエラーにならない
Stream#map(...).collect(toList())
は Stream#collect(mapping(..., toList()))
とも書ける。色々と勉強になりました。ありがとうございました。
今更感はありますが…。
Spring Boot(というよりSpring)でインジェクションされるコンポーネントのデフォルトのスコープはSingletonになります。
そのため、Singletonスコープのコンポーネントに対して、Singletonより短いスコープであるPrototypeコンポーネントをインジェクションしようと思った場合、@Scope("prototype")
をつけてコンポーネントを定義してインジェクションさせるだけではうまくいきません。
prototypeスコープを使うには、いくつか方法があります。今回は4つの方法を試してみました。
なお、以下の本エントリでは、動作の説明のため、カウンタコンポーネントを利用します。
カウンタの定義。proxyMode
を指定しているところがポイント。
@Component @Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS) public class Counter1 { private int count; public int increment() { return count++; } }
ロジックそのものは説明するまでもないですが、同じインスタンスに対して increment
メソッドを呼ぶと、そのたびに、戻り値が0, 1, 2, …と1ずつ増えていくものです。
利用例(spring-boot-starter-webを使ったWebアプリを想定しています)
@RestController public class Counter1Controller { @Autowired Counter1 counter1; @RequestMapping("counter1") public int counter1() { return counter1.increment(); } }
8080ポートでアプリを起動して、 http://localhost:8080/counter1 にアクセスすると、何度ブラウザをリロードしても、常に0が表示されます。 このことから、毎回新しいCounter1のインスタンスが参照されていることが分かります。
カウンタ。
@Component @Scope(value = "prototype") public class Counter2 { private int count; public int increment() { return count++; } }
利用例。Counter2を直接@Autowired
でインジェクションせずに、 ApplicationContext#getBean
で取得します。
@RestController public class Counter2Controller { @Autowired ApplicationContext context; @RequestMapping("counter2") public int counter2() { Counter2 counter = context.getBean(Counter2.class); return counter.increment(); } }
アプリの動きは方法1と同じです。
カウンタ(方法2とまったく同じ)
@Component @Scope(value = "prototype") public class Counter3 { private int count; public int increment() { return count++; } }
この方法の場合、新しくLookup用のサービスクラスを作ります。
@Component public class Counter3Lookup { @Lookup public Counter3 lookup() { return null; } }
Lookupメソッド経由で取得します。
@RestController public class Counter3Controller { @Autowired Counter3Lookup lookup; @RequestMapping("counter3") public int counter3() { Counter3 counter = lookup.lookup(); return counter.increment(); } }
ちょっと蛇足的ですが、一部Java EEのAPIが利用できるので、それを利用します。 カウンタは方法2、方法3と同様。
@Component @Scope(value = "prototype") public class Counter4 { private int count; public int increment() { return count++; } }
使う側は、 javax.inject.Provider#get
メソッド経由で取得します。
@RestController public class Counter4Controller { @Autowired Provider<Counter4> counterProvider; @RequestMapping("counter4") public int counter4() { Counter4 counter = counterProvider.get(); return counter.increment(); } }
今回の記事のメイン。それぞれの方法について、実行時間的にどれくらいの差が現れるのかを簡単に計測してみました。
計測の条件をなるべく揃えるために、以下のように、方法1〜方法4までのカウンタを取得するためのコンポーネントを1枚かませてみます。
@Component public class CounterProvider { @Autowired Counter1 counter1; @Autowired ApplicationContext context; @Autowired Counter3Lookup lookup; @Autowired Provider<Counter4> counterProvider; public Counter1 getCounter1() { return counter1; } public Counter2 getCounter2() { return context.getBean(Counter2.class); } public Counter3 getCounter3() { return lookup.counter3(); } public Counter4 getCounter4() { return counterProvider.get(); } }
その上で、コンポーネントの取得&incrementメソッドをの呼び出しのセットを1,000,000呼び出すのに、どれくらいの時間がかかるのかを測ってみました。 以下のメソッドは方法1の場合ですが、他の方法についても同様に実施します。
public int benchmarkCounter1() { long start = System.currentTimeMillis(); int sum = 0; for (int i = 0; i < LIMIT; i++) { Counter1 c = counterProvider.getCounter1(); sum += c.increment(); } log.info("sum = {}, total = {} ms", sum, (System.currentTimeMillis() - start)); // sum はどれも 0 return sum; }
方法 | 実行時間 |
---|---|
1: @Scope に proxyMode = ScopedProxyMode.TARGET_CLASS をつける |
4,430ms |
2: ApplicationContext#getBean |
4,105ms |
3: @Lookup |
4,284ms |
4: Provider#get |
7,908ms |
方法4だけが突出して遅いですね。
何度か試しても、この傾向は同じでした。
それ以外の方法はどれも大差なく proxyMode
を使っても、顕著に遅くなる、ということはないようです。
今回実行時間を比較してみることにした動機は、 方法1の場合に極端に処理が遅くなることがない、という裏付けをとることだったので、確認できてよかったです。
Spring Bootのアプリを作る上で、極力プロトタイプスコープのコンポーネントが必要ないようにはしているものの、どうしても必要な時は、1を使う、という方針でいこうかなーと思いました。
ES6で書いていたJavaScriptのコードがIE(※)で動かなかったので、その時に対応した内容のメモです。
※ IEのバージョンは9以上を想定してます。IE8、あるいはそれ以前となるとさらに面倒さが増すので。
各ブラウザの対応状況を調べるには、以下のサイトが便利でした。
ECMAScript 6 compatibility table
「Show obsolete platforms」にチェックを入れると、サポート切れの古いブラウザについても状況を確認することができます。
なお、IE9以下は、ECMAScriptとして「6」ではなく「5」を選んでおかないと表示されません。ES6はIE10でも絶望的なのに、それより古いバージョンが対応してるわけないですもんね。。
Chromeが良くも悪くも手厚くサポートしているので、普段の動作確認をChromeでのみ行うのはリスクだなと実感。
ES6のコードはBabelでトランスパイルして使っていました。 詳しく言うと、Babelはwebpackのloaderを使っており、各バージョンは以下の通りです。
とはいえ、今回の対応箇所は、webpack関係なく通用するものだと思います。
そもそもES6の新しい仕様をフルに使って書いていたわけではないため、実はそこまで手間はかかりませんでした。
原因)IEではネイティブでPromiseがサポートされていません。
対策)代替ライブラリとしてbluebirdを利用しました。
今回、webpackでビルドしていたため、webpack.ProvidePlugin
を使うことで、既存のコードに手を加えることなく Promise
をすべて bluebird
を使うようにひねることができます。
webpack.config.js
に以下を追加してあげればOKです。
plugins: [ new webpack.ProvidePlugin({ "Promise": "bluebird" }) ]
原因)Object.assign
はオブジェクト同士をマージするメソッドですが、これもIEでは使えません。
対策)代替ライブラリとしてobject-assignを利用しました。
Object.assign
を使ったコードをすべて置き換えて対応しました。
Object.assign(foo, bar);
→ objectAssign(foo, bar);
原因)IE9以前では、開発者ツールを起動していない場合、consoleがundefinedになります。
対策)代替ライブラリとしてConsole.log wrapperを使いました。
こちらも前述の Promise
のように、単なる log
メソッドとして呼び出せるようにしました。
webpack.config.js
に
plugins: [ new webpack.ProvidePlugin({ "Promise": "bluebird", "log": "consolelog" }) ]
を追加し、 console.log
を使っている箇所を log
に置き換えて対応しました。
ちなみに、consoleが undefined
だったら自作のオレオレconsoleに差し替える、みたいな方法でももちろん可能なのですが、雑に作ってしまうと、開発者コンソールで見た時のログ出力行が、console.log
を呼び出した行ではなく、全てオレオレconsoleの行になってしまう、みたいな残念なことになってしまうので気をつける必要があるかと思います。
先日Qiitaにこんな記事を書きました。その続きです。
さて、変換仕様をwscを使ってアップロードするメソッドは2つあります。
void BulkConnection#createTransformationSpecFromStream(JobInfo, InputStream)
TransformationSpecRequest BulkConnection#createTransformationSpec(JobInfo)
Qiitaでは前者の方だけ紹介しました。
使い分けですが、それぞれのメソッドのシグネチャを見ると想像がつく通り、CSVファイル(やその他何かしらのストリーム)がそのまま渡せる場合は前者を、プログラム的に変換仕様を渡したい場合は後者を使う、みたいな感じだと思います*1。
後者の場合の利用例は以下のような感じ。
TransformationSpecRequest specRequest = connection.createTransformationSpec(job); specRequest.addSpecRow("Name", "Full Name", "", ""); specRequest.addSpecRow("Title", "Job Title", "", ""); specRequest.addSpecRow("LeadSource", "Lead Source", "Import", ""); specRequest.addSpecRow("Description", "", "Imported from XYZ.csv", ""); specRequest.addSpecRow("Birthdate", "Date of Birth", "", "dd MMM yy"); specRequest.completeRequest();
変換仕様は、データ項目の対応付け | Bulk API 開発者ガイド | Salesforce Developersでサンプルとして載っているspec.csvと同等のものです。
しかし、これがどうしてもエラーで動かず、最終的にwscのバグという結論に落ち着きました。
そこでコードを修正し、正常に期待した動きになるところまで確認したので、プルリクを送ってみました。
正直なところ、業務で使っているわけでもないので、別にマージされなくてもいいやと思ってはいるのですが、プルリクを送るまでの手順だけ、メモとして残しておこうと思います。
以下の流れです。
(追記:無事、マージされました。)
このうち、 3. と 4. のところだけメモ。
READMEの通りです(要Maven)。きちんと既存のテストがパスすることは確認します。
mvn clean package -Dgpg.skip
ビルドが成功すると、 target フォルダの下に force-wsc-35.2.6.jar のようなファイルが作られるので、これを使います。
私はビルドにGradleを使っているので、元々、
dependencies { compile "com.force.api:force-wsc:35.2.7" compile "com.force.api:force-partner-api:35.0.1" }
のようにしていたところを、
dependencies { compile files('/path/to/wsc/target/force-wsc-35.2.6.jar') compile ("com.force.api:force-partner-api:35.0.1") { transitive = false } compile 'org.antlr:ST4:4.0.7' compile 'org.codehaus.jackson:jackson-core-asl:1.9.13' compile 'org.codehaus.jackson:jackson-mapper-asl:1.9.13' }
のように修正します。force-partner-apiがforce-wscに依存関係を持っているので、推移的な依存関係を無効にしつつ(transitive = true)、ST4やらjacksonやらの必要なものだけ別途追加してます。
ちなみに修正内容ですが、10行程度の修正の中で3〜4つのバグを直した(つもり)です。
Transport#connect
でZIPフラグがtrueで処理されている*1: 正直、プログラム中で変換仕様を組み立てたい場合でもByteArrayInputStreamあたりを渡せば事足りますが…。