2014年12月21日

Maven経由のユニットテストでCDIを簡単に使う

前の二つの記事でCDIとJPAをユニットテストで使う方法について書いてきたから詳細なプログラムの内容はそっちに任せるとして、
まとめたパッケージを簡単に使う方法をまとめると、pom.xmlをこんな感じに。

	<repositories>
……
<repository>
<id>Empressia</id>
<url>http://www.empressia.jp/maven/</url>
</repository>
</repositories>

<dependencies>
……
<dependency>
<groupId>jp.empressia</groupId>
<artifactId>jp.empressia.test</artifactId>
<version>1.0.1-1</version>
<scope>test</scope>
</dependency>
</dependencies>

これでRunner指定すればCDIが使えるよ。さらにbeans.xmlをこんな感じに。

	<beans>
<alternatives>
<stereotype>jp.empressia.test.UnitTest</stereotype>
</alternatives>
<intercepters>
<class>jp.empressia.test.TransactionInterceptor</class>
</intercepters>
</beans>

これでJPAとJTAが使える。一部だけど。

で、Maven使ってるときに気をつけないといけないことがあるんだよね。

beans.xmlにalternatives書くのは、main側じゃないとダメって言うか、main側のパスに入ってないとだめなのね。
でも、main側にテストの要素書いちゃだめなわけで……えーっと、
mainのclassesとtest-classesで別のbeans.xm管理になるって言う感じ?
実際にalternativeしたいのは、main側。設定書いておきたいのはtest側。
有効にするためには、test側にもbeans.xml必要。
うーん……ここ、ポイントだからしっかり整理〜。

  • JavaEEの環境だと、beans.xml無くてもCDIは有効になる。
  • JavaSEの環境だとbeans.xmlが必要。
  • Mavenだとクラスファイルの格納場所が、classesとtest-classesに分かれてる。
  • ユニットテストでalternativeの書いたbeans.xmlを置かないといけないのはclasses側(試して見た感じ)。
  • ユニットテストでCDIを有効にするためにbeans.xml置くのはtest-classes側(中身は何でもよさそう)(試して見た感じ)。
  • テスト固有のbeans.xmlの内容はmain側じゃなくてtest側に書いておきたい。

これを解決しようとすると、
testするときには、テスト側のbeans.xmlをmain側において実行して、
パッケージするときは、メイン側のbeans.xmlで上書きするって手順にしないとダメそう。

あと、persistence.xmlもmain側だけじゃなくてtest側にもおいちゃうと、ユニットテストの時に、EclipseLinkを使った時点で両方読まれちゃう。
test用にRESOURCE_LOCALで細かい接続設定書いたpersistence.xmlを置くだろうから、
ユニットテストの時はmainの方を消すのを忘れずに。packagingの時にこれも気をつけないとね。

具体的にはMavenのビルドをこんな感じに。
良い悪いいろんな意見はあるだろうけど、まぁ、手軽にやるなら良いかなって。

	<build>
<plugins>
……
<!-- process-test-resources:beans.xmlをmainのclassesにコピーする。test側のbeans.xmlに書かれた内容をユニットテストの時に読み込まれるようにする。 -->
<plugin>
<groupId>com.github.goldin</groupId>
<artifactId>copy-maven-plugin</artifactId>
<version>0.2.5</version>
<executions>
<execution>
<id>copy test beans.xml to main for unit test.</id>
<phase>process-test-resources</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<resources>
<resource>
<targetPath>${project.build.directory}/classes/META-INF</targetPath>
<file>src/test/resources/META-INF/beans.xml</file>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- process-test-resources:persistence.xmlをmainのclassesから削除する。mainのpersistence.xmlがユニットテストの時に読み込まれるのを防止する。 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<version>2.6.1</version>
<executions>
<execution>
<id>remove persistence.xml from classes for avoiding duplicated persistence.xml at unit test.</id>
<phase>process-test-resources</phase>
<goals>
<goal>clean</goal>
</goals>
<configuration>
<excludeDefaultDirectories>true</excludeDefaultDirectories>
<filesets>
<fileset>
<directory>${project.build.directory}/classes/META-INF</directory>
<includes>
<include>persistence.xml</include>
</includes>
</fileset>
</filesets>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
<!-- テスト用にtargetが書き換えているから、src側からパッケージする。 -->
<webResources>
<resource>
<directory>src/main/resources/META-INF</directory>
<targetPath>WEB-INF/classes/META-INF</targetPath>
<includes>
<include>beans.xml</include>
<include>persistence.xml</include>
</includes>
</resource>
<resource>
<directory>target/classes/META-INF</directory>
<excludes>
<!-- beans.xmlはユニットテストの時にmain側にコピーしてるから取り込まないようにする。 -->
<exclude>beans.xml</exclude>
<!-- persistence.xmlはユニットテストの時に消えるはずだけどincludeと形をあわせておく。 -->
<exclude>persistence.xml</exclude>
</excludes>
</resource>
</webResources>
</configuration>
</plugin>
</plugins>
</build>

