背景
- spring 版本:3.2.8.RELEASE
- JDK版本:1.8
- 本地是正常,线上环境是有问题的
应用从云下迁移到云上的过程中出现了一个应用部分aop 通知失效的问题,场景如下:
- node1 节点上的category 是失效的,element是正常的
- node2 节点上aop都是正常
从上面我们判断是节点导致的,然后我们给节点添加亲和性标签;所有的pod都部署到这个节点上,pod全部部署到这个节点上后spring aop竟然都正常了。
难道是节点的问题?
spring 是Java层面的东西,中间还隔了一层JVM用来屏蔽不同操作系统的影响;我判断肯定是应用代码中有什么配置影响了spring aop,毕竟是07年的老项目了。
我们首先分析一下 spring aop的流程。
spring aop流程
spring xml配置
下面是spring aop的配置,依赖于org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator对象来生产代理对象。
<bean id="myAfterAdvisor" class="com.xbin.aop.MyAfterAdvisor" lazy-init="false" >
<property name="category" ref="category"></property>
</bean>
<bean id="nameMatchMethodPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor" >
<property name="mappedNames">
<list>
<value>test</value>
<value>insert</value>
<value>update</value>
</list>
</property>
<property name="order" value="99"></property>
<property name="advice">
<ref bean="myAfterAdvisor"></ref>
<!-- <ref ="myAfterAdvisor"></ref>-->
</property>
</bean>
<bean id="beanNameAutoProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>nameMatchMethodPointcutAdvisor</value>
</list>
</property>
<property name="order" value="99"></property>
<property name="beanNames">
<list>
<value>element</value>
<value>category</value>
</list>
</property>
</bean>
spring 代理对象创建的流程
我们了解到spring aop功能的实现依赖于代理对象的创建,代理类实现了接口所以使用的是JDK的动态代理。
对应JDK的动态代理来说最重要的是InvocationHandler接口。
- 创建代理对象
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
Class[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised);
findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
- InvocationHandler的实现
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodInvocation invocation;
Object oldProxy = null;
boolean setProxyContext = false;
TargetSource targetSource = this.advised.targetSource;
Class targetClass = null;
Object target = null;
try {
if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
// The target does not implement the equals(Object) method itself.
return equals(args[0]);
}
if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
// The target does not implement the hashCode() method itself.
return hashCode();
}
if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
method.getDeclaringClass().isAssignableFrom(Advised.class)) {
// Service invocations on ProxyConfig with the proxy config...
return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
}
Object retVal;
if (this.advised.exposeProxy) {
// Make invocation available if necessary.
oldProxy = AopContext.setCurrentProxy(proxy);
setProxyContext = true;
}
// May be null. Get as late as possible to minimize the time we "own" the target,
// in case it comes from a pool.
target = targetSource.getTarget();
if (target != null) {
targetClass = target.getClass();
}
// Get the interception chain for this method.
List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
// Check whether we have any advice. If we don't, we can fallback on direct
// reflective invocation of the target, and avoid creating a MethodInvocation.
if (chain.isEmpty()) {
// 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.
retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args);
}
else {
// We need to create a method invocation...
invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
// Proceed to the joinpoint through the interceptor chain.
retVal = invocation.proceed();
}
// Massage return value if necessary.
Class<?> returnType = method.getReturnType();
if (retVal != null && retVal == target && returnType.isInstance(proxy) &&
!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
// Special case: it returned "this" and the return type of the method
// is type-compatible. Note that we can't help if the target sets
// a reference to itself in another returned object.
retVal = proxy;
} else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method);
}
return retVal;
}
finally {
if (target != null && !targetSource.isStatic()) {
// Must have come from TargetSource.
targetSource.releaseTarget(target);
}
if (setProxyContext) {
// Restore old proxy.
AopContext.setCurrentProxy(oldProxy);
}
}
}
大体流程如上,至于 spring aop 中前置通知、后置通知、环绕通知等是如果调用的这边就不进行分析了;我们回归解决问题的本质上。
问题分析
代理对象是否生成
我们都知道spring aop的能力都是通过代理对象来实现了,我们直接输出日志查看对象信息。发现类名有Proxy说明是生成是由JDK的动态代理生成的对象。
代理对象中通知是否可以获取到
代理对象已经生成,接下来看看调用代理对象时候相关的通知是否可以获取到。
JdkDynamicAopProxy的invoke方法里面需要获取到通知处理的 chain。
线上环境这里我们借助阿里的arthas线上调试工具进行排查。
arthas地址:https://arthas.aliyun.com/doc/
我们来监控:org.springframework.aop.framework.AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice 这个方法。
watch org.springframework.aop.framework.JdkDynamicAopProxy invoke -x 2
-
正常的bean
-
异常的bean
通过上述分析可以发现异常bean没有获取通知相关的拦截器。
源码分析
getInterceptorsAndDynamicInterceptionAdvice
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
MethodCacheKey cacheKey = new MethodCacheKey(method);
List<Object> cached = this.methodCache.get(cacheKey);
if (cached == null) {
cached = this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
this, method, targetClass);
this.methodCache.put(cacheKey, cached);
}
return cached;
}
getInterceptorsAndDynamicInterceptionAdvice
public List<Object> getInterceptorsAndDynamicInterceptionAdvice(
Advised config, Method method, Class targetClass) {
// This is somewhat tricky... we have to process introductions first,
// but we need to preserve order in the ultimate list.
List<Object> interceptorList = new ArrayList<Object>(config.getAdvisors().length);
boolean hasIntroductions = hasMatchingIntroductions(config, targetClass);
AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
// 如果config.getAdvisors获取的对象是空的,那么获取的 list也是空的
for (Advisor advisor : config.getAdvisors()) {
if (advisor instanceof PointcutAdvisor) {
// Add it conditionally.
PointcutAdvisor pointcutAdvisor = (PointcutAdvisor) advisor;
if (config.isPreFiltered() || pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
MethodInterceptor[] interceptors = registry.getInterceptors(advisor);
MethodMatcher mm = pointcutAdvisor.getPointcut().getMethodMatcher();
if (MethodMatchers.matches(mm, method, targetClass, hasIntroductions)) {
if (mm.isRuntime()) {
// Creating a new object instance in the getInterceptors() method
// isn't a problem as we normally cache created chains.
for (MethodInterceptor interceptor : interceptors) {
interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptor, mm));
}
}
else {
interceptorList.addAll(Arrays.asList(interceptors));
}
}
}
}
else if (advisor instanceof IntroductionAdvisor) {
IntroductionAdvisor ia = (IntroductionAdvisor) advisor;
if (config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
else {
Interceptor[] interceptors = registry.getInterceptors(advisor);
interceptorList.addAll(Arrays.asList(interceptors));
}
}
return interceptorList;
}
结合上面2中图,可以分析得出 advisorArray中的内容是空的,那么接下来我们需要分析这个数组为啥是空。
这个对象是在spring aop 生成代理对象的时候进行赋值的。
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#postProcessAfterInitialization
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.containsKey(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#wrapIfNecessary
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
if (beanName != null && this.targetSourcedBeans.containsKey(beanName)) {
return bean;
}
if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
return bean;
}
if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
// Create proxy if we have advice.
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;
}
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#createProxy
protected Object createProxy(
Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {
ProxyFactory proxyFactory = new ProxyFactory();
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
proxyFactory.copyFrom(this);
if (!shouldProxyTargetClass(beanClass, beanName)) {
// Must allow for introductions; can't just set interfaces to
// the target's interfaces only.
Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, this.proxyClassLoader);
for (Class<?> targetInterface : targetInterfaces) {
proxyFactory.addInterface(targetInterface);
}
}
// 如果这里没有获取到 advisors ,那么advisorArray数组就是空的了
Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
}
proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory);
proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
}
return proxyFactory.getProxy(this.proxyClassLoader);
}
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#buildAdvisors
protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
// Handle prototypes correctly...
// resolveInterceptorNames 方法非常关键
Advisor[] commonInterceptors = resolveInterceptorNames();
List<Object> allInterceptors = new ArrayList<Object>();
if (specificInterceptors != null) {
allInterceptors.addAll(Arrays.asList(specificInterceptors));
if (commonInterceptors != null) {
if (this.applyCommonInterceptorsFirst) {
allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
}
else {
allInterceptors.addAll(Arrays.asList(commonInterceptors));
}
}
}
if (logger.isDebugEnabled()) {
int nrOfCommonInterceptors = (commonInterceptors != null ? commonInterceptors.length : 0);
int nrOfSpecificInterceptors = (specificInterceptors != null ? specificInterceptors.length : 0);
logger.debug("Creating implicit proxy for bean '" + beanName + "' with " + nrOfCommonInterceptors +
" common interceptors and " + nrOfSpecificInterceptors + " specific interceptors");
}
Advisor[] advisors = new Advisor[allInterceptors.size()];
for (int i = 0; i < allInterceptors.size(); i++) {
advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
}
return advisors;
}
org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator#resolveInterceptorNames
private Advisor[] resolveInterceptorNames() {
ConfigurableBeanFactory cbf = (this.beanFactory instanceof ConfigurableBeanFactory) ?
(ConfigurableBeanFactory) this.beanFactory : null;
List<Advisor> advisors = new ArrayList<Advisor>();
for (String beanName : this.interceptorNames) {
// 这里有一个逻辑非常关键, interceptorName如果正在创建中是不会添加到 advisors里面的,
// spring认为这不是一个完整的对象,直接对外使用会出现问题。
if (cbf == null || !cbf.isCurrentlyInCreation(beanName)) {
Object next = this.beanFactory.getBean(beanName);
advisors.add(this.advisorAdapterRegistry.wrap(next));
}
}
return advisors.toArray(new Advisor[advisors.size()]);
}
总结
原因总结
通过分析上述源码,那么什么场景下会出现这种问题?
spring 没有添加特殊配置的前提,存在上述依赖是会报
信息: Destroying singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@3796751b: defining beans [category,element,myAfterAdvisor,nameMatchMethodPointcutAdvisor,beanNameAutoProxyCreator]; root of factory hierarchy
Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'category': Bean with name 'category' has been injected into other beans [myAfterAdvisor] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:548)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:458)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:296)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:628)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:932)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:479)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at com.xbin.TestAopMain.main(TestAopMain.java:15)
Process finished with exit code 1
需要添加 lazy-init来解决多个代理对象之间的循环依赖。
- aop 生效场景下的输出。
- aop 失效场景下的输出:
就只改了myAfterAdvisor延迟初始化的就会导致 aop失效。
解决方案
- 通过配置延迟初始化bean来打断依赖创建对象。
- 修改应用代码不应该存在这种依赖关系。(推荐方案)