TOP > 技術メモ > Java > Spring Framework

Spring Framework

Executor(SimpleAsyncTaskExecutor, ThreadPoolTaskExecutor)

Springの非同期はデフォルト SimpleAsyncTaskExecutor が使用される。
pool-size等の指定があれば ThreadPoolTaskExecutor が使用される。
ThreadPoolTaskExecutorを使用する場合、PoolSizeとMaxPoolSize、TaskQueueCapacityの関係に注意して見積もる。 ThreadPoolExecutor PoolSizeが拡張される条件は、TaskQueueCapacityがいっぱいになること。
そのため、特に意識しないのであれば、PoolSize=MaxPoolSizeに指定するのが最適。
TaskQueueCapacityは、キュー待ち領域で、FIFO(LinkedBlockingQueue)。適切な値を指定する。

					# applicationContext.xml
					<?xml version="1.0" encoding="UTF-8"?>
					<beans
						xmlns="http://www.springframework.org/schema/beans"
						xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
						xmlns:task="http://www.springframework.org/schema/task"
						xsi:schemaLocation="
							・・・省略
							http://www.springframework.org/schema/task
							http://www.springframework.org/schema/task/spring-task-3.0.xsd">
	
						<-- 非同期アノテーションを有効にする -->
						<task:annotation-driven executor="myExecutorSample"/>
						
						<-- SimpleAsyncTaskExecutor -->
						<task:executor id="myExecutorSample" />
						
						<-- ThreadPoolTaskExecutor -->
						<-- <task:executor id="myExecutorSample" pool-size="50" queue-capacity="100" /> -->
						<-- <task:executor id="myExecutorSample" pool-size="20-50" queue-capacity="100" /> -->
					
						<bean id="AsyncSample" class="fomsan.co.jp.example.async.AsyncImple" />
					
					</beans>
					
					# interface例
					package fomsan.co.jp.example.async;

					import org.springframework.scheduling.annotation.Async;
					
					public interface IAsync {
					
						/** @Asyncアノテーションをつけるだけ */
						@Async
						abstract void Print(int i, String parrent);
					}
					

SpringAOPベンチマーク(Java Dynamic Proxy, CGLIB)

Spring AOPで選択できるAOPの仕組みとしては"Java Dynamic Proxy"と"CGLIB"があり、
対象がインターフェースを実装している場合は "Java Dynamic Proxy"、
インターフェースを実装していない場合は "CGLIB Proxy" と、使い分ける。

以下、SpringAOPのJava Dynamic ProxyとCGLIBのベンチマーク。
Java Dynamic Proxy は安定した性能だが、
CGLIB は Singleton の場合は早いが、Prototype になると性能が落ちている。
まとめると、初期化と実行は Java Dynamic Proxy の方が速いが、"singleton"実行であれば CGLIB のほうが速い
但し、処理時間も1回当たりの差はナノ秒単位なのであまり目くじら立てるほどでもない

					●SpringのAOPベンチマーク(Java Dynamic Proxy, CGLIB)
					
						■1Bean生成×10000回
							◇Java Dynamic Proxy
								singleton:1703ms
								prototype:1813ms
					
							◇CGLIB
								singleton:1625ms
								prototype:1907ms
							
							// 以下Beanを10000回生成する(getBean) *singletonはsocpe="singleton"
							<bean id="service1" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" />
					
						■5000Bean生成×1回
							◇Java Dynamic Proxy
								singleton:937ms
								prototype:953ms
					
							◇CGLIB
								singleton:875ms
								prototype:1079ms
								
							// 以下Beanをそれぞれ1回生成する(getBean) *singletonはsocpe="singleton"
							<bean id="service1" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" />
							<bean id="service2" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" />
								・・・省略
							<bean id="service5000" class="fomsan.ne.jp.di.ServiceImpl" scope="prototype" />
					
					
					●getBeanとAutowiredのベンチマーク
					
						■呼び元Singleton→Autowired(singleton)(5000Bean生成×1回)
							◇Java Dynamic Proxy
								・Autowiredアノテーション
									singleton:1828ms
									prototype:1719ms
								・getBean(String)
									singleton:1703ms
									prototype:1812ms
							◇CGLIB
								・Autowiredアノテーション
									singleton:1719ms
									prototype:1859ms
								・getBean(String)
									singleton:1734ms
									prototype:1797ms
									
						■呼び元Singleton→Autowired(prototype)(5000Bean生成×1回)
							◇Java Dynamic Proxy
								・Autowiredアノテーション
									singleton:1944ms
									prototype:1969ms
								・getBean(String)
									singleton:1875ms
									prototype:1890ms
							◇CGLIB
								・Autowiredアノテーション
									singleton:2000ms
									prototype:2125ms
								・getBean(String)
									singleton:1844ms
									prototype:1984ms
					
					
					●AOPのサンプル
					
						[applicationContext.xml]
							<!-- 対象メソッド終了後にログを出すだけ -->
							<!-- proxy-target-classが"true"がCGLIB -->
							<aop:config proxy-target-class="false">
								<aop:aspect id="sample" ref="myMethodIntercepor">
									<aop:pointcut id="pointcut" expression="execution(* fomsan.ne.jp.di..*.*(..))" />
									<aop:after pointcut-ref="pointcut" method="sampleMethod" />
								</aop:aspect>
							</aop:config>
							<bean id="myMethodIntercepor" class="fomsan.ne.jp.aop.MyMethodInterceptor" />
					
					
							<!-- 対象メソッドの呼び出し後・呼び出し前にログを出すだけ -->
							<!-- proxy-target-classが"true"がCGLIB -->
							<aop:config proxy-target-class="false">
								<aop:advisor pointcut="execution(* fomsan.ne.jp.di..*.*(..))"
									advice-ref="myLogInterceptor" />
							</aop:config>
							<bean id="myLogInterceptor" class="fomsan.ne.jp.aop.MyLogInterceptor">
								<property name="useDynamicLogger" value="true" />
							</bean>
					
					
					●getBeanとAutowiredのサンプル
						
						[Java]
							// getBean時は以下アノテーションをコメントとする
							@Autowired
							@Qualifier(value = "SubServiceImpl")
							private IService iserve;
					
							@Override
							public void sample(ApplicationContext context) {
								LOG.info("test ok");
								// getBean時は以下を有効とする
								// iserve = (IService) context.getBean("SubServiceImpl");
								iserve.sample(context);
							}
						
						[applicationContext.xml]
							<bean id="SubServiceImpl" class="fomsan.ne.jp.di.sub.SubServiceImpl" scope="prototype" />
				