ここまでで、@Injectが使いたい放題でJPAのPersistenceUnit1個に対応して、Transactionalにも簡単にだけど対応した状態になっているはず!(゜▽、゜
気に入らなければCDIの力で差し替えも簡単だから、是非、お試しあれ?(゜▽、゜

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

JPAとJTAをJavaSE環境で使えるようにする

こっちの記事に書いたけど、CDIが出来ちゃえば、後はもう簡単だよね。
CDI使ってるってことは、リソース系は、全部@Produces作ってるはずだから、
それを上書きしちゃえばok!

JPAは普通……かどうか分からないけど、CDI使っているなら、
persistence.xmlに定義しているUnitごとに、Producer作るよね。
たとえば、persistence.xmlにSamplePUが定義されてるとするとこんな感じ。

	@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE})
public @interface Sample {
}
	@RequestScoped
public class SampleProducer {

@PersistenceContext(unitName="SamplePU")
private EntityManager em;

@Produces @Sample
public EntityManager getEntityManager() {
return this.em;
}

}

そしたら、テスト側はこんな感じのを用意すれば完成!テスト用のbeans.xmlに追加するの忘れないでね。

	package jp.empressia.test;
// (略)
@Stereotype
@Alternative
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Documented
public @interface UnitTest {
}
	@RequestScoped
public class LocalSampleProducer {

private static final EntityManagerFactory emf;

static {
emf = Persistence.createEntityManagerFactory("SamplePU");
}

private EntityManager em;

public LocalSampleProducer() {
this.em = emf.createEntityManager();
}

@Produces @Sample @UnitTest
public EntityManager getEntityManager() {
return this.em;
}

}
	<beans>
<alternatives>
<stereotype>jp.empressia.test.UnitTest</stereotype>
</alternatives>
</beans>

EntityManagerFactoryとEntityManagerの扱い方はこれでいいよね、たぶん。

それから、JTAかな。
CDIとJTAなら、javax.transaction.Transactional使っているという前提で、Interceptorを作っちゃう方向でいいよね。
あと、ユニットテストだし、接続先は共通で1個だけってことにするよ。
まず、ProducerにTransactionを提供する部分を追加するのだ。

	@RequestScoped
public class LocalSampleProducer {
// (略)
@Produces
public EntityTransaction getTransaction() {
return this.em.getTransaction();
}
}

そうしたら、こんな感じのInterceptorでも十分かなっと。
別にもっと作り込んでも良いんだけど、わたしはそんな高度な使い方しないしね。

	package jp.empressia.test;
// (略)
@Transactional
@Interceptor
public class TransactionInterceptor {

@Inject
private EntityTransaction trans;

@AroundInvoke
public Object doTransaction(InvocationContext ic) throws Exception {
Object result = null;
try {
this.trans.begin();
result = ic.proceed();
if(this.trans.isActive()) {
if(this.trans.getRollbackOnly() == false) {
this.trans.commit();
} else {
this.trans.rollback();
}
} else {
String message = "なにかメッセージ";
logger(this).warning(message);
}
} catch(Exception proceededEx) {
try {
if(trans.isActive()) {
trans.rollback();
}
} catch(Exception rollbackEx) {
String message = "なにかメッセージ";
logger(this).severe(message);
}
throw proceededEx;
}
return result;
}
}

で、これを、やっぱりbeans.xmlにっと。

	<beans>
