论坛首页 Java企业应用论坛

关于spring的aop拦截的问题

浏览 12595 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-08-10   最后修改:2012-08-10
貌似不能拦截私有方法?
试了很多次,都失败了,是不是不行啊?

我想了一下,因为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的方法也不拦截。
   发表时间:2012-08-10   最后修改:2012-08-11
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 编译期/运行期织入。


引用
如果写了protected,他就什么事情都不做,连protected的方法也不拦截;这个应该不会

原因基本分析明白了:

是否能应用增强的判断代码如下(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
在使用代理的时候,@Transactional 注解应该只被应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,系统也不会报错, 但是这个被注解的方法将不会执行已配置的事务设置。如果你非要注解非公共方法的话,请参考使用AspectJ


关于spring切入点语法可以参考我的博客 【http://jinnianshilongnian.iteye.com/blog/1420691
0 请登录后投票
   发表时间:2012-08-15   最后修改:2012-08-15
非常感谢您的回帖~canApply方法是不是用于判断某个切点能否应用于某个类上,只要这个类里面有一个方法能够和切点匹配,就返回true,从整个逻辑来看,就是这个类可以/需要被代理。

实际运行时在方法拦截的时候,如果某个类不需要被代理,就直接调用这个类实例的方法,而不是这个类的代理的方法,
如果需要代理,再匹配方法名和修饰符?

对于上面这个帖子里,之所以protected方法能被无访问修修饰符的execution拦截,是因为这个类里面其他public方法被execution匹配了,导致spring认为这个类可以被代理,而不是protected的方法本身被execution匹配?
0 请登录后投票
   发表时间:2012-08-15  
wangyu1221 写道
非常感谢您的回帖~canApply方法是不是用于判断某个切点能否应用于某个类上,只要这个类里面有一个方法能够和切点匹配,就返回true,从整个逻辑来看,就是这个类可以/需要被代理。

实际运行时在方法拦截的时候,如果某个类不需要被代理,就直接调用这个类实例的方法,而不是这个类的代理的方法,
如果需要代理,再匹配方法名和修饰符?

对于上面这个帖子里,之所以protected方法能被无访问修修饰符的execution拦截,是因为这个类里面其他public方法被execution匹配了,导致spring认为这个类可以被代理,而不是protected的方法本身被execution匹配?


引用
canApply方法是不是用于判断某个切点能否应用于某个类上,只要这个类里面有一个方法能够和切点匹配,就返回true,从整个逻辑来看,就是这个类可以/需要被代理。

是的。

引用
实际运行时在方法拦截的时候,如果某个类不需要被代理,就直接调用这个类实例的方法,而不是这个类的代理的方法,
如果需要代理,再匹配方法名和修饰符?


这个只看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使用):
引用
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;
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文档
引用
7.2.4.2. 动态切入点
动态切入点比起静态切入点在执行时要消耗更多的资源。它们同时计算方法参数和静态信息。 这意味着它们必须在每次方法调用时都被计算;由于参数的不同,结果不能被缓存。
动态切入点的主要例子是控制流切入点。

这个在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(); 这里的任何一个,因此无法代理的。


不知道说明白了吗,自己也感觉比较乱,要是不明白可以私信我,qq聊。
0 请登录后投票
   发表时间:2012-08-16  
jinnianshilongnian 写道


大概有点明白了,我再仔细理解一下~
0 请登录后投票
   发表时间:2012-08-16  
wangyu1221 写道
jinnianshilongnian 写道


大概有点明白了,我再仔细理解一下~


很喜欢这样的问题 

要是再有什么问题 分享下啊
0 请登录后投票
   发表时间:2012-08-23  
jinnianshilongnian 写道
wangyu1221 写道
jinnianshilongnian 写道


大概有点明白了,我再仔细理解一下~


很喜欢这样的问题 

要是再有什么问题 分享下啊



大牛貌似公司事情不多吧,经常在论坛上看到你回答问题。
0 请登录后投票
   发表时间:2012-08-23  
kelloKitty 写道
jinnianshilongnian 写道
wangyu1221 写道
jinnianshilongnian 写道


大概有点明白了,我再仔细理解一下~


很喜欢这样的问题 

要是再有什么问题 分享下啊



大牛貌似公司事情不多吧,经常在论坛上看到你回答问题。

偶不是大牛,也是小白鼠   遇到问题就像研究明白了

最近还好,不是很忙
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics