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