Spring+HibernateEntityManager(AspectJ AOP with Load-Time-Weaver編)

今回は、Spring+HibernateEntityManager(@Required編)の続きです。

トランザクションがかからない!?

現在は、ServiceImpl#registerUser(User) メソッドに、@Transactional アノテーションを用いてトランザクションをかけています。

@Transactional
public void registerUser(User<Integer> user) {
    getEntityManager().persist(user);
}

ここで、このメソッドにバリデーションをかけるために、メソッドを分割しようとおもいます。

ということで、現在1つのメソッドを、下記の3つのメソッドに分割してみます。

  • 元の registerUser メソッドを、下記の2メソッドを呼び出すようにして、@Transaction アノテーションを削除。
  • User 登録の可否をチェックするメソッドを作成。
  • 実際に User を登録するメソッドを作成し、@Transaction アノテーションを設定。
/**
 * {@inheritDoc}
 */
@Override
public void registerUser(User<Integer> user) {
    if (isRegisterableUser(user)) {
        doRegisterUser(user);
    }
}

/**
 * User が登録可能かどうかを返します。
 * 
 * @param user
 *            User
 * @return User が登録可能な場合は true
 */
protected boolean isRegisterableUser(User<Integer> user) {
    return true;
}

/**
 * User を登録します。
 * 
 * @param user
 *            User
 */
@Transactional
protected void doRegisterUser(User<Integer> user) {
    getEntityManager().persist(user);
}


では、実行!

おや? トランザクションがかかりません o...rz

プロキシベースのAOPの限界

なぜ、トランザクションがかからなかったのでしょうか?


それは、プロキシをベースとしたAOPの限界を超えたからです。


以前は、Main が持つ UserService の参照は、実は UserServiceImpl をラップしたプロキシでした。そのため、Main が UserService#registerUser(User) を呼び出す場合、プロキシを経由して処理が行われました。そして、プロキシを経由する際に、トランザクションを開始する処理が実行されていました。


しかし今回は、 UserServiceImpl#registerUser(User) には @Transactional アノテーションがありません。そのため、プロキシを経由する際には、トランザクションに関する処理は行われません。そして、UserServiceImpl#registerUser(User) から UserServiceImpl#doRegisterUser(User) が呼ばれるわけですが、同一インスタンス内での呼び出しのため、プロキシを経由しません。そうすると、トランザクションを開始する処理が行われません。


※詳細は SpringFramework のマニュアルの 6.6.1. Understanding AOP proxies で説明されているので、ぜひご一読を。

AspectJ があるじゃないか

プロキシがダメなら、アスペクトがあるじゃないか。というわけで、AspectJ を使ってみることにします。

load-time-weaver

beans.xml に、load time weaver の設定をします。

<!-- LTW enables AOP not "Spring AOP" but "Aspectj AOP" -->
<context:load-time-weaver/>

この設定をするだけでは、以下のようなエラーが出てしまいますので、次に javaagent の設定を行います。

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'loadTimeWeaver': Initialization of bean failed; nested exception is java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-agent.jar
Caused by: java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:spring-agent.jar
javaagentの設定

まずは、spring-agent.jar を用意します。そして、javaの起動オプションで、javaagentを指定します。

以下、VMオプションの例。

-javaagent:/spring-agent-2.5.4.jar
annotation-driven

beans.xml の、annotation-driven の設定を変更します。

以前は、以下のように設定していました。

<!-- for @Transactional annotations -->
<tx:annotation-driven transaction-manager="txManager"/>

今回は、AspectJ を使用するために、設定を以下のように変更します。

<!-- Enables @Transactional annotations with "Aspectj AOP" -->
<tx:annotation-driven transaction-manager="txManager" mode="aspectj"/>
jarの追加

上記のように設定しても、以下のように怒られてしまうので、spring-aspects.jar を追加します。

Exception in thread "main" org.springframework.beans.factory.CannotLoadBeanClassException: Cannot find class [org.springframework.transaction.aspectj.AnnotationTransactionAspect] for bean with name 'org.springframework.transaction.config.internalTransactionAspect' defined in null; nested exception is java.lang.ClassNotFoundException: org.springframework.transaction.aspectj.AnnotationTransactionAspect
Caused by: java.lang.ClassNotFoundException: org.springframework.transaction.aspectj.AnnotationTransactionAspect

pom.xml への dependency の追加。

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>2.5.4</version>
</dependency>

AspectJ AOP with Load-Time-Weaver 版の実行!

今度は、下記のように、DBに対して insert が走りました。

トランザクションの設定が正しく行われたようです。(`・ω・´)

SET autocommit=0
insert into User (name) values ('Kumapiyo')
commit
SET autocommit=1