之前一论坛朋友问的,复制保存下。原文地址。
问题
貌似不能拦截私有方法?
试了很多次,都失败了,是不是不行啊?
我想了一下,因为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(); 这里的任何一个,因此无法代理的。
相关推荐
项目中含有一整个springboot实现aop的功能,在拦截的方法形式上有两种一种是通过切点设置为拦截某个包路径下面的类中的所有方法;还有一种是基于某个自定义注解的.
spring aop切面拦截指定类和方法实现流程日志跟踪 一般情况下,在不侵入业务代码的情况下,需要做流程日志跟踪是比较合理的 采用springaop切面思想
springaop拦截controller日志
Spring AOP 拦截器 Advisor Spring AOP 拦截器 Advisor
死磕Spring之AOP篇 - Spring AOP两种代理对象的拦截处理(csdn)————程序
springboot spring aop 拦截器 注解方式实现脱敏(涉及到:pom.xml -->application.properties --->启动类-->拦截器)
spring aop 拦截实例,下载下来直接就可以用了,很好很方便。
用spring写的小示例程序,拦截某一层的所有方法。可以在调用之前、之后、抛出异常拦截调用,日志打印。
spring之AOP(动态代理),包括jdk动态代理和CGLib动态代理
主要对Spring AOP的相关概念和简单的静态代理、动态代理以及常见的几种AOP配置方式做总结学习。主要包括:1. AOP的常见概念 2. 静态代理 3. jdk动态代理 4. Aspectj and Aspectjweaver 5. **aop-config** 6. CGLIB ...
spring aop 拦截 实例spring aop 拦截 实例spring aop 拦截 实例
spring aop 拦截系统操作日志和异常日志,存储到数据库中的全过程
spring aop spring aop spring aop spring aop spring aop spring aop spring aop spring aop spring aop
本篇文章主要介绍了详解Spring AOP 拦截器的基本实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
AOP的意思就是面向切面编程。本文主要是通过梳理JDK中自带的反射机制,实现 AOP动态代理模式,这也是Spring AOP 的实现原理
spring aop jar 包
Spring 实现AOP需要三个第三方的jar包,分别是aspectjrt.jar,aspectjweaver.jar,aopalliance-1.0.jar
Spring Mvc AOP通过注解方式拦截controller等实现日志管理
NULL 博文链接:https://conkeyn.iteye.com/blog/2354644
spring-aop-1.1.1.jar spring-aop-1.2.6.jar spring-aop-1.2.9.jar spring-aop-2.0.2.jar spring-aop-2.0.6.jar spring-aop-2.0.7.jar spring-aop-2.0.8.jar spring-aop-2.0.jar spring-aop-2.5.1.jar spring-aop-...