前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Bean方法反射调用行为不一致问题分析

Spring Bean方法反射调用行为不一致问题分析

作者头像
Dylan Liu
发布2019-07-01 13:13:24
8000
发布2019-07-01 13:13:24
举报
文章被收录于专栏:dylanliudylanliu

问题描述

公司项目中自己开发了一个简易的事件服务,使用自定义注解。

实现方式:

实现BeanFactoryPostProcessor,在postProcessBeanFactory中,扫描所有的bean方法,将带有自定义注解EventListener的方法放到map中,然后可以使用服务来fire相关的方法,非常方便。

以前使用没有问题,这次写了一个方法,与以前不同的唯一一点就是可见性由public改为了private,因为这个方法在设计的时候就只打算在类内部使用。可是在使用的时候,连第一行的日志都打印不出来,改为public后调用正常。

调试

打断点进入相关方法,发现事件服务是采用这种方式来调用相关方法的:

  1. 通过ApplicationContextHolder在spring启动时保存ApplicationContext,然后使用方法getBean来根据map中的beanName来获取bean
  2. 使用反射获取相应的方法
  3. 调用方法invoke

可以看到,反射调用是没什么问题的,按照我们平常使用反射的经验来看,也完全不应该出问题。进入到调用的方法,发现类中的实例属性全部为null,第一行的打印log的log实例为空,直接报了NPE(由于项目原因,这个异常的栈也没有打印出来),所以在日志中连入口日志都没有。

分析

由于以前也读过Spring的源码,猜测应该是Spring使用cglib动态代理的类有问题,对于publicprivate方法的调用方式可能不同。这个类使用了事务,因此我去Spring中找到动态代理部分:

代码语言:javascript
复制
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的生成

代码语言:javascript
复制
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<String, Integer>(methods.length);

    	// TODO: small memory optimisation here (can skip creation for methods with no advice)
    	for (int x = 0; x < methods.length; x++) {
    		List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(methods[x], rootClass);
    		fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
    				chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
    		this.fixedInterceptorMap.put(methods[x].toString(), x);
    	}

    	// Now copy both the callbacks from mainCallbacks
    	// and fixedCallbacks into the callbacks array.
    	callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
    	System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
    	System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
    	this.fixedInterceptorOffset = mainCallbacks.length;
    }
    else {
    	callbacks = mainCallbacks;
    }
    return callbacks;
}

主要的callback在变量mainCallbacks中,可以看到有aop的拦截,也有Spring定义的equal hashCode方法拦截,也有直接调用回target的拦截器,具体使用哪个,是由ProxyCallbackFilter来决定的,accept方法来决定执行哪个回调

代码语言:javascript
复制
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生成的类保存下来,使用反编译来看看生成的类是什么样的。

这里我只展示一下头部和其中的一个方法:

代码语言:javascript
复制
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代码的陌生,以往看过的也忘得差不多了,以后还需要多多回顾。也会把相关的知识记录下来,以备日后查阅。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 调试
  • 分析
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档