SpringAOP私有方法导致注入失败原理

SpringAOP私有方法导致注入失败原理

最近开发项目的过程中,项目组成员遇到一个注入失败导致空指针的问题。原因是因为对这个方法进行了SpringAOP切面拦截,且方法是private的。
最后改为public解决问题。但是对于具体导致的原因并没有掌握,于是时候查看源码终于解惑,特此记录。

jar包版本

  • spring-boot-starter-web 2.1.3.RELEASE
    • spring-aop 5.1.5.RELEASE

问题代码

@RestController
@RequestMapping("/sql-intercept")
public class SqlInterceptController {

    @Autowired
    PersonService personService;
    
    @GetMapping("/get")
    public Object getValue() {
        return null;
    }

    @GetMapping("/add")
    private Object createValue() {
        Person person = new Person();
        personService.createPerson(person);
        return "true";
    }
}

@Component
@Aspect
public class AspectJTest {

    @Around("execution(* com.ding.spring.demo..*Controller.*(..))")
    public Object intoControllerLog(ProceedingJoinPoint point) throws Throwable {
        return point.proceed();
    }
}

首先定义一个切面,切面逻辑是Around所有的Controller,且方法属性是*,即包含了private、protected以及public。启动并执行请求createValue,没有
进入切面逻辑,且personService为空,导致空指针。执行getValue请求正常进入切面逻辑。

问题初步分析

首先我个人之前对于SpringAOP的了解是知道cglib是不会拦截private方法的(而JDK动态代理是基于接口的更不可能对private方法生效),因此不进切面逻辑是在我的理解范围之内,但是为什么连spring的注入也没有这是在我的
意料之外的。百度之后也无想要答案,遂看源码。

源码分析

首先知道@Aspect注解会生成一个Advisor的bean注册到spring容器中,并且在AbstractAutoProxyCreator#postProcessBeforeInstantiation这个方法在bean实例化初始化之后被调用到,用来判断这个实例化的bean是否要
创建Proxy。其主要逻辑在wrapIfNecessary方法中。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
	    //省略不重要代码,重点看getAdvicesAndAdvisorsForBean方法。
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}
		return bean;
	}
}

getAdvicesAndAdvisorsForBean方法就是获取所有符合条件的Advisor,其中主要调用AbstractAdvisorAutoProxyCreator#findEligibleAdvisors。

public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
		List<Advisor> candidateAdvisors = findCandidateAdvisors();
		//过滤符合条件的切面
		List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
		extendAdvisors(eligibleAdvisors);
		if (!eligibleAdvisors.isEmpty()) {
			eligibleAdvisors = sortAdvisors(eligibleAdvisors);
		}
		return eligibleAdvisors;
	}
}

public abstract class AopUtils {
    public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
        //省略不重要代码
        for (Class<?> clazz : classes) {
            //注意spring5.X此处已经改为所有方法了,而并非只是获取public方法
            Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);
            for (Method method : methods) {
                if (introductionAwareMethodMatcher != null ?
                        introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions) :
                        methodMatcher.matches(method, targetClass)) {
                    return true;
                }
            }
        }
        return false;
    }
}

这里嵌套的代码层数比较深,因此不一一展开,其逻辑就是通过AspectJExpressionPointcut#matchs方法来过滤所有方法是否需要代理bean。通过前面的问题描述bean是被代理的。因此
通过代码调试发现此处确实是返回了true,同时开始执行createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
逻辑来创建代理。(matches方法本质上是解析execution()表达式,通过对表达式的解析比对每个method,如果有一个需要被代理就返回true。这里有很多人可能有误区,认为execution(*) 只能匹配protected和public,实际上private方法在高版本的spring也是能被这里匹配的)

createProxy方法是通过proxyFactory来决定使用cglib还是使用jdk动态代理,此处代码使用的是cglib,因此直接查看CglibAopProxy#getproxy方法

class CglibAopProxy implements AopProxy, Serializable {

