アドバイスの作成2 - @Around
Spring AOP により、どのようなことができるのか、少しずつ掴めてきたかと思います。既に説明済みではありますが、@Around
については、挙動にくせがあるので、ここで実際に確認しておきます。
SQL の結果を、ヒープメモリー上のマップにキャッシュするためのコンフィグレーション、ポイントカット、およびアドバイスを、それぞれ以下を参考に作成します。
// src/main/java/io/github/yo1000/sss/config/CacheConfiguration.java
package io.github.yo1000.sss.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ConcurrentHashMap;
@Configuration
public class CacheConfiguration {
public static class CacheStoreMap<K, V> extends ConcurrentHashMap<K, V> {}
@Bean
public CacheStoreMap<String, Object> cacheStoreMap() {
return new CacheStoreMap<>();
}
}
// src/main/java/io/github/yo1000/sss/aspect/RepositoryPointcut.java
package io.github.yo1000.sss.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class RepositoryPointcut {
@Pointcut("execution(* io.github.yo1000.sss.repository..MemoRepository+.save(..))")
public void save() {}
@Pointcut("execution(* io.github.yo1000.sss.repository..MemoRepository+.findByAuthor(..)) && args(author)")
public void findByAuthor(String author) {}
}
// src/main/java/io/github/yo1000/sss/aspect/CacheAdvice.java
package io.github.yo1000.sss.aspect;
import io.github.yo1000.sss.config.CacheConfiguration;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class CacheAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(CacheAdvice.class);
private CacheConfiguration.CacheStoreMap<String, Object> cacheStoreMap;
@Autowired
public CacheAdvice(CacheConfiguration.CacheStoreMap<String, Object> cacheStoreMap) {
this.cacheStoreMap = cacheStoreMap;
}
@Around(value = "io.github.yo1000.sss.aspect.RepositoryPointcut.findByAuthor(author)")
public Object cacheFind(ProceedingJoinPoint proceedingJoinPoint, String author) throws Throwable {
LOGGER.info("getSignature().toLongString() " + proceedingJoinPoint.getSignature().toLongString());
if (getCacheStoreMap().containsKey(author)) {
LOGGER.info("Cache hit");
return getCacheStoreMap().get(author);
}
LOGGER.info("Cache miss");
Object returnValue = proceedingJoinPoint.proceed();
getCacheStoreMap().put(author, returnValue);
return returnValue;
}
public CacheConfiguration.CacheStoreMap<String, Object> getCacheStoreMap() {
return cacheStoreMap;
}
}
アプリケーションを再起動して、改めて、http://localhost:8080/memo/金次郎 へアクセスします。画面が表示されたら、ページをリロードして、ログを確認します。
2016-05-24 20:23:48.278 INFO 8091 --- [nio-8080-exec-1] io.github.yo1000.sss.aspect.CacheAdvice : getSignature().toLongString() public abstract java.util.List io.github.yo1000.sss.repository.MemoRepository.findByAuthor(java.lang.String)
2016-05-24 20:23:48.279 INFO 8091 --- [nio-8080-exec-1] io.github.yo1000.sss.aspect.CacheAdvice : Cache miss
2016-05-24 20:23:50.499 INFO 8091 --- [nio-8080-exec-2] io.github.yo1000.sss.aspect.CacheAdvice : getSignature().toLongString() public abstract java.util.List io.github.yo1000.sss.repository.MemoRepository.findByAuthor(java.lang.String)
2016-05-24 20:23:50.500 INFO 8091 --- [nio-8080-exec-2] io.github.yo1000.sss.aspect.CacheAdvice : Cache hit
初回アクセスでは、Cache miss
が出力され、リロード後は、Cache hit
がログに出力されます。キャッシュ上に目的のオブジェクトが見つかった場合、#proceed()
を呼び出しておらず、DB へはアクセスされないことが確認できます。
このように、@Around
を使用すると、目的の処理前後にアドバイスが作用し、本来の処理の流れそのものを変更できてしまいます。非常に協力に開発を助ける一方、処理の流れを変えることができてしまうため、ポイントカット記述次第では、予期せぬ処理の変更が紛れ込んでしまうこともあり、十分に注意して使用する必要があります。
サンプル
ここまでのコードサンプルは、以下より入手できます。
$ git clone -b chapter/9 https://github.com/yo1000/self-study-spring.git