问题描述
公司项目中自己开发了一个简易的事件服务,使用自定义注解。
实现方式:
实现BeanFactoryPostProcessor
,在postProcessBeanFactory
中,扫描所有的bean方法,将带有自定义注解EventListener
的方法放到map中,然后可以使用服务来fire相关的方法,非常方便。
以前使用没有问题,这次写了一个方法,与以前不同的唯一一点就是可见性由public
改为了private
,因为这个方法在设计的时候就只打算在类内部使用。可是在使用的时候,连第一行的日志都打印不出来,改为public
后调用正常。
调试
打断点进入相关方法,发现事件服务是采用这种方式来调用相关方法的:
- 通过
ApplicationContextHolder
在spring启动时保存ApplicationContext
,然后使用方法getBean
来根据map中的beanName来获取bean - 使用反射获取相应的方法
- 调用方法invoke
可以看到,反射调用是没什么问题的,按照我们平常使用反射的经验来看,也完全不应该出问题。进入到调用的方法,发现类中的实例属性全部为null,第一行的打印log的log实例为空,直接报了NPE(由于项目原因,这个异常的栈也没有打印出来),所以在日志中连入口日志都没有。
分析
由于以前也读过Spring的源码,猜测应该是Spring使用cglib动态代理的类有问题,对于public
和private
方法的调用方式可能不同。这个类使用了事务,因此我去Spring中找到动态代理部分:
public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug("Creating CGLIB proxy: target source is " + this.advised.getTargetSource()); } try { Class rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy"); Class proxySuperClass = rootClass; if (ClassUtils.isCglibProxyClass(rootClass)) { proxySuperClass = rootClass.getSuperclass(); Class [] additionalInterfaces = rootClass.getInterfaces(); for (Class additionalInterface : additionalInterfaces) { this.advised.addInterface(additionalInterface); } } // Validate the class, writing log messages as necessary. validateClassIfNecessary(proxySuperClass, classLoader); // Configure CGLIB Enhancer... 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 UndeclaredThrowableStrategy(UndeclaredThrowableException.class)); //获取方法调用时的拦截 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); }
方法的调用代理到了callback中,我们来看一下callback的生成
private Callback[] getCallbacks(Class rootClass) throws Exception { // Parameters used for optimisation choices... boolean exposeProxy = this.advised.isExposeProxy(); boolean isFrozen = this.advised.isFrozen(); boolean isStatic = this.advised.getTargetSource().isStatic(); // Choose an "aop" interceptor (used for AOP calls). Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised); // Choose a "straight to target" interceptor. (used for calls that are // unadvised but can return this). May be required to expose the proxy. Callback targetInterceptor; if (exposeProxy) { targetInterceptor = isStatic ? new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource()); } else { targetInterceptor = isStatic ? new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedInterceptor(this.advised.getTargetSource()); } // Choose a "direct to target" dispatcher (used for // unadvised calls to static targets that cannot return this). Callback targetDispatcher = isStatic ? new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp(); //callback组成元素 Callback[] mainCallbacks = new Callback[]{ aopInterceptor, // for normal advice targetInterceptor, // invoke target without considering advice, if optimized new SerializableNoOp(), // no override for methods mapped to this targetDispatcher, this.advisedDispatcher, new EqualsInterceptor(this.advised), new HashCodeInterceptor(this.advised) }; Callback[] callbacks; // If the target is a static one and the advice chain is frozen, // then we can make some optimisations by sending the AOP calls // direct to the target using the fixed chain for that method. if (isStatic && isFrozen) { Method[] methods = rootClass.getMethods(); Callback[] fixedCallbacks = new Callback[methods.length]; this.fixedInterceptorMap = new HashMap(methods.length); // TODO: small memory optimisation here (can skip creation for methods with no advice) for (int x = 0; x < methods.length; x++) { List
主要的callback在变量mainCallbacks
中,可以看到有aop
的拦截,也有Spring定义的equal
hashCode
方法拦截,也有直接调用回target
的拦截器,具体使用哪个,是由ProxyCallbackFilter
来决定的,accept
方法来决定执行哪个回调
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 = this.fixedInterceptorMap.get(key); 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, then // cannot use a dispatcher because the target cannot 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; } }}
可以看到,对于final
方法,不作拦截,如果定义了切面,就会使用切面的连接点来织入,其余的也会有对应的回调原则。
到这其实我们还是没有找到为何public
方法和private
方法调用的行为为什么是不一样的,我把cglib生成的类保存下来,使用反编译来看看生成的类是什么样的。
这里我只展示一下头部和其中的一个方法:
public class VideoAccessServiceImpl$$EnhancerBySpringCGLIB$$4d078710 extends VideoAccessServiceImpl implements SpringProxy, Advised, Factory{ public final boolean saveVideo(String paramString) { try { MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0; if (tmp4_1 == null) { tmp4_1; CGLIB$BIND_CALLBACKS(this); } MethodInterceptor tmp17_14 = this.CGLIB$CALLBACK_0; if (tmp17_14 != null) { Object tmp41_36 = tmp17_14.intercept(this, CGLIB$saveVideo$0$Method, new Object[] { paramString }, CGLIB$saveVideo$0$Proxy); tmp41_36; return tmp41_36 == null ? false : ((Boolean)tmp41_36).booleanValue(); } return super.saveVideo(paramString); } catch (RuntimeException|Error localRuntimeException) { throw localRuntimeException; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } }
然后搜索了一下私有方法在这里面是不存在的,也就是说cglib对于私有方法并不会代理,而对于公有方法则会调用相应的MethodInterceptor
到这我们已经找到了两者行为不一致的原因:
public
方法被Spring拦截,最终是使用相关的bean来调用的,private
方法则是跟我们平常使用反射一样,得到了原始的方法,里面的实例属性全都没有初始化,都为null。
总结
通过这次问题搜索,也发现了自己在以往读源码,学习新知识的时候挖掘的不够,以前也写过cglib相关的博客,现在来看也是有点草草,没有深入下去,经过这次的问题解决,让自己对cglib又多了一点理解。
在搜索源代码的时候,也发现了自己对Spring代码的陌生,以往看过的也忘得差不多了,以后还需要多多回顾。也会把相关的知识记录下来,以备日后查阅。