	@Override
	public Object getProxy(@Nullable ClassLoader classLoader) {
        // 除去不必要代码
        Enhancer enhancer = createEnhancer();
        if (classLoader != null) {
            enhancer.setClassLoader(classLoader);
            if (classLoader instanceof SmartClassLoader &&
                    ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
                enhancer.setUseCache(false);
            }
        }
        enhancer.setSuperclass(proxySuperClass);
        enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new ClassLoaderAwareUndeclaredThrowableStrategy(classLoader));

        Callback[] callbacks = getCallbacks(rootClass);
        Class<?>[] types = new Class<?>[callbacks.length];
        for (int x = 0; x < types.length; x++) {
            types[x] = callbacks[x].getClass();
        }
        // fixedInterceptorMap only populated at this point, after getCallbacks call above
        enhancer.setCallbackFilter(new ProxyCallbackFilter(
                this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
        enhancer.setCallbackTypes(types);

        // Generate the proxy class and create a proxy instance.
        return createProxyClassAndInstance(enhancer, callbacks);
	}    
}

对于cglib来说enhancer类使用是比较简单的设置完setSuperclass和callbacks就能创建代理了,那么cglib到底是在哪里过滤的private方法呢。答案就在
createProxyClassAndInstance(enhancer, callbacks)中。此处逻辑及其复杂,创建代理类的过程是通过开启线程创建的,有兴趣的可以自己调试一下,这里
直接到达关键代码。Enhancer#generateClass。

public class Enhancer extends AbstractClassGenerator {
    public void generateClass(ClassVisitor v) throws Exception {
        //省略不必要代码
        //获取需要代理增强的方法
        getMethods(sc, interfaces, actualMethods, interfaceMethods, forcePublic);
        List methods = CollectionUtils.transform(actualMethods, new Transformer() {
            public Object transform(Object value) {
                Method method = (Method) value;
                int modifiers = Constants.ACC_FINAL
                        | (method.getModifiers()
                        & ~Constants.ACC_ABSTRACT
                        & ~Constants.ACC_NATIVE
                        & ~Constants.ACC_SYNCHRONIZED);
                if (forcePublic.contains(MethodWrapper.create(method))) {
                    modifiers = (modifiers & ~Constants.ACC_PROTECTED) | Constants.ACC_PUBLIC;
                }
                return ReflectUtils.getMethodInfo(method, modifiers);
            }
        });
    }
    private static void getMethods(Class superclass, Class[] interfaces, List methods, List interfaceMethods, Set forcePublic) {
        ReflectUtils.addAllMethods(superclass, methods);
        List target = (interfaceMethods != null) ? interfaceMethods : methods;
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                if (interfaces[i] != Factory.class) {
                    ReflectUtils.addAllMethods(interfaces[i], target);
                }
            }
        }
        if (interfaceMethods != null) {
            if (forcePublic != null) {
                forcePublic.addAll(MethodWrapper.createSet(interfaceMethods));
            }
            methods.addAll(interfaceMethods);
        }
        //各种方法过滤器,CollectionUtils实际调用传入的Predicate#evaluate方法来过滤methods
        CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_STATIC));
        CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true));
        CollectionUtils.filter(methods, new DuplicatesPredicate());
        CollectionUtils.filter(methods, new RejectModifierPredicate(Constants.ACC_FINAL));
    }
}

public class VisibilityPredicate implements Predicate {
    public boolean evaluate(Object arg) {
        Member member = (Member)arg;
        int mod = member.getModifiers();
        //终于找到了关键代码
        if (Modifier.isPrivate(mod)) {
            return false;
        } else if (Modifier.isPublic(mod)) {
            return true;
        } else if (Modifier.isProtected(mod) && this.protectedOk) {
            return true;
        } else {
            return this.samePackageOk && this.pkg.equals(TypeUtils.getPackageName(Type.getType(member.getDeclaringClass())));
        }
    }
}

