浏览 12583 次
锁定老帖子 主题:关于spring的aop拦截的问题
精华帖 (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的方法也不拦截。 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间: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】 |
|
返回顶楼 | |
发表时间:2012-08-15
最后修改:2012-08-15
非常感谢您的回帖~canApply方法是不是用于判断某个切点能否应用于某个类上,只要这个类里面有一个方法能够和切点匹配,就返回true,从整个逻辑来看,就是这个类可以/需要被代理。
实际运行时在方法拦截的时候,如果某个类不需要被代理,就直接调用这个类实例的方法,而不是这个类的代理的方法, 如果需要代理,再匹配方法名和修饰符? 对于上面这个帖子里,之所以protected方法能被无访问修修饰符的execution拦截,是因为这个类里面其他public方法被execution匹配了,导致spring认为这个类可以被代理,而不是protected的方法本身被execution匹配? |
|
返回顶楼 | |
发表时间: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聊。 |
|
返回顶楼 | |
发表时间:2012-08-16
jinnianshilongnian 写道
大概有点明白了,我再仔细理解一下~ |
|
返回顶楼 | |
发表时间:2012-08-16
wangyu1221 写道 jinnianshilongnian 写道
大概有点明白了,我再仔细理解一下~ 很喜欢这样的问题 要是再有什么问题 分享下啊 |
|
返回顶楼 | |
发表时间:2012-08-23
jinnianshilongnian 写道 wangyu1221 写道 jinnianshilongnian 写道
大概有点明白了,我再仔细理解一下~ 很喜欢这样的问题 要是再有什么问题 分享下啊 大牛貌似公司事情不多吧,经常在论坛上看到你回答问题。 |
|
返回顶楼 | |
发表时间:2012-08-23
kelloKitty 写道 jinnianshilongnian 写道 wangyu1221 写道 jinnianshilongnian 写道
大概有点明白了,我再仔细理解一下~ 很喜欢这样的问题 要是再有什么问题 分享下啊 大牛貌似公司事情不多吧,经常在论坛上看到你回答问题。 偶不是大牛,也是小白鼠 遇到问题就像研究明白了 最近还好,不是很忙 |
|
返回顶楼 | |