問題
Spring AOP(の AspectJ)では、インタフェースにつけたアノテーションを目印としたポイントカットが使用出来ない。このため、MyBatisのMapperインタフェースのメソッドなどに対して、アノテーション目印としたインタセプタを差し込むことができない。
原因
メソッドにつけたアノテーションは継承クラス(あるいはインタフェースを実装したクラス)のメソッドに引き継がれない。そして Spring AOP(というか AspectJ)が対象とするのはインスタンス化されたBeanであり、その型はインタフェースを実装したクラスとなるため、インタフェースのメソッドにつけたアノテーションは検査されない。
解決方法
インタフェースにつけたアノテーションも、ポイントカット判定時に検査するようにAspectJPointcutAdvisor
をカスタマイズして使用する。
AspectJPointcutAdvisorの拡張
ポイントカットを取得するメソッドがあるので、これをオーバーライドして、インタフェースのアノテーションも検査します。
InterfaceMethodAwareAspectJPointcutAdvisor.java
package aop;
import java.lang.reflect.Method;
import java.util.stream.Stream;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.support.ComposablePointcut;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.val;
public class InterfaceMethodAwareAspectJPointcutAdvisor extends AspectJPointcutAdvisor {
public InterfaceMethodAwareAspectJPointcutAdvisor(@NonNull AbstractAspectJAdvice advice) {
super(advice);
}
@Override
public Pointcut getPointcut() {
val original = super.getPointcut();
return new ComposablePointcut(original)
.union(new InterfaceAwareMethodMatcher(original.getMethodMatcher()));
}
@RequiredArgsConstructor
private static class InterfaceAwareMethodMatcher implements MethodMatcher {
@NonNull
private final MethodMatcher underlying;
@Override
public boolean isRuntime() {
return underlying.isRuntime();
}
@Override
public boolean matches(Method method, Class<?> targetClass) {
return Stream.of(targetClass.getInterfaces())
.anyMatch(x -> underlying.matches(method, x));
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return Stream.of(targetClass.getInterfaces())
.anyMatch(x -> underlying.matches(method, x, args));
}
}
}
Configurationクラス
上記で作成した、インタフェースのアノテーションも検査するInterfaceMethodAwareAspectJPointcutAdvisor
を使用して、Advisorを登録する。
AspectConfiguration.java
@Configuration
@EnableAspectJAutoProxy
public class AspectConfiguraiton implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(@NonNull ConfigurableListableBeanFactory factory) {
val original = new FooAspect();
val advisor = new ReflectiveAspectJAdvisorFactory()
.getAdvisors(new SingletonMetadataAwareAspectInstanceFactory(original, "fooAspect"));
advisors.stream()
.map(advisor -> new InterfaceMethodAwareAspectJPointcutAdivsor((AbstractAspectJAdvice) advisor))
.forEacth(advisor -> factory.registerSingleton(resolveName(advisor), advisor));
}
private static String resolveName(AspectJPointcutAdvisor advisor) {
val info = (AspectJPrecedenceInformation) advisor.getAdvice();
return info.getAspectName() + '#' + info.getDeclarationOrder();
}
}