关于spring的aop拦截的问题 protected方法代理问题
看到一篇很好的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(); 这里的任何一个,因此无法代理的。
关于spring的aop拦截的问题 protected方法代理问题的更多相关文章
- spring mvc aop拦截controller层获取RequestBody反序列化后参数
最近,为了解耦,把一逻辑从interceptor抽出来,放在aop中处理,需要得到RequestBody.如下: @Aspect @Configuration public class CheckAs ...
- Spring的 AOP底层用到两种代理机制
JDK 的动态代理:针对实现了接口的类产生代理.CGlib 的动态代理:针对没有实现接口的类产生代理,应用的是底层的字节码增强的技术 生成当前类的子类对象 JDK动态代理实现1. 创建接口和对应实现类 ...
- 利用CGLib实现动态代理实现Spring的AOP
当我们用Proxy 实现Spring的AOP的时候, 我们的代理类必须实现了委托类的接口才能实现. 而如果代理类没有实现委托类的接口怎么办? 那么我们就可以通过CGLib来实现 package cn. ...
- Spring框架第五篇之Spring与AOP
一.AOP概述 AOP(Aspect Orient Programming),面向切面编程,是面向对象编程OOP的一种补充.面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运 ...
- Spring 梳理-AOP
界面应用场景 日志.声明式事务.安全.缓存 AOP功能演化图 图片引用地址:https://www.cnblogs.com/best/p/5679656.html AOP设计模式-代理模式 静态代理: ...
- Spring基础篇——Spring的AOP切面编程
一 基本理解 AOP,面向切面编程,作为Spring的核心思想之一,度娘上有太多的教程啊.解释啊,但博主还是要自己按照自己的思路和理解再来阐释一下.原因很简单,别人的思想终究是别人的,自己的理解才是 ...
- 浅析Spring中AOP的实现原理——动态代理
一.前言 最近在复习Spring的相关内容,刚刚大致研究了一下Spring中,AOP的实现原理.这篇博客就来简单地聊一聊Spring的AOP是如何实现的,并通过一个简单的测试用例来验证一下.废话不 ...
- 利用C#实现AOP常见的几种方法详解
利用C#实现AOP常见的几种方法详解 AOP面向切面编程(Aspect Oriented Programming) 是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 下面这篇文章主要 ...
- AOP的几种实现方法
C# 实现AOP 的几种常见方式 原文出处:http://www.cnblogs.com/zuowj/p/7501896.html AOP为Aspect Oriented Programming的缩写 ...
随机推荐
- jenkins入门系列之一 jenkins的安装
Jenkins是一个CI(持续集成环境)工具.它可以根据设定持续定期编译,运行相应代码:运行UT或集成测试:将运行结果发送至邮件,或展示成报告... 这样做的最终目的是: 让项目保持健康的状态.如果任 ...
- SpringBoot学习之Json数据交互
最近在弄监控主机项目,对javaweb又再努力学习.实际的项目场景中,前后分离几乎是所以项目的标配,全栈的时代的逐渐远去,后端负责业务逻辑处理,前端负责数据展示成了一种固定的开发模式.像thymele ...
- ajax同步与异步的坑
之前工作中一个需求,需要动态的添加一组下拉菜单并为这个菜单绑定一个插件,很明显获取数据用Ajax,这本身是没错的,坑就坑在我用了 同步请求,当服务器端正确返回数据时再去执行下一个方法,这逻辑本身没 ...
- IDEA的导包优化问题
一.现象 文件初始导包状态 package co.x.dw.function; import java.text.SimpleDateFormat; import java.util.ArrayLis ...
- httpd的三种模式比较
查看你的httpd使用了哪种模式: /usr/local/apache2/bin/httpd -V |grep 'Server MPM' 使用哪种模式,需要在编译的时候指定 --with-mpm=pr ...
- linux下卸载已安装的软件
1.先查询该软件是否安装,是否存在 rpm -qa | grep -i teamview 2.根据一中的结果(软件包名称),执行如下命令 rpm -e [软件包名]
- Python常用数据结构之collections模块
Python数据结构常用模块:collections.heapq.operator.itertools collections collections是日常工作中的重点.高频模块,常用类型由: 计数器 ...
- Linux Centos下编译安装Redis
需要安装 tcl 8.5 wget http://downloads.sourceforge.net/tcl/tcl8.6.1-src.tar.gz //直接下载 sudo tar xzvf tcl8 ...
- MySQL的常见存储引擎介绍与参数设置调优
MySQL常用存储引擎之MyISAM 特性: 1.并发性与锁级别 2.表损坏修复 check table tablename repair table tablename 3.MyISAM表支持的索引 ...
- HashMap/HashSet,hashCode,哈希表
hash code.equals和“==”三者的关系 1) 对象相等则hashCode一定相等: 2) hashCode相等对象未必相等. == 是比较地址是否相等,JAVA中声明变量都是引用嘛,不同 ...