看到一篇很好的Spring aop 攔截方法的問題, 原文地址。
問題
貌似不能攔截私有方法?
試了很多次,都失敗了,是不是不行啊?
我想了一下,因為aop底層是代理,
jdk是代理接口,私有方法必然不會存在在接口里,所以就不會被攔截到;
cglib是子類,private的方法照樣不會出現在子類里,也不能被攔截。
我不是類內部直接調用方法,而是通過維護一個自身實例的代理
execution(* test.aop.ServiceA.*(..))
- public class ServiceA {
- private ServiceA self;
- public void setSelf(ServiceA self) {
- this.self = self;
- }
- public String methodA(String str) {
- System.out.println("methodA: args=" + str);
- self.methodB("b");
- return "12345" + str;
- }
- private String methodB(String str) {
- System.out.println("methodB: args=" + str);
- self.methodC("c");
- return "12345" + str;
- }
- public String methodC(String str) {
- System.out.println("methodC: args=" + str);
- return "12345" + str;
- }
- }
如果外部調用methodA,那么methodA和methodC會被攔截到,methodB不行
是不是這么回事?
但是stackoverflow上,有人說 it works fine
http://stackoverflow.com/questions/4402009/aspectj-and-catching-private-or-inner-methods
execution(public * test.aop.ServiceA.*(..))
還有個奇怪的現象,execution里如果不寫權限,那么public protected package的方法都能被攔截到
如果寫了public,那就只攔截public方法這個沒問題,
如果寫了protected,他就什么事情都不做,連protected的方法也不攔截。
分析
private方法 在Spring使用純Spring AOP(只能攔截public/protected/包)都是無法被攔截的 因為子類無法覆蓋;包級別能被攔截的原因是,如果子類和父類在同一個包中是能覆蓋的。
在cglib代理情況下, execution(* *(..)) 可以攔截 public/protected/包級別方法(即這些方法都是能代理的)。
- private static boolean isOverridable(Method method, Class targetClass) {
- if (Modifier.isPrivate(method.getModifiers())) {
- return false;
- }
- if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {
- return true;
- }
- return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass));
- }
如果想要實現攔截private方法的 可以使用 原生 AspectJ 編譯期/運行期織入。
原因基本分析明白了:
是否能應用增強的判斷代碼如下(org.springframework.aop.support.AopUtils):
- public static boolean canApply(Pointcut pc, Class targetClass, boolean hasIntroductions) {
- if (!pc.getClassFilter().matches(targetClass)) {
- return false;
- }
- MethodMatcher methodMatcher = pc.getMethodMatcher();
- IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
- if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
- introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
- }
- Set classes = new HashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
- classes.add(targetClass);
- for (Iterator it = classes.iterator(); it.hasNext();) {
- Class clazz = (Class) it.next();
- Method[] methods = clazz.getMethods();
- for (int j = 0; j < methods.length; j++) {
- if ((introductionAwareMethodMatcher != null &&
- introductionAwareMethodMatcher.matches(methods[j], targetClass, hasIntroductions)) ||
- methodMatcher.matches(methods[j], targetClass)) {
- return true;
- }
- }
- }
- return false;
- }
此處Method[] methods = clazz.getMethods();只能拿到public方法。。
場景1:execution(* *(..))
- public class Impl2 {
- protected/public String testAop2() {
- System.out.println("234");
- return "1233";
- }
- }
因為切入點沒有訪問修飾符,即可以是任意,因此canApply方法能拿到如wait這種public方法,即可以實施代理。
場景2:execution(public * *(..))
- public class Impl2 {
- public String testAop2() {
- System.out.println("234");
- return "1233";
- }
- }
因為攔截public的,因此canApply方法能拿到如wait這種public方法,即可以實施代理。
場景3:execution(protected * *(..))
- public class Impl2 {
- protected String testAop2() {
- System.out.println("234");
- return "1233";
- }
- }
還記得之前說過,在canApply方法中 的 Method[] methods = clazz.getMethods();只能拿到public方法的,因此跟protected訪問修飾符是無法匹配的,所以如果“execution(protected * *(..))” 是 無法代理的。
這就是為什么execution(protected * *(..))在純Spring AOP環境下不行的原因。
注,@Transactional注解事務的特殊情況:
在使用代理的時候,@Transactional 注解應該只被應用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,系統也不會報錯, 但是這個被注解的方法將不會執行已配置的事務設置。如果你非要注解非公共方法的話,請參考使用AspectJ
關於spring切入點語法可以參考我的博客 【http://jinnianshilongnian.iteye.com/blog/1420691】
實際運行時在方法攔截的時候,如果某個類不需要被代理,就直接調用這個類實例的方法,而不是這個類的代理的方法,
如果需要代理,再匹配方法名和修飾符?
對於上面這個帖子里,之所以protected方法能被無訪問修修飾符的execution攔截,是因為這個類里面其他public方法被execution匹配了,導致spring認為這個類可以被代理,而不是protected的方法本身被execution匹配?
是的。
如果需要代理,再匹配方法名和修飾符?
這個只看Cglib2AopProxy吧:
- public int accept(Method method) {
- if (AopUtils.isFinalizeMethod(method)) {
- logger.debug("Found finalize() method - using NO_OVERRIDE");
- return NO_OVERRIDE;
- }
- if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&
- method.getDeclaringClass().isAssignableFrom(Advised.class)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Method is declared on Advised interface: " + method);
- }
- return DISPATCH_ADVISED;
- }
- // We must always proxy equals, to direct calls to this.
- if (AopUtils.isEqualsMethod(method)) {
- logger.debug("Found 'equals' method: " + method);
- return INVOKE_EQUALS;
- }
- // We must always calculate hashCode based on the proxy.
- if (AopUtils.isHashCodeMethod(method)) {
- logger.debug("Found 'hashCode' method: " + method);
- return INVOKE_HASHCODE;
- }
- Class targetClass = this.advised.getTargetClass();
- // Proxy is not yet available, but that shouldn't matter.
- List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
- boolean haveAdvice = !chain.isEmpty();
- boolean exposeProxy = this.advised.isExposeProxy();
- boolean isStatic = this.advised.getTargetSource().isStatic();
- boolean isFrozen = this.advised.isFrozen();
- if (haveAdvice || !isFrozen) {
- // If exposing the proxy, then AOP_PROXY must be used.
- if (exposeProxy) {
- if (logger.isDebugEnabled()) {
- logger.debug("Must expose proxy on advised method: " + method);
- }
- return AOP_PROXY;
- }
- String key = method.toString();
- // Check to see if we have fixed interceptor to serve this method.
- // Else use the AOP_PROXY.
- if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Method has advice and optimisations are enabled: " + method);
- }
- // We know that we are optimising so we can use the
- // FixedStaticChainInterceptors.
- int index = ((Integer) this.fixedInterceptorMap.get(key)).intValue();
- return (index + this.fixedInterceptorOffset);
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("Unable to apply any optimisations to advised method: " + method);
- }
- return AOP_PROXY;
- }
- }
- else {
- // See if the return type of the method is outside the class hierarchy
- // of the target type. If so we know it never needs to have return type
- // massage and can use a dispatcher.
- // If the proxy is being exposed, then must use the interceptor the
- // correct one is already configured. If the target is not static cannot
- // use a Dispatcher because the target can not then be released.
- if (exposeProxy || !isStatic) {
- return INVOKE_TARGET;
- }
- Class returnType = method.getReturnType();
- if (targetClass == returnType) {
- if (logger.isDebugEnabled()) {
- logger.debug("Method " + method +
- "has return type same as target type (may return this) - using INVOKE_TARGET");
- }
- return INVOKE_TARGET;
- }
- else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) {
- if (logger.isDebugEnabled()) {
- logger.debug("Method " + method +
- " has return type that ensures this cannot be returned- using DISPATCH_TARGET");
- }
- return DISPATCH_TARGET;
- }
- else {
- if (logger.isDebugEnabled()) {
- logger.debug("Method " + method +
- "has return type that is assignable from the target type (may return this) - " +
- "using INVOKE_TARGET");
- }
- return INVOKE_TARGET;
- }
- }
- }
List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
即如果此方法有對應的advice就走代理。
//getInterceptorsAndDynamicInterceptionAdvice代碼如下所示:
- public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
- MethodCacheKey cacheKey = new MethodCacheKey(method);
- List cached = (List) this.methodCache.get(cacheKey);
- if (cached == null) {
- cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
- this, method, targetClass); //轉調DefaultAdvisorChainFactory
- this.methodCache.put(cacheKey, cached);
- }
- return cached;
- }
也就是說需要一次切入點的匹配,即如果方法有切入點就走代理方法 否則目標方法。
再來看CglibMethodInvocation(cglib的 DynamicAdvisedInterceptor使用):
// 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;
if (dm.methodMatcher.matches(this.method, this.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);
}
}
/**
* Gives a marginal performance improvement versus using reflection to
* invoke the target when invoking public methods.
*/
protected Object invokeJoinpoint() throws Throwable {
if (this.protectedMethod) {
return super.invokeJoinpoint();
}
else {
return this.methodProxy.invoke(this.target, this.arguments);
}
}
}
即如果有InterceptorAndDynamicMethodMatcher 這是動態切入點切入點匹配器:
引用spring文檔
動態切入點比起靜態切入點在執行時要消耗更多的資源。它們同時計算方法參數和靜態信息。 這意味着它們必須在每次方法調用時都被計算;由於參數的不同,結果不能被緩存。
動態切入點的主要例子是控制流切入點。
這個在spring aop中只有一種情況:PerTargetInstantiationModelPointcut 這個切入點;這個可以參考《【第六章】 AOP 之 6.8 切面實例化模型 ——跟我學spring3 》 pertarget。
也就是說如果是
靜態切入點代理:如果有匹配的advice就走代理;
動態切入點代理:需要在運行時進行匹配。
綜上所述:
execution(* *(..)) 可以匹配public/protected的,因為public的有匹配的了,目標類就代理了,,,再進行切入點匹配時也是能匹配的,而且cglib方式能拿到包級別/protected方法,而且包級別/protected方法可以直接通過反射調用。
對於上面這個帖子里,之所以protected方法能被無訪問修修飾符的execution攔截,是因為這個類里面其他public方法被execution匹配了,導致spring認為這個類可以被代理,而不是protected的方法本身被execution匹配?
這個是因為protected 修飾符的切入點 無法匹配 Method[] methods = clazz.getMethods(); 這里的任何一個,因此無法代理的。