<intercepters>
<class>jp.empressia.test.TransactionInterceptor</class>
</intercepters>
</beans>

これで、@TransactionalもEntityManagerのInjectも動くね。

ちなみに、前の記事のパッケージには、接続先のUnitが1個前提の実装が入ってるよ。
beans.xmlをこんな感じにすれば有効になるようになってる。

	<beans>
<alternatives>
<stereotype>jp.empressia.test.UnitTest</stereotype>
</alternatives>
<intercepters>
<class>jp.empressia.test.TransactionInterceptor</class>
</intercepters>
</beans>

あとはLocalなEntityManagerなProducerをこんな感じに書けばok。
『@Sample』みたいな指定が元々なければ、子のクラスは用意する必要も無い……はず、たぶん。
@Inject EntityManagerが動作してるからね。

	@RequestScoped
public class LocalSampleProducer {

@Inject
private EntityManager em;

@Produces @Sample @UnitTest
public EntityManager getEntityManager() {
return this.em;
}

}

あ、もしかしたら、EclipseLinkをjavaagentに指定して起動すれば、もっと簡単かもしれないけど、その辺は試してないよ。

posted by すふぃあ at 21:00| Comment(2) | TrackBack(0) | 雁字

JavaEE用のスコープをJavaSE環境で使えるようにする

CDIをJavaSEで使えるようにするのはそんな難しいことじゃないよね。
問題なのは、JavaEEのスコープをJavaSEで使うこと。
RequestScopedとか、ConversationScopedとか、SessionScopedとか。

他のカスタムされたスコープは、まぁ、必要な人が対応すれば良いよねってことで。
基本は、同じだし。

まずは、コンテナとしての機能を追加しないとダメだから、JUnitのRunnerを拡張してWeldの機能を有効にするところから。
ホントは、別の方法とかあるのかもしれないけど、JUnitに詳しくなくて……動けば良いよね!

こんな感じ。

	public class JUnit4RunnerWithWeld extends BlockJUnit4ClassRunner {

/** Weld */
private static final Weld Weld;
/** Weld Container. */
private static final WeldContainer Container;

static {
Weld = new Weld();
Container = Weld.initialize();
}

/** 対象となるテストクラス。 */
private final Class<?> TargetTestClass;

public JUnit4RunnerWithWeld(final Class<Object> TargetTestClass) throws InitializationError {
super(TargetTestClass);
this.TargetTestClass = TargetTestClass;
}

@Override
protected Object createTest() throws Exception {
Object test = JUnit4RunnerWithWeld.Container.instance().select(this.TargetTestClass).get();
return test;
}

}

ポイントは2つ。

  1. Weldを作って初期化する。
  2. BlockJUnit4ClassRunnerを継承してcreateTestで対象のクラスをコンテナとして作る。

うん、簡単だよね!Weld大好き!……かも。いや、別にそんな好きなわけじゃないけど。
ちなみに、Runner自体は、テストクラスごとに出来るから、Weldはstaticに作ってるよ。
これで、ユニットテストクラスに@RunWithでCDIを有効にできるようになったのだ。

	@RunWith(JUnit4RunnerWithWeld.class)
public class SomethingTest {

あ、実際に有効にするには、beans.xml忘れないでね。
beans.xmlをどういう風にしたら良いかは別の記事でまとめるよ。

次に、JavaEEのスコープにコンテキストとストレージを設定しないとね。
まぁ、これは、GlassFish3.xのころにViewScopedとか作ってたし楽勝ってことで。
こんなユーティリティを用意する。長いよ。

