likes
comments
collection
share

如何在不修改原有类的情况下,对@PostConstruct的方法做try...catch

作者站长头像
站长
· 阅读数 16

事情是酱紫的

工作中有一个SpringBoot工程

里面有一个类,有个方法加了@PostConstruct,但是由于某些外部原因,这个方法会抛异常,因此本地服务起不起来,也没办法进行功能测试(比如这个RemoteServer.getConfig()抛异常)。

如何在不修改原有类的情况下,对@PostConstruct的方法做try...catch

一般最直接的办法,就是在这个初始化方法里面加个try...catch,测试,改bug,去掉try...catch,提交,push...

但是,这种做事方式有个问题,便是可能会由于粗心导致忘了去删try...catch,最后提交到线上去了

所以就有了题目里面的问题:不修改原始类的情况下,给init方法加个try...catch

先来结论

  1. Step1:在项目中加个包 gitignored
  2. step2:右键这个包,git->add to gitignore -> .gitignore。如此一来,这个包下面所有的类,都不会进入git,从而只会存在于本地(换句话说就是可以随便整,不用担心误伤项目代码)如何在不修改原有类的情况下,对@PostConstruct的方法做try...catch
  3. step3:最后一步,在上一步建的包下面,新建一个类
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class TryCatchInitMethods extends CommonAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        registry.getBeanDefinition(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME).setBeanClassName(getClass().getName());
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        
        if (!"xxxModule".equals(beanName)) {
            return super.postProcessBeforeInitialization(bean, beanName);
        }
        
        try {
            return super.postProcessBeforeInitialization(bean, beanName);
        } catch (Exception e) {
            e.printStackTrace();
            return bean;
        }
    }
}

走近科学

接下来是解密时刻,这其实是利用Spring生命周期的一个扩展,为了方便不了解Spring的同学理解,这里我画一个简略图,只包含相关的重点

Spring容器启动,其实是一个流程化的工作,也就是步骤1,步骤2,步骤3,...看似神秘,但多研究研究,熟悉之后,就会豁然开朗,然后感叹大神就是牛!

Spring简略流程如下:

如何在不修改原有类的情况下,对@PostConstruct的方法做try...catch

一些相关解释

  1. BeanDefinition:其实就是spring-bean的“图纸”,用来描述这个bean,是什么类型,有哪些初始化方法等等(信息很多,想了解可以直接看org.springframework.beans.factory.config.BeanDefinition)
  2. 切入点BeanDefinitionRegistryPostProcessor:这是个Spring自己提供的接口,实现它就可以人为的对已存在的BeanDefinition做一些操作
  3. 初始化CommonAnnotationBeanPostProcessor:这是Spring内置的一个处理器,会注册成SpringBean(这个bean的名字是个常量:AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME),每个bean在构过程中,都会交给一系列PostProcessor去执行一些流程化操作,其中CommonAnnotationBeanPostProcessor.postProcessBeforeInitialization这个方法就是负责调用@PostConstruct

关键时刻

我加的这个类TryCatchInitMethods,实现了BeanDefinitionRegistryPostProcessor,继承了CommonAnnotationBeanPostProcessor,所以它具备了修改BeanDefinition和执行bean初始化方法的能力。

  1. postProcessBeanDefinitionRegistry方法:负责修改BeanDefinition,在这里我把AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME这个bean的类型,设置成了TryCatchInitMethods,也就是用我的类,直接替换了Spring内置的那个CommonAnnotationBeanPostProcessor
  2. 既然已经替换了CommonAnnotationBeanPostProcessor,那么在执行初始化方法的时候,实际上是调用的TryCatchInitMethods.postProcessBeforeInitialization方法,在这里判断,如果Bean是XxxModule,就加一个try...catch

结尾

看起来花里胡哨,还解释了大半天,好像还没有直接在原始类里面写try...catch来的快

有点繁琐是真,但是好处就是,完全不用担心自己粗心导致事故(曾有同事把测试代码合到上线分支里面去了,所以对这方面尤为小心)

另外更重要的一点是,这个例子需要对spring生命周期有一定程度的理解,所以这也是强化知识理解的一种途径(在思考解决方案的过程中,我也回顾了一下Spring的源码,才找到办法,这种状态非常好,因为学习源码最有效的途径就是,遇到问题,然后去源码中寻找答案)。