忍者ブログ

MockMvcのgetRequestURLとgetServletPathに任意の値を指定する方法

2017年08月16日 20時49分32秒
SpringのMockMvcを使用してJUnitを実行しています。
Servletのロジック内で
HttpServletRequestのgetRequestURLgetServletPathを使用しているのですが、JUnitで単体テストすると、getRequestURLのホスト名がlocalhost、getServletPathが空で返ってきて困りました。
いや、別にlocalhostでも空でもいいっちゃぁいいんですが、どうせなら本番環境に近い状態でテストしたい。

なので、getRequestURLとgetServletPathに任意の値(本番環境と同じホスト名、パス)を返すように設定する方法を調査。
MockMvcを色々触ってみたら出来たのでメモ。

RequestPostProcessorをimplementsした新規クラスを作成

import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.request.RequestPostProcessor;

public class TestRequestPostProcessor implements RequestPostProcessor {
  private String serverName = null;
  private String servletPath = null;

  public TestRequestPostProcessor(String _serverName, String _servletPath)
  {

      this.serverName = _serverName;
      this.servletPath = _servletPath;
  }

  @Override
  public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request)
  {

      request.setServerName(serverName);
      request.setServletPath(servletPath);
      // 他に設定したい項目があればここで編集
      return request;
  }
}


テストコードに新規作成クラスを指定

@Test
public void テスト() throws Exception {
  String hostName = "hostname.co.jp";
  String servletPath = "/test/hoge";
  mockMvc.perform(get(servletPath)
      .with(new TestRequestPostProcessor(hostName, servletPath))
      .session(mockSession)
      .param("hoge1", "hogehoge"))
      .andExpect(status().isOk());
}

これでJUnitを実行すれば、
getRequestURL()はhttp://hostname.co.jp/test/hoge
getServletPath()は/test/hoge
を返してくれるようになりました。


PR

FindBugsの設定項目

2017年08月15日 20時49分58秒
EclipseFindBugsを入れて使用していますが、FindBugsの設定項目が分かりにくい。
デフォルト設定のままだと意外と大事な問題が指摘されなかったりするので、備忘録として設定内容をメモ。

