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

问题

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

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

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

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

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

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

  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):

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

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

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

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

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

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

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

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

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

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

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

  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代码如下所示:

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

关于spring的aop拦截的问题 protected方法代理问题的更多相关文章

  1. spring mvc aop拦截controller层获取RequestBody反序列化后参数

    最近,为了解耦,把一逻辑从interceptor抽出来,放在aop中处理,需要得到RequestBody.如下: @Aspect @Configuration public class CheckAs ...

  2. Spring的 AOP底层用到两种代理机制

    JDK 的动态代理:针对实现了接口的类产生代理.CGlib 的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象 JDK动态代理实现1. 创建接口和对应实现类 ...

  3. 利用CGLib实现动态代理实现Spring的AOP

    当我们用Proxy 实现Spring的AOP的时候, 我们的代理类必须实现了委托类的接口才能实现. 而如果代理类没有实现委托类的接口怎么办? 那么我们就可以通过CGLib来实现 package cn. ...

  4. Spring框架第五篇之Spring与AOP

    一.AOP概述 AOP(Aspect Orient Programming),面向切面编程,是面向对象编程OOP的一种补充.面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运 ...

  5. Spring 梳理-AOP

    界面应用场景 日志.声明式事务.安全.缓存 AOP功能演化图 图片引用地址:https://www.cnblogs.com/best/p/5679656.html AOP设计模式-代理模式 静态代理: ...

  6. Spring基础篇——Spring的AOP切面编程

    一  基本理解 AOP,面向切面编程,作为Spring的核心思想之一,度娘上有太多的教程啊.解释啊,但博主还是要自己按照自己的思路和理解再来阐释一下.原因很简单,别人的思想终究是别人的,自己的理解才是 ...

  7. 浅析Spring中AOP的实现原理——动态代理

    一.前言   最近在复习Spring的相关内容,刚刚大致研究了一下Spring中,AOP的实现原理.这篇博客就来简单地聊一聊Spring的AOP是如何实现的,并通过一个简单的测试用例来验证一下.废话不 ...

  8. 利用C#实现AOP常见的几种方法详解

    利用C#实现AOP常见的几种方法详解 AOP面向切面编程(Aspect Oriented Programming) 是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 下面这篇文章主要 ...

  9. AOP的几种实现方法

    C# 实现AOP 的几种常见方式 原文出处:http://www.cnblogs.com/zuowj/p/7501896.html AOP为Aspect Oriented Programming的缩写 ...

随机推荐

  1. Hibernate之HelloWorld

    1. 步骤 0. 导入相关Jar包. 1. 编写Hibernate的持久化文件 (默认为hibernate.cfg.xml). 2. 编写持久化类. 3. 创建对象 - 关系文件(.htm.xml文件 ...

  2. 痞子衡随笔:常用的数据传输差错检测技术(1)- 奇偶校验(Parity Check)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是嵌入式数据传输里的差错检测技术-奇偶校验. 在嵌入式应用里,除了最核心的数据处理外,我们还会经常和数据传输打交道.数据传输需要硬件传输接口 ...

  3. 聊聊一直困扰前端程序员的浏览器兼容-【css】

    1.为什么会出现浏览器兼容问题? 由于各大主流浏览器由不同的厂家开发,所用的核心架构和代码也很难重和,这就为各种莫名其妙的Bug(代码错误)提供了温床.再加上各大厂商出于自身利益考虑而设置的种种技术壁 ...

  4. LNMP搭建02 -- 编译安装Nginx

    [编译安装Nginx]   为了顺利安装Nginx,先安装下面这些: [CentOS 编译 nginx 前要做的事情] yum install gcc gcc-c++ kernel-devel yum ...

  5. Redis进阶实践之十三 Redis的Redis-trib.rb文件详解

    一.简介     事先说明一下,本篇文章不涉及对redis-trib.rb源代码的分析,只是从使用的角度来阐述一下,对第一次使用的人来说很重要.redis-trib.rb是redis官方推出的管理re ...

  6. mongodb 配置均衡器的运行窗口

    当系统的数据量增长不是太快的时候,考虑到数据迁移会降低系统性能,可以配置均衡器在只在特定时间段运行.详细的配置步骤如下: 连接到任意的mongos服务器,并通过安全认证(如果有认证的话). 切换到co ...

  7. 让网站通过Https访问

    Prerequisites Before you begin, you should have some configuration already taken care of. We will be ...

  8. 《android开发艺术探索》读书笔记(十五)--Android性能优化

    接上篇<android开发艺术探索>读书笔记(十四)--JNI和NDK编程 No1: 如果<include>制定了这个id属性,同时被包含的布局文件的根元素也制定了id属性,那 ...

  9. spring boot admin + spring boot actuator + erueka 微服务监控

    关于spring boot actuator简单使用,请看 简单的spring boot actuator 使用,点击这里 spring boot admin 最新的正式版本是1.5.3 与 spri ...

  10. Enum枚举写的一个简单状态机

    今天下雨,心情有点压抑,所以用枚举写个状态机排解一下心情,顺便记录一下枚举使用方法. package statemachine; import java.util.ArrayList; import ...