jpa调用远程代理类的hashcode方法导致无法初始化的问题

作者 胡萝虎 日期 2021-02-25
jpa调用远程代理类的hashcode方法导致无法初始化的问题

新年上班,开始新的搬砖之旅。这天需要将一个系统从Springboot 1.6.5升级到2.1.x版本,于是照着网上的一些教程改了一些配置,修改了一些类,然后点击Run,结果一顿操作猛如虎,一Run就GG~~

问题现场

直接上异常:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'abcApiService' defined in class path resource [com/xxx/ag/backend/BackendBizBeanConfiguration.class]: Unexpected exception during bean creation; nested exception is java.lang.reflect.UndeclaredThrowableException
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:529)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:322)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:207)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.resolveBeanByName(AbstractAutowireCapableBeanFactory.java:453)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.autowireResource(CommonAnnotationBeanPostProcessor.java:527)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.getResource(CommonAnnotationBeanPostProcessor.java:497)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor$ResourceElement.getResourceToInject(CommonAnnotationBeanPostProcessor.java:650)
at org.springframework.beans.factory.annotation.InjectionMetadata$InjectedElement.inject(InjectionMetadata.java:228)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119)
at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessProperties(CommonAnnotationBeanPostProcessor.java:318)
... 17 common frames omitted
Caused by: java.lang.reflect.UndeclaredThrowableException: null
at com.sun.proxy.$Proxy145.hashCode(Unknown Source)
at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936)
at java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:964)
at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.requiresDestruction(PersistenceAnnotationBeanPostProcessor.java:392)
at org.springframework.beans.factory.support.DisposableBeanAdapter.hasApplicableProcessors(DisposableBeanAdapter.java:405)
at org.springframework.beans.factory.support.AbstractBeanFactory.requiresDestruction(AbstractBeanFactory.java:1856)
at org.springframework.beans.factory.support.AbstractBeanFactory.registerDisposableBeanIfNecessary(AbstractBeanFactory.java:1873)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:635)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
... 28 common frames omitted
Caused by: java.util.concurrent.ExecutionException: com.xxx.ag.nrpc.client.NrpcInvocationException: MethodInfo not found
at com.google.common.util.concurrent.AbstractFuture.getDoneValue(AbstractFuture.java:503)
at com.google.common.util.concurrent.AbstractFuture.get(AbstractFuture.java:404)
at com.google.common.util.concurrent.AbstractFuture$TrustedFuture.get(AbstractFuture.java:86)
at com.xxx.ag.nrpc.client.NrpcClient.invokeMethod(NrpcClient.java:129)
at com.xxx.ag.nrpc.spring.NrpcInvocationHandler.invoke(NrpcInvocationHandler.java:53)
... 37 common frames omitted
Caused by: com.xxx.ag.nrpc.client.NrpcInvocationException: MethodInfo not found
at com.xxx.ag.nrpc.client.NrpcClient.lambda$internalInvoke$2(NrpcClient.java:195)
at com.google.common.util.concurrent.AbstractTransformFuture$TransformFuture.doTransform(AbstractTransformFuture.java:239)
at com.google.common.util.concurrent.AbstractTransformFuture$TransformFuture.doTransform(AbstractTransformFuture.java:229)
at com.google.common.util.concurrent.AbstractTransformFuture.run(AbstractTransformFuture.java:130)
at com.google.common.util.concurrent.MoreExecutors$DirectExecutor.execute(MoreExecutors.java:399)
at com.google.common.util.concurrent.AbstractFuture.executeListener(AbstractFuture.java:911)
at com.google.common.util.concurrent.AbstractFuture.complete(AbstractFuture.java:822)
at com.google.common.util.concurrent.AbstractFuture.set(AbstractFuture.java:664)
at io.grpc.stub.ClientCalls$GrpcFuture.set(ClientCalls.java:446)
at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:425)
at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:419)
at io.grpc.internal.ClientCallImpl.access$100(ClientCallImpl.java:60)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:493)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$500(ClientCallImpl.java:422)
at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:525)
at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:102)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:748)

啥情况,为啥应用启动后立即就调用原创接口的方法?而且还是没有注册的方法,所以才导致Bean创建失败。异常堆栈中没有显示出具体调用的远程方法,那就DEBUG一下看看,将断点打在RPC框架客户端的invoke方法上:

image-20210225210151284

执行到断点,可以看到,是Spring JPA中的代码在初始化时调用了远程接口的hashcode方法,而这种Object方法在远程是服务中是不会注册的,所以就会导致客户端调用失败,进而引起客户端的Bean创建失败。

image-20210225210519140

解决方案

出现上面问题的原因很简单,就是JPA在初始化bean的时候,把远程服务接口也初始化了,但是这个服务接口是一个动态代理类,调用它的方法就会直接通过RPC框架调用远程方法,进而导致失败。

解决的方法很简单,既然JPA会初始化远程服务接口,那就不让他这么做就行了。

  1. 首先重写bean的加载顺序,创建一个新类。当一个bean是代理类且是接口时,就直接不进行初始化。
/**
* @author xxx
* @date 2021/2/23 20:30
*/
public class RpcPersistenceAnnotationBeanPostProcessor extends PersistenceAnnotationBeanPostProcessor {
@Override
public boolean requiresDestruction(Object bean) {
Class<?> clazz = bean.getClass();
//是代理接口类
if (Proxy.isProxyClass(clazz) && clazz.getInterfaces().length == 1) {
return false;
}
return super.requiresDestruction(bean);
}
}
  1. 第二步,将RpcPersistenceAnnotationBeanPostProcessor类设置为默认的PersistenceAnnotationBeanPostProcessor bean,并且将bean的名称也设置成Spring中默认实现类的名称,进行覆盖
@Bean(AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)
@Primary
public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor(){
return new RpcPersistenceAnnotationBeanPostProcessor();
}
  1. 第三步,重写BeanPostProcessor,如果发现spring容器开始初始化默认的PersistenceAnnotationBeanPostProcessor ,那就把我们上面自定义的bean先初始化,从而达到优先初始化的目的
@Component
public class RpcBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements BeanFactoryAware {
private ConfigurableListableBeanFactory beanFactory;

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
throw new IllegalArgumentException("AutowiredAnnotationBeanPostProcessor requires a ConfigurableListableBeanFactory: " + beanFactory);
}
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}

@Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if (AnnotationConfigUtils.PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME.equals(beanName)) {
beanFactory.getBean(RpcPersistenceAnnotationBeanPostProcessor.class);
}
return null;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return super.postProcessBeforeInitialization(bean, beanName);
}
}
  1. 最后,还需要在配置文件增加一个配置,即允许覆盖相同名称的bean
spring.main.allow-bean-definition-overriding=true

OK,到这里就修改完成了,把应用Run起来,顺利启动。

image-20210225212807565

“扫一扫接着看”