分析力
分析力には「最小」「デフォルト」「最大」の3項目があります。
普通に考えたら「最大」が一番厳しいチェックを行ってくれるのだろうと思いがち。
ですが、「最小」にした方が一番指摘件数が多い。
なぜだ?(^^;
とりあえず「最小」で動かしてます。

報告する最小ランク(1は最も厳しく、20は軽微です)
「1は最も厳しく」とあるので、1が一番厳しいチェックかと思いきや、
この項目は、報告する問題の「最小ランク」を指定。
指定したランクより高い問題が報告されるようになります。
なので1を選ぶと重大な問題しか報告されなくなります。
逆に、20だとどうでもいいような軽微な問題まで報告されるようになります。
とりあえず「20」を指定し、無視していいような問題は無視し、直すべき問題だけ直すようにしてます。

レポートする最低の信頼度
「High」「Medium」「Low」の3項目があります。
こちらも報告する「最低の信頼度」を指定。
なので、指定した信頼度より高い問題が報告されるようになります。
とりあえず「Low」を選択し、直すべき問題だけ直すようにしてます。

Spring SessionでClassNotFoundException

2017年06月26日 22時37分21秒
Spring Sessionを使ったWebアプリケーションを開発中。
セッションレプリケーションにRedisを使用。
今回想定外のエラーに出くわしたのでメモ。

データクラスを新規に作成し、クラスごとセッションにセットする修正を行い、サーバーにデプロイ。
バグがあったため、前回のバージョンでデプロイしなおし。
セッションレプリケーションしているので、サービス自体は無停止でデプロイしなおし。
ここでエラーが発生。

デプロイ自体は問題なく出来ているんですが、アクセス中だったユーザーのリクエストが500エラー。
エラー内容は、
org.springframework.data.redis.serializer.SerializationException: Cannot deserialize;
nested exception is java.lang.ClassNotFoundException:
※エラーの一部のみ記載

正常に動いていた前のバージョンに戻したのにClassNotFoudException。
なぜ?

ここからはエラー内容からの勝手な想像になりますが、
・新バージョンをデプロイ(クラスAが新規に追加される)
・セッションの中にクラスAをセット。Redisに保存。
・クラスAが存在しないバージョンでデプロイしなおし。(RedisにはクラスAのデータが残っている)
・前バージョンでデプロイしなおし(クラスAがwarファイル上から消える)
・同一セッションで新しいリクエストを実行
・SpringがRedisからセッションデータをデシリアライズ
 →クラスAなんて存在しないぞ(゚Д゚)ゴルァ!!

前バージョンのロジック上には当然クラスAなんて存在しないので、
セッションにクラスAが格納されていても問題ないと気にもしていませんでしたが、
まさかのClassNotFoudExceptionが発生。
SpringSessionは「セッション(HttpSession)」という1つの塊でRedisに保存しているのかな?
そのせいで使われないはずのクラスAのデータもわざわざデシリアライズしてエラーになっているんだと予想してます。
※SpringSessionの仕様、Redisの中身を確認したわけではないので、あくまで予想です

いちいちセッションの中身を全部デシリアライズしていたら、遅くなりそうな気がするが・・・。
セッションIDとキー名毎にRedisに保存してくれた方が早いだろうし、今回のようなエラーは発生しなかったような気が。
SpringSessionは初めて使ったのでどっか設定がイケてないのだろうか?

AWSのS3からJAVAでフォルダごと取得する方法

2017年06月05日 20時32分11秒
AWS SDK for Javaを使ってS3からフォルダごと一括で取得し、ローカルに保存する必要があったので調査。
ネットで調べると、AWS CLIを使った方法だったり、Javaでフォルダ配下のファイルを一覧でパスを取得する方法は沢山出てくるが、全ファイルを取得する方法がなかなか見つからない。

日本語で見つからないなら英語でってことで検索してみると簡単に発見。
https://stackoverflow.com/questions/35865045/how-to-download-entire-folder-located-on-s3-bucket/35865581

上記サイトにも載ってるがパラメータが何なのか日本語で分かるようにメモ。

TransferManager transferManager = new TransferManager(new
DefaultAWSCredentialsProviderChain());

MultipleFileDownload download = transferManager.downloadDirectory("バケ
ット名", "ダウンロードS3したいS3上のパス(先頭に/はNG)", new File("ロー
カルの保存先パス"));
try {
    download.waitForCompletion();
} catch (AmazonClientException | InterruptedException e) {
    e.printStackTrace();
}

Spring Session

2016年07月24日 00時24分23秒
ELB、EC2、WildFly8とSpringフレームワークを使っているWebアプリケーションでセッションレプリケーション機能を実装しなければいけなくなった。

そもそもセッションレプリケーションを実現する為にはどうすればいいのか調べてみたところ、
何個か方法はあるようだが、
一番簡単に実現できそうだったのが、比較的新しい技術のSpring Session
WildFlyやインフラ周りには一切手を加えず、JAVAソース、設定ファイルの修正だけで実現できる模様。
※セッション情報の保存先であるRedisサーバーの容易は必要(今回は保存先にRedisを採用)
※Spring Sessionを検索すると実装方法を紹介してくれるサイトは存在するが、
 「Spring Session」のことしか書いてない。
 当然といえば当然だが、Redisに接続できる状態でなければ話にならない。
 Redisの使用方法についてはここら辺を参考に
 http://tm8r.hateblo.jp/entry/20120329/1333033094

とりあえずセッション情報の保存先であるRedisについては、AWSのElastiCacheを使用。

で、ソースの修正については下記サイトを参考+試行錯誤。
参考サイト:http://qiita.com/uich/items/cd5d63c12f1d342fc65c
※Spring Sessionの実装方法を検索すると、Spring Bootでの例が多い
 今回はSpring Boot使っていないので、設定ファイル系は試行錯誤が必要だった

参考サイトと今回の修正での差分は下記の通り

pom.xml】
gradleを使っていたので、build.gradleにspring-session-data-redisの依存関係を記載

web.xml
参考サイトの記述だけだとエラーになったので下記を記載
<filter>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

<!-- ここから独自に追加 -->
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>
ContextLoaderListenerを追加すると、WEB-INFの下にapplicationContext.xmlがないとエラーになる。
なので、
classpath:applicationContext.xmlを指定してあげる。

【RedisHttpSessionConfig

基本的には参考サイトと同様だが、一部問題が発生した為、下記の通り修正
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class RedisHttpSessionConfig {
  @Resource
  private RedisConnectionFactory redisConnectionFactory;

  @Bean
  public RedisConnectionFactory connectionFactory() {
    return redisConnectionFactory;
  }

  @Bean
  public static ConfigureRedisAction configureRedisAction() {
      return ConfigureRedisAction.NO_OP;
  }
}
ElastiCacheではconfigureRedisAction()が必要らしいので追加
※参考サイトには記載されているhttpSessionStrategy()を実装すると、リクエストの度にセッションIDが変わってしまい、レプリケーションどころではなくなってしまった。原因はよく分からないがhttpSessionStrategy()自体を削除して問題解決。

以上でRedisにセッション情報が保存され、ELBに紐付いているEC2(WildFly)間でセッションが共有されるようになった。

※仕事場の情報は持ち出せないので記憶を頼りに自宅で記載してます。
 記載ミスがあったらゴメンナサイ。