	public class WeldUtilities {

public static String ExtensionProductName = "Empressia Test Library";
public static String SupportCDIProductName = "Weld";

/** JavaEE向けのスコープをJavaSEでサポートする。 */
public static void supportEEScopes(BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Map<String, Object> requestMap = new HashMap<>();
WeldUtilities.activateContextWithStorage(bm, RequestScoped.class, requestMap);
Map<String, Object> sessionMap = new HashMap<>();
WeldUtilities.activateContextWithStorage(bm, SessionScoped.class, sessionMap);
WeldUtilities.activateContextWithStorage(bm, ConversationScoped.class, new MutableBoundRequest(requestMap, sessionMap));
}

/** JavaEE向けのスコープをJavaSEでサポートをリセットする。 */
public static void resetEEScopeContexts(BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
AbstractBoundContext<?>[] contexts = {
WeldUtilities.findFirstContext(bm, RequestScoped.class),
WeldUtilities.findFirstContext(bm, SessionScoped.class),
WeldUtilities.findFirstContext(bm, ConversationScoped.class)
};
for(AbstractBoundContext<?> context : contexts) {
context.deactivate();
context.cleanup();
}
WeldUtilities.supportEEScopes(bm);
}

public static <S> void activateContextWithStorage(BeanManager bm, Class<? extends Annotation> scopeType, S storage)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
AbstractBoundContext<S> boundContext = WeldUtilities.findFirstContext(bm, scopeType);
boundContext.associate(storage);
boundContext.activate();
}

/** BeanManagerから指定スコープの最初のContextを抽出する。アクティブかどうかは考慮されない。 */
public static <S> AbstractBoundContext<S> findFirstContext(BeanManager bm, Class<? extends Annotation> scopeType)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Map<Class<? extends Annotation>, List<Context>> contextsMap = WeldUtilities.extractContextsMap(bm);
List<Context> contexts = contextsMap.get(scopeType);
// ContextはScopeの種類に対して必ず1個用意してある前提で。
if(contexts.isEmpty()) {
String message = "なにかメッセージ";
throw new IllegalStateException(message);
}
Context context = contexts.get(0);
if(context instanceof AbstractBoundContext) {
} else {
// java.lang.ClassCastException: org.jboss.weld.context.PassivatingContextWrapper$AlterableContextWrapper cannot be cast to org.jboss.weld.context.AbstractBoundContext
context = PassivatingContextWrapper.unwrap(context);
}
@SuppressWarnings("unchecked")
AbstractBoundContext<S> boundContext = (AbstractBoundContext<S>)context;
return boundContext;
}

/** BeanManagerからScopeTypeごとの?Contextを抽出する。 */
public static Map<Class<? extends Annotation>, List<Context>> extractContextsMap(BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
BeanManagerImpl beanManagerImpl;
if(bm instanceof BeanManagerProxy) {
BeanManagerProxy beanManagerProxy = (BeanManagerProxy)bm;
beanManagerImpl = beanManagerProxy.delegate();
} else if(bm instanceof BeanManagerImpl) {
beanManagerImpl = (BeanManagerImpl)bm;
} else {
String message = "なにかメッセージ";
throw new IllegalStateException(message);
}
// getContextだとactiveなものしか取れないから、強引に設定したい今回の用途では使えない。
Method m = BeanManagerImpl.class.getDeclaredMethod("getContexts");
m.setAccessible(true);
@SuppressWarnings("unchecked")
Map<Class<? extends Annotation>, List<Context>> contextsMap = (Map<Class<? extends Annotation>, List<Context>>)m.invoke(beanManagerImpl);
return contextsMap;
}

}

この辺は、解説入れるとめんどくさいから、WeldのソースとかGlassFishのソースとか読むと良いかもってことで。
で、これを呼び出すExtensionを用意する。

	package jp.empressia.test;
// (略)
public class WeldTestExtension implements Extension {

public void afterDeployment(@Observes AfterDeploymentValidation e, BeanManager bm)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
logger(this).info("Empressia Test Library Weld Test Extension startup.");
WeldUtilities.supportEEScopes(bm);
}

}

このExtensionは、META-INF/servicesに『javax.enterprise.inject.spi.Extension』ってファイルを作って、その中に、

	jp.empressia.test.WeldTestExtension

とか書いておくよ。こうしておくことで、Weldの初期化がされたときに、@Observesが呼ばれてJavaEEのスコープがすぐに有効になるのだ。
あと、テストごとに、スコープはリセットした方が良いだろうから、Runnerのメソッドをオーバーライドしておこうね。

	public class JUnit4RunnerWithWeld extends BlockJUnit4ClassRunner {

// (略)

/** テストを1個ずつ実行します。順序は保証されないから、コンテキストは毎回リセットする。 */
@Override
protected void runChild(FrameworkMethod method, RunNotifier notifier) {
BeanManager bm = JUnit4RunnerWithWeld.Container.getBeanManager();
try {
WeldUtilities.resetEEScopeContexts(bm);
} catch(NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
String message = "なにかメッセージ";
throw new IllegalStateException(message, ex);
}
super.runChild(method, notifier);
}

}

