public interface MethodMatcher {/**
* Perform static checking whether the given method matches.
* If this returns {@code false} or if the {@link #isRuntime()}
* method returns {@code false}, no runtime check (i.e. no
* {@link #matches(java.lang.reflect.Method, Class, Object[])} call)
* will be made.
* @param method the candidate method
* @param targetClass the target class
* @return whether or not this method matches statically
*/
boolean matches(Method method, Class> targetClass);
/**
* Is this MethodMatcher dynamic, that is, must a final call be made on the
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method at
* runtime even if the 2-arg matches method returns {@code true}?
* Can be invoked when an AOP proxy is created, and need not be invoked
* again before each method invocation,
* @return whether or not a runtime match via the 3-arg
* {@link #matches(java.lang.reflect.Method, Class, Object[])} method
* is required if static matching passed
*/
boolean isRuntime();
/**
* Check whether there a runtime (dynamic) match for this method,
* which must have matched statically.
* This method is invoked only if the 2-arg matches method returns
* {@code true} for the given method and target class, and if the
* {@link #isRuntime()} method returns {@code true}. Invoked
* immediately before potential running of the advice, after any
* advice earlier in the advice chain has run.
* @param method the candidate method
* @param targetClass the target class
* @param args arguments to the method
* @return whether there's a runtime match
* @see MethodMatcher#matches(Method, Class)
*/
boolean matches(Method method, Class> targetClass, Object... args);
/**
* Canonical instance that matches all methods.
*/
MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}
Advisor
前面介绍了Advice的增强逻辑,Pointcut定义了方法的集合,但是哪些方法使用什么样的增强逻辑依旧没有关联起来,Advisor就是将Advice和Pointcut结合起来,通过Advisor,可以定义在某个Pointcut连接点上使用哪个Advice。 Spring 提供了一个DefaultPointcutAdvisor, 在DefaultPointcutAdvisor中有两个属性,分别为advice和pointcut用来配置Advice 和Pointcut。 其实现如下所示。
public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {private Pointcut pointcut = Pointcut.TRUE;
/**
* Create an empty DefaultPointcutAdvisor.
* Advice must be set before use using setter methods.
* Pointcut will normally be set also, but defaults to {@code Pointcut.TRUE}.
*/
public DefaultPointcutAdvisor() {
}/**
* Create a DefaultPointcutAdvisor that matches all methods.
* {@code Pointcut.TRUE} will be used as Pointcut.
* @param advice the Advice to use
*/
public DefaultPointcutAdvisor(Advice advice) {
this(Pointcut.TRUE, advice);
}/**
* Create a DefaultPointcutAdvisor, specifying Pointcut and Advice.
* @param pointcut the Pointcut targeting the Advice
* @param advice the Advice to run when Pointcut matches
*/
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
this.pointcut = pointcut;
setAdvice(advice);
}/**
* Specify the pointcut targeting the advice.
* Default is {@code Pointcut.TRUE}.
* @see #setAdvice
*/
public void setPointcut(@Nullable Pointcut pointcut) {
this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
}@Override
public Pointcut getPointcut() {
return this.pointcut;
}@Override
public String toString() {
return getClass().getName() + ": pointcut [" + getPointcut() + "];
advice [" + getAdvice() + "]";
}}
Spring AOP的实现
前面已经介绍了Spring AOP的相关概念,但是Spring AOP是如何对方法的调用进行拦截的呢?下面就Spring AOP的实现进行分析。 同样以Spring中的单元测试开始Spring AOP的实现分析。 通过以下代码开始Spring AOP的实现分析。
@Test
public void testProxyFactory() {
TestBean target = new TestBean();
ProxyFactory pf = new ProxyFactory(target);
NopInterceptor nop = new NopInterceptor();
CountingBeforeAdvice cba = new CountingBeforeAdvice();
Advisor advisor = new DefaultPointcutAdvisor(cba);
pf.addAdvice(nop);
pf.addAdvisor(advisor);
ITestBean proxied = (ITestBean) pf.getProxy();
proxied.setAge(5);
assertThat(cba.getCalls()).isEqualTo(1);
assertThat(nop.getCount()).isEqualTo(1);
assertThat(pf.removeAdvisor(advisor)).isTrue();
assertThat(proxied.getAge()).isEqualTo(5);
assertThat(cba.getCalls()).isEqualTo(1);
assertThat(nop.getCount()).isEqualTo(2);
}
/** The AdvisorChainFactory to use. */
AdvisorChainFactory advisorChainFactory = new DefaultAdvisorChainFactory();
/** Cache with Method as key and advisor chain List as value. */
private transient Map> methodCache;
/**
* Interfaces to be implemented by the proxy. Held in List to keep the order
* of registration, to create JDK proxy with specified order of interfaces.
*/
private List> interfaces = new ArrayList<>();
/**
* List of Advisors. If an Advice is added, it will be wrapped
* in an Advisor before being added to this List.
*/
private List advisors = new ArrayList<>();
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
Assert.notNull(config, "AdvisedSupport must not be null");
if (config.getAdvisorCount() == 0 && config.getTargetSource() == AdvisedSupport.EMPTY_TARGET_SOURCE) {
throw new AopConfigException("No advisors and no TargetSource specified");
}
this.advised = config;
this.proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
findDefinedEqualsAndHashCodeMethods(this.proxiedInterfaces);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
else if (method.getDeclaringClass() == DecoratingProxy.class) {
// There is only getDecoratedClass() declared -> dispatch to proxy config.
return AopProxyUtils.ultimateTargetClass(this.advised);
}
else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}// Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
Class> targetClass = (target != null ? target.getClass() : null);
// Get the interception chain for this method.
List
public List getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, @Nullable Class> targetClass) {// This is somewhat tricky... We have to process introductions first,
// but we need to preserve order in the ultimate list.
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
Advisor[] advisors = config.getAdvisors();
List interceptorList = new ArrayList<>(advisors.length);
Class> actualClass = (targetClass != null ? targetClass : method.getDeclaringClass());
Boolean hasIntroductions = null;
for (Advisor advisor : advisors) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(actualClass)) {
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
boolean match;
if (mm instanceof IntroductionAwareMethodMatcher) {
if (hasIntroductions == null) {
hasIntroductions = hasMatchingIntroductions(advisors, actualClass);
}
match = ((IntroductionAwareMethodMatcher) mm).matches(method, actualClass, hasIntroductions);
}
else {
match = mm.matches(method, actualClass);
}
if (match) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(actualClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}return interceptorList;
}
public Object proceed() throws Throwable {
// We start with an index of -1 and increment early.
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
}Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
// Evaluate dynamic method matcher here: static part will already have
// been evaluated and found to match.
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
Class> targetClass = (this.targetClass != null ? this.targetClass : this.method.getDeclaringClass());
if (dm.methodMatcher.matches(this.method, targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}
else {
// Dynamic matching failed.
// Skip this interceptor and invoke the next in the chain.
return proceed();
}
}
else {
// It's an interceptor, so we just invoke it: The pointcut will have
// been evaluated statically before this object was constructed.
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}