Due to the proxy-based nature of Spring’s AOP framework, calls within the target object are by definition not intercepted. For JDK proxies, only public interface method calls on the proxy can be intercepted. With CGLIB, public and protected method calls on the proxy will be intercepted, and even package-visible methods if necessary. However, common interactions through proxies should always be designed through public signatures.
Note that pointcut definitions are generally matched against any intercepted method. If a pointcut is strictly meant to be public-only, even in a CGLIB proxy scenario with potential non-public interactions through proxies, it needs to be defined accordingly.
If your interception needs include method calls or even constructors within the target class, consider the use of Spring-driven native AspectJ weavinginstead of Spring’s proxy-based AOP framework. This constitutes a different mode of AOP usage with different characteristics, so be sure to make yourself familiar with weaving first before making a decision.
Pointcut
public interface Pointcut {
ClassFilter getClassFilter();
MethodMatcher getMethodMatcher();
Pointcut TRUE = TruePointcut.INSTANCE;
}
public interface ClassFilter {
/**
* Should the pointcut apply to the given interface or target class?
* @param clazz the candidate target class
* @return whether the advice should apply to the given target class
*/
boolean matches(Class<?> clazz);
/**
* Canonical instance of a ClassFilter that matches all classes.
*/
ClassFilter TRUE = TrueClassFilter.INSTANCE;
}
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 (may be {@code null}, in which case
* the candidate class must be taken to be the method's declaring 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}?
* <p>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.
* <p>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 (may be {@code null}, in which case
* the candidate class must be taken to be the method's declaring 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;
}
ProxyFactory
- targetObject
- interface
- advisor(advice+pointcut)
基本與直接使用InvocationHandler和Proxy類似,不過是把InvocationHandler中的邏輯移到了advice,并且通過pointcut匹配需要添加邏輯的地方;
如果沒有實現接口,那么就會使用cglib來生成子類,這里不一樣的地方在于上面一種代理對象是Proxy子類。
AopProxy -- AopProxyFactory -- AdvisedSupport 《Spring揭秘》p176
ProxyFactoryBean
Proxy的FactoryBean
AutoProxy
上面的ProxyFactory都是針對特定的對象,如果目標對象太多,則工作量很大。
通過BeanPostProcessor可以實現將滿足要求的bean代理后返回
AutoProxyCreator
- BeanNameAutoProxyCreator
- DefaultAdvisorAutoProxyCreator
BeanNameAutoProxyCreator需要聲明BeanName和InterceptorName,然后將Interceptor應用到bean上,這里的Interceptor可以是Advisor或者Advice(然后包裹成DefaultPointcutAdvisor)
DefaultAdvisorAutoProxyCreator掃描所有的Advisor(只有Advisor的bean),通過pointcut匹配后將advice織入bean(還是使用ProxyFactory實現)
@AspectJ Spring AOP
基于注解的Aspect或者aop命名空間
與之前的AOP的關鍵差別:
- 使用POJO聲明Aspect和Advice,不需要實現特定的接口(Pointcut,Advice,Advisor)
- AspectJ的Pointcut表述語言,而不是方法名或者正則
- 本質沒有變,代理模式處理橫切邏輯
編程方式 | AutoProxy | XSD | |
---|---|---|---|
SpringAOP1.0 | ProxyFactory|ProxyFactoryBean | DefaultAdvisorAutoProxyCreator|BeanNameAutoProxyCreator | 無 |
SpringAOP2.0 | AspectJProxyFactory | AnnotationAwareAspectJAutoProxyCreator | <aop:config> |
AspectJProxyFactory 或 AnnotationAwareAspectjAutoProxyCreator 通過反射獲取了@Pointcut的定義后,會構造一個AspectJExpressionPointcut,而這個Pointcut在實現ClassFilter和MethodMatcher的邏輯的時候會委托AspectJ類庫完成。
- JoinPoint參數 (除了Around 和 Introduction)
- 除了execution之外,所有的標識符都可以綁定參數,然后傳入advice方法。
@Before
@Component(value = "huge")
public class TestImpl implements TestService {
@Override
@Transactional
public void print(String word) {
System.out.println(word);
}
@Override
@Transactional
public String getWord() {
return "Hello world.";
}
}
@Aspect
@Component
public class TestAspect {
@Pointcut("execution(* TestService.*(..)) && args(word)")
public void matchTestService(String word){}
@Before(value = "matchTestService(word)")
public void out(String word) {
System.out.println(word);
}
@Before(value = "matchTestService(word) && this(obj) && target(obj2) && @within(info) && @target(info2) && @annotation(info3)")
public void out2(String word, TestService obj, Object obj2, Component info, Component info2, Transactional info3) {
System.out.println(obj);
System.out.println(obj2);
System.out.println(info.value());
System.out.println(info2.value());
System.out.println(info3);
}
}
@AfterThrowing
@AfterThrowing(value = "testServiceThrow()", throwing = "e")
public void throwing(RuntimeException e){
System.out.println(e.getMessage());
}
@AfterReturning
@AfterReturning(value = "testServiceReturnValue()", returning = "value")
public void returning(String value) {
System.out.println(value);
}
@Around
第一個參數只能是ProceedingJoinPoint
@Around(value = "matchTestService(word)")
public void hach(ProceedingJoinPoint proceedingJoinPoint, String word) {
try {
proceedingJoinPoint.proceed(new Object[]{"hahahah"});
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
執行順序
What happens when multiple pieces of advice all want to run at the same join point? Spring AOP follows the same precedence rules as AspectJ to determine the order of advice execution. The highest precedence advice runs first "on the way in" (so given two pieces of before advice, the one with highest precedence runs first). "On the way out" from a join point, the highest precedence advice runs last (so given two pieces of after advice, the one with the highest precedence will run second).
When two pieces of advice defined in different aspects both need to run at the same join point, unless you specify otherwise the order of execution is undefined. You can control the order of execution by specifying precedence. This is done in the normal Spring way by either implementing the org.springframework.core.Ordered interface in the aspect class or annotating it with the Order annotation. Given two aspects, the aspect returning the lower value from Ordered.getValue() (or the annotation value) has the higher precedence.
When two pieces of advice defined in the same aspect both need to run at the same join point, the ordering is undefined (since there is no way to retrieve the declaration order via reflection for javac-compiled classes). Consider collapsing such advice methods into one advice method per join point in each aspect class, or refactor the pieces of advice into separate aspect classes - which can be ordered at the aspect level.
一個問題
如果一個被代理對象的方法A調用了自身的另一個方法B,那么如果B是被代理的方法,那么調用A間接調用B時不會觸發代理的
解決方法:AopContext.currentProxy()獲取代理對象,通過代理對象調用B會觸發代理
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class<?> targetClass = null;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
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;
}
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// We can skip creating a MethodInvocation: just invoke the target directly
// Note that the final invoker must be an InvokerInterceptor so we know it does
// nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
}
else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException(
"Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
可以看到開始代理調用的時候,會把代理對象設置到AopContext中,這個是與線程綁定的,當完成了代理調用以后,會把之前的代理對象重新設置到AopContext中
應用場景
- 異常處理
指unchecked Exception,程序無法解決,只能人工干預,所以提供足夠的信息就可以了,各種類型的unchecked Exception 可以無差別對待。 - 安全檢查
Web應用常使用Filter實現安全檢查,通過AOP可以為任何類型的應用添加相應的安全支持。 - 緩存