2013-01-15追記 struts-2.3.8では解消済みのようです。
https://issues.apache.org/jira/secure/ReleaseNote.jspa?projectId=12311041&version=12323480
最近、Struts-2.3.7がリリースされたわけですが、何故だかアプリの動作がやたら重くなる。
jarを更新した以外は何もしていないのに。
で、しょーがないので原因を調べてみた所、JBossFileManagerが犯人だということを突き止めました。
原因の解析
Struts2は、設定ファイルなどリロード対象となるリソースをFileManager経由でウォッチするわけですが、
どのFileManagerを使うか決定する処理に問題があります。以下の通り。
(DefaultFileManagerFactoryから抜粋)
public FileManager getFileManager() { Set<String> names = container.getInstanceNames(FileManager.class); if (LOG.isDebugEnabled()) { LOG.debug("Found following implementations of FileManager interface: #0", names.toString()); } Set<FileManager> internals = new HashSet<FileManager>(); Set<FileManager> users = new HashSet<FileManager>(); for (String fmName : names) { FileManager fm = container.getInstance(FileManager.class, fmName); if (fm.internal()) { internals.add(fm); } else { users.add(fm); } } for (FileManager fm : users) { if (fm.support()) { // 注意1 if (LOG.isDebugEnabled()) { LOG.debug("Using FileManager implementation [#0]", fm.getClass().getSimpleName()); } fm.setReloadingConfigs(reloadingConfigs); return fm; } } if (LOG.isDebugEnabled()) { LOG.debug("No user defined FileManager, looking up for internal implementations!"); } for (FileManager fm : internals) { if (fm.support()) { //注意2 return fm; } } if (LOG.isDebugEnabled()) { LOG.debug("Using default implementation of FileManager provided under name [system]: #0", fileManager.getClass().getSimpleName()); } fileManager.setReloadingConfigs(reloadingConfigs); return fileManager; }
このソースには2箇所、『注意』と書いてある場所がありますが、このFileManager#supportが問題。
どのFileManagerを使うか決定するのがココの処理の目的なので、すべてのFileManagerの実装に対してsupportが呼ばれます。
さて、JBossFileManager#supportの実装は次の通りです。
@Override public boolean support() { boolean supports = isJBoss7() || isJBoss5(); if (supports && LOG.isDebugEnabled()) { LOG.debug("JBoss server detected, Struts 2 will use [#0] to support file system operations!", JBossFileManager.class.getSimpleName()); } return supports; } private boolean isJBoss5() { try { Class.forName(VFS_JBOSS5); return true; } catch (ClassNotFoundException e) { LOG.debug("Cannot load [#0] class, not a JBoss 5!", VFS_JBOSS7); return false; } } private boolean isJBoss7() { try { Class.forName(VFS_JBOSS7); return true; } catch (ClassNotFoundException e) { LOG.debug("Cannot load [#0] class, not a JBoss 7!", VFS_JBOSS7); return false; } }
見て分かる通り、Class#forNameでJBoss由来のクラスがロードできるか否かで判定しています。
ま、この程度ならよくある実装パターンですが、このメソッド、何とStrutsのタグを1つ実行する度に呼ばれるようです。
つまりタグを1つ実行する度に例外が発生することになり、そして例外の生成は非常に遅い処理です。
だから全体として非常に処理が重くなるわけ。
回避策
回避策としては、FileManagerの実装を決定する処理が1回だけ行われるようにすれば良いはず。
従って、先ずは次のようなクラスを用意します。
package foo; import com.opensymphony.xwork2.FileManager; import com.opensymphony.xwork2.util.fs.DefaultFileManagerFactory; public class MyFileManagerFactory extends DefaultFileManagerFactory { private FileManager cache; @Override public FileManager getFileManager() { if (this.cache == null) { this.cache = super.getFileManager(); } return this.cache; } }
見て分かる通り、FileManagerの決定結果をキャッシュするだけです。
次に、struts.xmlに次のエントリを追加します。
<constant name="struts.fileManagerFactory" value="foo.MyFileManagerFactory" />
以上です。僕の環境ではこれで問題が解消しました。