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可以为任何类型的应用添加相应的安全支持。 - 缓存