通过上面的enhancer代码分析,终于找到了不代理私有方法的元凶,method直接被过滤掉了。我们知道cglib实际上是对目标bean的一种继承(这个说法也许不太贴切,个人理解)
并且生成代理后的增强方法重写了父类方法。然后此处method被过滤了,因此我们大胆猜测生成的cglib代理类里面并没有private方法。为了证实这个想法,我们把生成的cglib类
字节码打印出来。(通过vm启动参数和DebuggingClassWriter.DEBUG_LOCATION_PROPERTY可以打印)

public class SqlInterceptController$$EnhancerBySpringCGLIB$$55a0cfe3 extends SqlInterceptController implements SpringProxy, Advised, Factory {
    //省略其他方法
    final Object CGLIB$createValue$0() {
        return super.createValue();
    }

    public final Object createValue() {
        try {
            MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
            if (var10000 == null) {
                CGLIB$BIND_CALLBACKS(this);
                var10000 = this.CGLIB$CALLBACK_0;
            }

            return var10000 != null ? var10000.intercept(this, CGLIB$createValue$0$Method, CGLIB$emptyArgs, CGLIB$createValue$0$Proxy) : super.createValue();
        } catch (Error | RuntimeException var1) {
            throw var1;
        } catch (Throwable var2) {
            throw new UndeclaredThrowableException(var2);
        }
    }

    final boolean CGLIB$equals$1(Object var1) {
        return super.equals(var1);
    }
    //省略其他方法
}

通过cglib创建的代理类class文件发现,确实没有private方法。因此证明了之前的推测,也就是说调用SqlInterceptController#getValue的时候并不是通过代理类调用的,而是直接调用父类的方法。
由于我们这个应用是web应用,请求是通过DispatchServlet调用到真正的handler,具体源码也不在此分析属于MVC内容,这里就直接跳转到调用

public class InvocableHandlerMethod extends HandlerMethod {
    @Nullable
    protected Object doInvoke(Object... args) throws Exception {
        ReflectionUtils.makeAccessible(getBridgedMethod());
        //直接通过的反射调用,此处getBean返回的就是我们的代理bean,SqlInterceptController$$EnhancerBySpringCGLIB$$55a0cfe3 
        //然而代理bean本身是没有覆盖父类private方法的因此调用的是父类的getValue方法
        return getBridgedMethod().invoke(getBean(), args);
    }
}

到此我们解决了第一个疑问,就是cglib为什么不代理private方法,但是还没解决第二个疑问,就是为什么会没有注入的autowire的bean呢。
我们继续分析,实际上调用创建proxy的时候是在SmartInstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation这个时候bean已经被实例化了,因此可以断定autowire的bean 实际上是已经被注入了。然而这里我们要知道被注入autowire属性的bean是被代理的那个bean而不是代理之后的SqlInterceptController$$EnhancerBySpringCGLIB$$55a0cfe3,这两个bean是有本质
区别的,创建之后的SqlInterceptController E n h a n c e r B y S p r i n g C G L I B EnhancerBySpringCGLIB EnhancerBySpringCGLIB55a0cfe3并没有被注入PersonService属性。实际上通过调试可以看到cglib创建的类实例,它的实例变量确实都是null的,因为
根本就没有注入的地方。那么问题又来了我们在使用AOP的时候明明都能正常注入使用啊。

因此继续分析cglib的调用方法。

private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
    @Override
    @Nullable
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //省略不必要代码
        TargetSource targetSource = this.advised.getTargetSource();
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
            Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
            retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
            retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;

    }
}

看到这里是不是已经恍然大悟了,代理类在调用的时候并不是传的自己的实例,而是target。这个target就是被代理的bean,因此才能获取各种注入属性。

总结

实际上对于cglib这块怎么过滤掉private其实并不关键,只要知道就行。关键就是在于我们创建的这个代理对象bean本身并不会去注入spring的bean,因此
我们直接调用未被增强的方法时,实际上是无法获取spring容器注入的bean的。而代理之后的方法实际调用的是targetSource,也就是被代理对象本身,因此是
完整的经历过springbean的生命周期的。

springAOP确实是个比较复杂的过程,只看源码很容易漏掉关键信息,最终还是要多实践,遇到问题记录下来并找寻原因。

  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 15
    评论
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值