これで完成(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン

この辺のクラスをパッケージにしたのを置いておいたよ。

Mavenのリポジトリを追加して依存関係を書けば使えるよ。こんな感じ?

<repositories>
……
<repository>
<id>Empressia</id>
<url>http://www.empressia.jp/maven/</url>
</repository>
</repositories>

<dependencies>
……
<dependency>
<groupId>jp.empressia</groupId>
<artifactId>jp.empressia.test</artifactId>
<version>1.0.1-1</version>
<scope>test</scope>
</dependency>
</dependencies>

古い実装とかライブラリが混じっていると、JNDIにBeanManagerを登録しておかないとちゃんと動かないかもしれないけど。
このパッケージは、その辺も簡単にだけど対応しておいたよ。

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

JavaEEのユニットテスト環境を用意する

☆はじめに☆

JavaEEの機能は、去年作ったファイル転送Webアプリだと、WebSocketの機能しか使ってなかったし、
そんなユニットテスト書くくらい難しいの自体作ってないんだよね。
で、今回は、JavaEEをもうちょっとしっかり使おうかなと。

とりあえず、JavaEEが提供しているJAX-RS、CDI、JPAあたりを使ってWebアプリケーションを作ろうと思ったんだけど、
これ、どれもユニットテストがめんどくさい。
まぁ、JAX-RSは、問題領域とは直接関係ないから、ユニットテストの対象からは外すとして、問題はCDIとJPAだよね。

JavaEE自体は、関数の集まりみたいなライブラリと言うよりは、コンテナだから、
ユニットテストするためには、そのコンテナを再現しないといけないんだよね。
特に、CDIはコンテナそのものって感じだしね。

あとは、環境に依存したリソースの表現だよね。
JPAは、永続化先を抽象化してくれるけど、ユニットテストの時は、それが逆にめんどくさいことに。
でも、抽象化されること自体は便利だからやっぱりJPAは使うよね。

なんか、Mockって言うの作って対応したりする例とかよくWebで見かけるんだけど、
DBとかに繋げば良いだけなのに、そんなの作るのめんどくさいよね。
きっとそれが必要な時もあるんだとは思うけど。

☆準備☆

さて、ユニットテスト環境を作るにしても、何か想定はしないとダメだよね。
今回は、こんな条件を付けてみるよ。

  • ゴール:JavaEEのWebアプリ向けに問題領域のユニットテストとしてのコンテナを用意して簡単に使えるようにする。
  • ユニットテストには、JUnitを使う。
  • CDIとJPA(とJTA)を何となく使えるようにする。
  • ユニットテストでJPAは1個の接続先を使う。
  • JavaSE向けの実装は、WeldとEclipseLinkのGlassFish4.1のバージョンを使う。
  • Mock系のライブラリとかは使わない(使うといろいろ楽だけど依存しない)。
  • ビルドにはMavenを使う。

で、ちょっと書き出してから気づいたんだけど、これ、結構長くなる……。
ってことで分割したよ。

ホントいつも流れで書いてごめんなさい(゜▽、゜

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字

Java8の暗号利用モードのGCMって遅すぎない?

去年作ったファイル転送WebアプリをGlassFish4.1とJava8に載せ替えたんだけど、
それだけで、凄ーく遅くなったの。
載せ替える前はGlassFish4だからJava7で動いてたんだよね。
あ、ハードはfit-PC2iのWindows8(32bit)で動いてるよ。

実際に測定するとこんな感じ。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定)
サーバー 速度
Java7 GlassFish4.0 26000〜28000bps
Java8 GlassFish4.1 2600〜2800bps

うん、超遅くなった。あり得ないよね、ほんと。

わたしが作った部分がおかしいのかなーとか思って、プログラムを見たんだけど、
ファイルの書き込みくらいしか重そうな処理がなったんだよね。
ファイルIOが遅いのかなーって思って、ファイルの書き込み処理を外すとこんな感じ。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定:書き込み処理無し)
サーバー 速度
Java7 GlassFish4.0 26000〜28000bps
Java8 GlassFish4.1 2600〜2800bps