Springの性能改善(チューニング)

Springのメリットは何より認知度で、技術者も多いため、皆知っているから大丈夫だろう」という安心感がある。
しかし、その安心とは裏腹に「性能を意識した設計・製造」が出来ていない現場も多く感じる。
大規模なシステムほど、最終的にDIやAOPのコストも無視できないものになってくる。
そのためにも、個人的にはSeaser2をお勧めしたいのだが・・

バージョンアップを検討する
バージョンが古い場合は、バージョンアップを真っ先に検討する
特に2.5では大幅な性能改善が行われている。
また、よくSeaser2と比較されているが、比較対象のSpringのバージョンが古い場合もあるため注意する。
例えば、以下資料は2006年に作成されているもので、Springのバージョンは2.0のため比較対象としては古い。
パフォーマンス徹底比較 Seasar2 vs Spring
それでもSeaser2の方が早いと思う(実際に使用してみて)
コンポネント設計はシングルトン(singleton)パターンとする
共通コンポネントは、scopeを"singleton"で設計する。
共通な箇所ほど、scopeを"prototype"にしがち。
"prototype"は、コンポネント取得の都度インスタンス生成やAOP折り込みコストが発生する。
そのため、なるべくシングルトンで耐えられる設計とする(排他コストが大きくなってしまっては元も子もないが・・)。
Transaction管理を意識する
「参照のみ」には"readOnly=true"を設定し、トランザクションの制御を適切に行う。
(トランザクションを無駄に作らない、トランザクションを無駄にネストしない)
また、DBコネクションのプール管理は必ず行い、可能であれば"Statement"もプール管理すること。
REQUIRED(SUPPORTS), REQUIRED_NEW, NOT_SUPPORTEDを意識した設計・製造をする。
コネクション・トランザクション生成のコスト自体も大きく、DBサーバへの負荷・リソース不足の原因にもなる。
なんでもかんでも DI (Dependency Injection) ・AOP (Aspect Oriented Programming) していないか
コンテキストファイル(applicationContext.xml)が肥大化している場合は大体これ。
DI対象のコンポネントは"依存性をなくしたい"ものだけにする。
また、DIによる動的な引数(パラメータ)渡しもなるべく避ける。
DIやAOPしている箇所は、可読性が悪くなる(コードが追いづらい)傾向にあり、 可読性が悪くなると、保守性も悪くなる。
また、処理内容はDIやAOPが行われるため、最終的には性能に大きく影響してくる。
特に、DIやAOPによる動的な引数(パラメータ)渡しは、コストが大きい。

▲ページの先頭へ