如何在不修改原有类的情况下,对@PostConstruct的方法做try...catch
事情是酱紫的
工作中有一个SpringBoot工程
里面有一个类,有个方法加了@PostConstruct,但是由于某些外部原因,这个方法会抛异常,因此本地服务起不起来,也没办法进行功能测试(比如这个RemoteServer.getConfig()抛异常)。
一般最直接的办法,就是在这个初始化方法里面加个try...catch,测试,改bug,去掉try...catch
,提交,push...
但是,这种做事方式有个问题,便是可能会由于粗心导致忘了去删try...catch,最后提交到线上去了
所以就有了题目里面的问题:不修改原始类的情况下,给init方法加个try...catch
先来结论
- Step1:在项目中加个包 gitignored
- step2:右键这个包,git->add to gitignore -> .gitignore。如此一来,这个包下面所有的类,都不会进入git,从而只会存在于本地(换句话说就是可以随便整,不用担心误伤项目代码)
- 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简略流程如下:
一些相关解释
- BeanDefinition:其实就是spring-bean的“图纸”,用来描述这个bean,是什么类型,有哪些初始化方法等等(信息很多,想了解可以直接看org.springframework.beans.factory.config.BeanDefinition)
- 切入点BeanDefinitionRegistryPostProcessor:这是个Spring自己提供的接口,实现它就可以人为的对已存在的BeanDefinition做一些操作
- 初始化CommonAnnotationBeanPostProcessor:这是Spring内置的一个处理器,会注册成SpringBean(这个bean的名字是个常量:AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME),每个bean在构过程中,都会交给一系列PostProcessor去执行一些流程化操作,其中CommonAnnotationBeanPostProcessor.postProcessBeforeInitialization这个方法就是负责调用@PostConstruct
关键时刻
我加的这个类TryCatchInitMethods,实现了BeanDefinitionRegistryPostProcessor,继承了CommonAnnotationBeanPostProcessor,所以它具备了修改BeanDefinition和执行bean初始化方法的能力。
- postProcessBeanDefinitionRegistry方法:负责修改BeanDefinition,在这里我把AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME这个bean的类型,设置成了TryCatchInitMethods,也就是用我的类,直接替换了Spring内置的那个CommonAnnotationBeanPostProcessor
- 既然已经替换了CommonAnnotationBeanPostProcessor,那么在执行初始化方法的时候,实际上是调用的TryCatchInitMethods.postProcessBeforeInitialization方法,在这里判断,如果Bean是XxxModule,就加一个try...catch
结尾
看起来花里胡哨,还解释了大半天,好像还没有直接在原始类里面写try...catch来的快
有点繁琐是真,但是好处就是,完全不用担心自己粗心导致事故(曾有同事把测试代码合到上线分支里面去了,所以对这方面尤为小心)
另外更重要的一点是,这个例子需要对spring生命周期有一定程度的理解,所以这也是强化知识理解的一种途径(在思考解决方案的过程中,我也回顾了一下Spring的源码,才找到办法,这种状态非常好,因为学习源码最有效的途径就是,遇到问题,然后去源码中寻找答案)。
转载自:https://juejin.cn/post/7187763944663023671