変わんないし!全く変わんないし!

ファイル転送のWebアプリだから、ファイルの書き込み取っちゃったら、何も残らないのに……。
と言うことで、JavaとGlassFishに問題がないか探すことに。
まぁ、普通に考えると、GlassFish側かな?Javaなコアなところはみんな触ってるだろうし。

とはいえまずは実測実測。パフォーマンス測定の基本は実測だよね!
GlasFish4.1はJava7でも動くはずだから計ってみた。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定)
サーバー 速度
Java7 GlassFish4.1 26000〜28000bps

あれ……。えーっと……。遅くならない……。
Javaのコアな部分に問題があるってこと……???(゜▽、゜
もうわたしにはわからない、わからないよ。

こうなったらもうプロファイル?を見るしかないよね!
と言うことで、NetBeansのプロファイル機能を見てみる。

Java8で時間がかかってるあたり
call_hierarchy.png

うん、完全にSSLの復号化のところですっごい時間かかってるね。
同じあたりをJava7とJava8で比較してみる。

Java7の呼び出しツリー
call_hierarchy_jdk7.png

Java8の呼び出しツリー
call_hierarchy_jdk7.png

Java8は結構呼び出しを展開してみたんだけど、同じ呼び出しが延々深く続いてるだけみたい。
NetBeansが重くなって最後まで開けなかったけど。

ここから分かるのは、Java7の方は、『AES』とか『CihperBlockChaining』とかが見えるんだけど、
Java8の方は、『AES』の他に、『GaloisCounterMode』とか『GHASH』とかが見える。
使われている暗号化の方式が変わってる……?

Oracleのサイトを見ると、Java7からJava8になったときにGCMって言うのが増えてるんだね。
https://docs.oracle.com/javase/jp/8/technotes/guides/security/SunProviders.html

これか!これが悪さしてるのか!新しいのに遅いなんて!

と、決めつける前に、確認確認。
Java7とJava8とで、それぞれのGlassFish4.1のセキュリティ設定を確認してみる。

Java7のセキュリティ設定
ciphers_jdk7.png

Java8のセキュリティ設定
ciphers_jdk8.png

たしかに、Java8の方が多くなってる〜。
これでJava8の時の暗号化方式をGCM以外から選ぶように設定ッと。
select_without_gcm.png

測定しなおした結果はこんな感じ。

ファイル転送Webアプリの転送速度(インターネットを経由させて測定:GCM除外)
サーバー 速度
Java8 GlassFish4.1 26000〜28000bps

速くなった!原因はGCM、これだね!

GlassFish4.1が関係ないことを確認するために、
Java8で実際に検証コードを書いてみる。

こんな感じ。大きく差が出るだろうから、適当で良いよね。

CBC

	int length = 200000000;
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
Key key = keyGenerator.generateKey();
IvParameterSpec parameters = new IvParameterSpec(new byte[16]);
Cipher CBCEncrypter = Cipher.getInstance("AES/CBC/PKCS5Padding");
CBCEncrypter.init(Cipher.ENCRYPT_MODE, key, parameters);
Cipher CBCDecrypter = Cipher.getInstance("AES/CBC/PKCS5Padding");
CBCDecrypter.init(Cipher.DECRYPT_MODE, key, parameters);
byte[] targetBytes = new byte[length];
long time0 = System.currentTimeMillis();
byte[] encryptedBytes = CBCEncrypter.doFinal(targetBytes);
long time1 = System.currentTimeMillis();
byte[] decryptedBytes = CBCDecrypter.doFinal(encryptedBytes);
long time2 = System.currentTimeMillis();
System.out.println(Arrays.equals(targetBytes, decryptedBytes));
System.out.println("暗号化時間:" + (time1 - time0) + "ミリ秒");
System.out.println("復号化時間:" + (time2 - time1) + "ミリ秒");

GCM

	int length = 200000000;
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
Key key = keyGenerator.generateKey();
GCMParameterSpec parameters = new GCMParameterSpec(128, new byte[16]);
Cipher GCMEncrypter = Cipher.getInstance("AES/GCM/PKCS5Padding");
GCMEncrypter.init(Cipher.ENCRYPT_MODE, key, parameters);
Cipher GCMDecrypter = Cipher.getInstance("AES/GCM/PKCS5Padding");
GCMDecrypter.init(Cipher.DECRYPT_MODE, key, parameters);
byte[] targetBytes = new byte[length];
long time0 = System.currentTimeMillis();
byte[] encryptedBytes = GCMEncrypter.doFinal(targetBytes);
long time1 = System.currentTimeMillis();
byte[] decryptedBytes = GCMDecrypter.doFinal(encryptedBytes);
long time2 = System.currentTimeMillis();
System.out.println(Arrays.equals(targetBytes, decryptedBytes));
System.out.println("暗号化時間:" + (time1 - time0) + "ミリ秒");
System.out.println("復号化時間:" + (time2 - time1) + "ミリ秒");

測定結果は、こんな感じ〜。

# バイト数 時間(ミリ秒)  
CBC GCM GCM/CBC
暗号化 復号化 暗号化 復号化 暗号化 復号化 AVG
1 100000 16 24 160 96 10.0 4.0 7.0
2 100000 16 24 160 96 10.0 4.0 7.0
3 1000000 64 88 1000 915 15.6 10.4 13.0
4 1000000 56 88 1016 912 18.1 10.4 14.3
5 10000000 392 400 8483 7852 21.6 19.6 20.6
6 10000000 376 368 7881 8004 21.0 21.8 21.4
7 100000000 3060 3351 53760 68640 17.6 20.5 19.0
8 100000000 3065 3325 74182 72976 24.2 21.9 23.1
9 100000000 2720 3142 61908 64244 22.8 20.4 21.6
10 200000000 4886 5103 110236 102925 22.6 20.2 21.4
11 200000000 6267 6223 125277 110626 20.0 17.8 18.9
12 200000000 5658 6211 124878 124675 22.1 20.1 21.1

 

うん、サイズが小さいときも遅いけど、サイズが大きくなってくるとだいたい20倍くらい遅いね!これはひどい(゜▽、゜
GCMのWikipedia見た感じだと、速くなりそうなのにね。
遅いにしても限度って言うものがあると思うんだけど……。
もしかして、去年Windowsのアプリが遅くなったのも何か関係あったりするのかな?
暗号化は、WindowsのNativeなAPI使ってるとか……?

まぁ、とりあえず直ったから良いよね(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン

posted by すふぃあ at 21:00| Comment(3) | TrackBack(0) | 雁字

JavaEEなWebアプリケーションを作ろうとしたときのお話

☆はじめに☆

この記事はJava Advent Calendar 2014の12月21日分の記事だよ。

前は、2012年の時にAndroidの話を書いたんだけど、
今回は、Webアプリケーションを作ろうとしたときの話を書くのだ。

☆導入☆

去年の12月くらいに、いつも使ってたWindowsのファイル転送アプリが凄ーく遅くなって、
もう、これは耐えられない!ってことで、代わりにGlassFish4のWebSocketを使ったファイル転送Webアプリを作ったの。

で、先月、他にもなんかWebアプリケーションを作ってみようかなって思って、
ついでだからGlassFishを4.1にバージョンアップ!さらにJavaも7から8にバージョンアップ!したんだけど、
そのとたんに、ファイル転送Webアプリの速度が凄〜く遅くなったんだよね。

と言うわけで前編はその時のお話だよ。

で、遅いのは解決したんだけど、
新しいWebアプリは、ただ、転送するだけのアプリと違って、
いろいろ作りたかったから、
ちょっとがんばってユニットテストも作ろうかな!って思ったんだよね。

と言うわけで後編はその時のお話だったり。

ちなみに、今回作ったWebアプリケーション自体については何も書いてなかったりする(゜▽、゜

それと、今回はホント、ネタのあたりから、検証あたりとか、いろいろ協力してもらえて助かったのだ(゚ー゚)(。_。)(゚-゚)(。_。)ウンウン
間接的にも助けてもらったりしたし、みんなに感謝なのだ(゜▽、゜

posted by すふぃあ at 21:00| Comment(1) | TrackBack(0) | 雁字