IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    诺多发表于 2024-03-12 13:10:07
    love 0

    SpringBoot

    新年上班,开始新的搬砖之旅。这天需要将一个系统从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方法上:

    SpringBoot

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

    SpringBoot

    🛠️ 解决方案

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

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

    1. 首先重写bean的加载顺序,创建一个新类。当一个bean是代理类且是接口时,就直接不进行初始化。
    /**
     * @author xxx
     */
    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起来,顺利启动。

    SpringBoot

    原创不易,如果觉得此文对你有帮助,不妨点赞+收藏+关注,你的鼓励是我持续创作的动力!

    高等精灵实验室



沪ICP备19023445号-2号
友情链接