SpringMVC中控制层private方法中service层的Bean为null
一、现象
SpringMVC中controller里的private接口中注入的service层的bean为null,而同一个controller中访问修饰符为public和protected的方法不会出现这样的问题。
controller中的方法被AOP进行了代理,普通Controller如果没有AOP,private方法中bean也是正常的。
二、原因分析
因为没有AOP增强的private方法是正常的,所以我们可以联想到可能是因为创建了代理对象的原因导致的属性为空。
首先SpringAOP有两种实现方式,一种是Jdk动态代理,一种是Cglib动态代理。
这两种方式一种是通过对接口的实现,一种是通过创建子类重写,那么显然这两种方式都是无法代理私有方法的。
创建代理对象时会经过这么一段逻辑Enhancer#generateClass -> Enhancer#getMethods -> CollectionUtils.filter(methods, new VisibilityPredicate(superclass, true)) -> VisibilityPredicate#evaluate
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()))); } }
可以看到其中将私有方法进行了过滤,即创建的代理对象中并不会增强private方法
Spring中使用@Aspect注解会注册一个后置处理器,在Bean初始化时判断是否需要创建代理(主要逻辑在wrapIfNecessary方法中)。而我们都知道Bean在属性赋值时便将属性的依赖都注入了,所以此时的Bean中service层的bean是完成填充了的。
那为什么会出现调用private方法空指针异常呢?
这是因为为该类创建的代理并没有完成bean的生命周期,所以其中的属性是null。private方法并没有被真正的代理类拦截(如前面所说被过滤了),因此private方法无法获取被代理的对象,所以使用的是代理对象去调用的方法,而代理对象是由Cglib创建的并没有注入bean对象,所以出现了空指针异常。
而当调用被增强了的方法(即在代理类中重写了的方法)时,其实传入的并非代理的实例对象,而是target,即被代理的Bean的实例对象,所以才能取得service层的bean。
private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable { @Override @Nullable public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 省略... target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; // Check whether we only have one InvokerInterceptor: that is, // no real advice, but just reflective invocation of the target. if (chain.isEmpty() && CglibMethodInvocation.isMethodProxyCompatible(method)) { // We can skip creating a MethodInvocation: just invoke the target directly. // Note that the final invoker must be an InvokerInterceptor, so we know // it does nothing but a reflective operation on the target, and no hot // swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = invokeMethod(target, method, argsToUse, methodProxy); } else { // We need to create a method invocation... retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed(); } retVal = processReturnType(proxy, target, method, retVal); return retVal; // 省略... } } static boolean isMethodProxyCompatible(Method method) { return (Modifier.isPublic(method.getModifiers()) && method.getDeclaringClass() != Object.class && !AopUtils.isEqualsMethod(method) && !AopUtils.isHashCodeMethod(method) && !AopUtils.isToStringMethod(method)); }
从注释也可以看出,当调用public方法时“just reflective invocation of the target“,即只是对目标的反射调用