zoukankan      html  css  js  c++  java
  • 关于spring的aop拦截的问题 protected方法代理问题

    看到一篇很好的Spring aop 拦截方法的问题,  原文地址。

    问题

    貌似不能拦截私有方法? 
    试了很多次,都失败了,是不是不行啊? 

    我想了一下,因为aop底层是代理, 
    jdk是代理接口,私有方法必然不会存在在接口里,所以就不会被拦截到; 
    cglib是子类,private的方法照样不会出现在子类里,也不能被拦截。 

    我不是类内部直接调用方法,而是通过维护一个自身实例的代理 

    execution(* test.aop.ServiceA.*(..)) 

    Java代码  收藏代码
    1. public class ServiceA {  
    2.   
    3.     private ServiceA  self;  
    4.   
    5.     public void setSelf(ServiceA self) {  
    6.         this.self = self;  
    7.     }  
    8.   
    9.     public String methodA(String str) {  
    10.         System.out.println("methodA: args=" + str);  
    11.         self.methodB("b");  
    12.         return "12345" + str;  
    13.     }  
    14.   
    15.     private String methodB(String str) {  
    16.         System.out.println("methodB: args=" + str);  
    17.         self.methodC("c");  
    18.         return "12345" + str;  
    19.     }  
    20.   
    21.     public String methodC(String str) {  
    22.         System.out.println("methodC: args=" + str);  
    23.         return "12345" + str;  
    24.     }  
    25. }  



    如果外部调用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/包级别方法(即这些方法都是能代理的)。 

    Java代码  收藏代码
    1. private static boolean isOverridable(Method method, Class targetClass) {  
    2.         if (Modifier.isPrivate(method.getModifiers())) {  
    3.             return false;  
    4.         }  
    5.         if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {  
    6.             return true;  
    7.         }  
    8.         return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass));  
    9.     }  




    如果想要实现拦截private方法的 可以使用 原生 AspectJ 编译期/运行期织入。 


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


    原因基本分析明白了: 

    是否能应用增强的判断代码如下(org.springframework.aop.support.AopUtils): 

    Java代码  收藏代码
    1. public static boolean canApply(Pointcut pc, Class targetClass, boolean hasIntroductions) {  
    2.     if (!pc.getClassFilter().matches(targetClass)) {  
    3.         return false;  
    4.     }  
    5.   
    6.     MethodMatcher methodMatcher = pc.getMethodMatcher();  
    7.     IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;  
    8.     if (methodMatcher instanceof IntroductionAwareMethodMatcher) {  
    9.         introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;  
    10.     }  
    11.   
    12.     Set classes = new HashSet(ClassUtils.getAllInterfacesForClassAsSet(targetClass));  
    13.     classes.add(targetClass);  
    14.     for (Iterator it = classes.iterator(); it.hasNext();) {  
    15.         Class clazz = (Class) it.next();  
    16.         Method[] methods = clazz.getMethods();  
    17.         for (int j = 0; j < methods.length; j++) {  
    18.             if ((introductionAwareMethodMatcher != null &&  
    19.                     introductionAwareMethodMatcher.matches(methods[j], targetClass, hasIntroductions)) ||  
    20.                     methodMatcher.matches(methods[j], targetClass)) {  
    21.                 return true;  
    22.             }  
    23.         }  
    24.     }  
    25.   
    26.     return false;  
    27. }  



    此处Method[] methods = clazz.getMethods();只能拿到public方法。。 

    场景1:execution(* *(..)) 

    Java代码  收藏代码
    1. public class Impl2  {  
    2.       
    3.     protected/public String testAop2() {  
    4.         System.out.println("234");  
    5.         return "1233";  
    6.     }  
    7. }  


    因为切入点没有访问修饰符,即可以是任意,因此canApply方法能拿到如wait这种public方法,即可以实施代理。 

    场景2:execution(public * *(..)) 

    Java代码  收藏代码
    1. public class Impl2  {  
    2.       
    3.     public String testAop2() {  
    4.         System.out.println("234");  
    5.         return "1233";  
    6.     }  
    7. }  


    因为拦截public的,因此canApply方法能拿到如wait这种public方法,即可以实施代理。 


    场景3:execution(protected * *(..)) 

    Java代码  收藏代码
    1. public class Impl2  {  
    2.       
    3.     protected String testAop2() {  
    4.         System.out.println("234");  
    5.         return "1233";  
    6.     }  
    7. }  


    还记得之前说过,在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

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

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

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



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


    是的。 

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



    这个只看Cglib2AopProxy吧: 

    Java代码  收藏代码
    1. public int accept(Method method) {  
    2.             if (AopUtils.isFinalizeMethod(method)) {  
    3.                 logger.debug("Found finalize() method - using NO_OVERRIDE");  
    4.                 return NO_OVERRIDE;  
    5.             }  
    6.             if (!this.advised.isOpaque() && method.getDeclaringClass().isInterface() &&  
    7.                     method.getDeclaringClass().isAssignableFrom(Advised.class)) {  
    8.                 if (logger.isDebugEnabled()) {  
    9.                     logger.debug("Method is declared on Advised interface: " + method);  
    10.                 }  
    11.                 return DISPATCH_ADVISED;  
    12.             }  
    13.             // We must always proxy equals, to direct calls to this.  
    14.             if (AopUtils.isEqualsMethod(method)) {  
    15.                 logger.debug("Found 'equals' method: " + method);  
    16.                 return INVOKE_EQUALS;  
    17.             }  
    18.             // We must always calculate hashCode based on the proxy.  
    19.             if (AopUtils.isHashCodeMethod(method)) {  
    20.                 logger.debug("Found 'hashCode' method: " + method);  
    21.                 return INVOKE_HASHCODE;  
    22.             }  
    23.             Class targetClass = this.advised.getTargetClass();  
    24.             // Proxy is not yet available, but that shouldn't matter.  
    25.             List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);  
    26.             boolean haveAdvice = !chain.isEmpty();  
    27.             boolean exposeProxy = this.advised.isExposeProxy();  
    28.             boolean isStatic = this.advised.getTargetSource().isStatic();  
    29.             boolean isFrozen = this.advised.isFrozen();  
    30.             if (haveAdvice || !isFrozen) {  
    31.                 // If exposing the proxy, then AOP_PROXY must be used.  
    32.                 if (exposeProxy) {  
    33.                     if (logger.isDebugEnabled()) {  
    34.                         logger.debug("Must expose proxy on advised method: " + method);  
    35.                     }  
    36.                     return AOP_PROXY;  
    37.                 }  
    38.                 String key = method.toString();  
    39.                 // Check to see if we have fixed interceptor to serve this method.  
    40.                 // Else use the AOP_PROXY.  
    41.                 if (isStatic && isFrozen && this.fixedInterceptorMap.containsKey(key)) {  
    42.                     if (logger.isDebugEnabled()) {  
    43.                         logger.debug("Method has advice and optimisations are enabled: " + method);  
    44.                     }  
    45.                     // We know that we are optimising so we can use the  
    46.                     // FixedStaticChainInterceptors.  
    47.                     int index = ((Integer) this.fixedInterceptorMap.get(key)).intValue();  
    48.                     return (index + this.fixedInterceptorOffset);  
    49.                 }  
    50.                 else {  
    51.                     if (logger.isDebugEnabled()) {  
    52.                         logger.debug("Unable to apply any optimisations to advised method: " + method);  
    53.                     }  
    54.                     return AOP_PROXY;  
    55.                 }  
    56.             }  
    57.             else {  
    58.                 // See if the return type of the method is outside the class hierarchy  
    59.                 // of the target type. If so we know it never needs to have return type  
    60.                 // massage and can use a dispatcher.  
    61.                 // If the proxy is being exposed, then must use the interceptor the  
    62.                 // correct one is already configured. If the target is not static cannot  
    63.                 // use a Dispatcher because the target can not then be released.  
    64.                 if (exposeProxy || !isStatic) {  
    65.                     return INVOKE_TARGET;  
    66.                 }  
    67.                 Class returnType = method.getReturnType();  
    68.                 if (targetClass == returnType) {  
    69.                     if (logger.isDebugEnabled()) {  
    70.                         logger.debug("Method " + method +  
    71.                                 "has return type same as target type (may return this) - using INVOKE_TARGET");  
    72.                     }  
    73.                     return INVOKE_TARGET;  
    74.                 }  
    75.                 else if (returnType.isPrimitive() || !returnType.isAssignableFrom(targetClass)) {  
    76.                     if (logger.isDebugEnabled()) {  
    77.                         logger.debug("Method " + method +  
    78.                                 " has return type that ensures this cannot be returned- using DISPATCH_TARGET");  
    79.                     }  
    80.                     return DISPATCH_TARGET;  
    81.                 }  
    82.                 else {  
    83.                     if (logger.isDebugEnabled()) {  
    84.                         logger.debug("Method " + method +  
    85.                                 "has return type that is assignable from the target type (may return this) - " +  
    86.                                 "using INVOKE_TARGET");  
    87.                     }  
    88.                     return INVOKE_TARGET;  
    89.                 }  
    90.             }  
    91.         }  


    List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); 
    即如果此方法有对应的advice就走代理。 

    //getInterceptorsAndDynamicInterceptionAdvice代码如下所示: 

    Java代码  收藏代码
    1. public List getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {  
    2.     MethodCacheKey cacheKey = new MethodCacheKey(method);  
    3.     List cached = (List) this.methodCache.get(cacheKey);  
    4.     if (cached == null) {  
    5.         cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(  
    6.                 this, method, targetClass); //转调DefaultAdvisorChainFactory  
    7.         this.methodCache.put(cacheKey, cached);  
    8.     }  
    9.     return cached;  
    10. }  



    也就是说需要一次切入点的匹配,即如果方法有切入点就走代理方法 否则目标方法。 


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

  • 相关阅读:
    [Liferay6.2.2]AUI的小坑:input的type属性
    官方Tomcat 8.0.24 Web漏洞整改记录
    通过ajax访问Tomcat服务器web service接口时出现No 'Access-Control-Allow-Origin' header问题的解决办法
    前端开发之BOM和DOM
    前端开发之JavaScript
    前端开发之CSS
    前端开发之HTML
    python编程之进程
    python编程之操作系统基础
    python网络编程之socket
  • 原文地址:https://www.cnblogs.com/protected/p/6652188.html
Copyright © 2011-2022 走看看