likes
comments
collection
share

不是吧,这八股文真能解决问题

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

1. 引言

Hi,你好,我是有清何谓八股,乃“空疏无用,实于政事无涉”,在我们面试中经常碰到一些八股问题,我们常常嗤之以鼻,这玩意,我可以背诵并默写,但是八股真正的“空疏无用”么,直到有一天,射出去的子弹击中了我......不是吧,这八股文真能解决问题

2. 背景

在一个风和日丽的下午,啪啪啪,十几个风险处理单打破了美好的摸鱼时光,中间件团队要求升级一个二方包,否则就直接断流掉不给我们的应用提供查询服务,属实霸道。但毕竟寄人二方包下,我们还是按照操作文档升级一波。

经过在一个应用上的严谨的测试与调试,发现该升级未影响到业务的使用,为了和团队小伙伴分享升级二方包的乐趣,我将风险处理单进行了分发,让大家一起升级。

结果,团队中的一位同学干活非常快,在半小时就在群里@我,你这个升级文档不行啊,查询的时候报了空指针出现了问题,首先不要慌,先把锅甩出去“不对啊,我升级的没问题”然后为了展示我们的责任感,我们需要“我来看看你的改动”

3. 大胆分析

小窗私聊了这个同学发生错误的应用,以及相关的 trace 信息观察堆栈,发现在 userService.getUser(id) 这一行发生了空指针认真核实这个同学的升级步骤,发现果然和我操作的升级步骤一摸一样,果然这个同学没有骗人,看一下这个 UserCenter 的代码

@Component(userCenter)
public class UserCenter{

    @Autowired
    private UserService userService;

    ...

    // 这一段是空指针的位置
    public UserDO getUser(Long id) {
        return userService.getUser(id);
    }
}

朴实无华的 userService 注入,朴实无华的 getUser 查询控制变量,问题肯定出现在这个二方包团队提供的包上,为了找出实锤,我们分析一波当前升级的版本和之前我们使用的版本差异在哪里最明显的差异在于,新增 UserSecurityProcessor 类,我们来看一下 UserSecurityProcessor 类代码

实际上的代码会比较复杂,这边简化一大版

public class UserSecurityProcessor implements BeanFactoryPostProcessor {

    private static final AtomicBoolean registed = new AtomicBoolean(false);

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        if (registed.compareAndSet(false, true)) {
            BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
            Map<String, BBean> beansOfType = beanFactory.getBeansOfType(UserService.class);
        }
    }
    
    ......
}

4. 八股论证

现场先勘查到目前为止,接下来来一波理论知识

4.1 Spring refresh 过程

不是吧,这八股文真能解决问题

  1. postProcessBeanFactory(beanFactory);

允许在上下文子类中对bean工厂进行后处理。这是一个扩展点,让开发者有机会在标准初始化之后,但在任何bean定义被加载之前,对BeanFactory进行自定义修改。

  1. invokeBeanFactoryPostProcessors(beanFactory);

调用在上下文中注册为bean的BeanFactoryPostProcessor。这一步允许所有的BeanFactoryPostProcessor对bean的定义进行修改之前初始化一些特别的bean设置,比如通过PropertyPlaceholderConfigurer解析属性文件。

  1. registerBeanPostProcessors(beanFactory);

注册那些拦截bean创建过程的BeanPostProcessor。这一步是注册所有的BeanPostProcessor实例到BeanFactory中,这些处理器会在bean初始化前后应用(如@Autowired注解处理)。

  1. initMessageSource();

初始化消息源,为这个上下文设置国际化能力,例如用于解析消息代码。

  1. initApplicationEventMulticaster();

初始化应用事件广播器,设置一个应用事件多播器,用于后续的事件发布。

  1. onRefresh();

初始化其他特殊bean,在具体的上下文子类中。这是一个扩展点,允许在特定的上下文实现中进行进一步的初始化。

  1. registerListeners();

检查监听器bean并注册它们。注册之前通过XML或者注解配置的应用事件监听器。

  1. finishBeanFactoryInitialization(beanFactory);

实例化所有剩余的(非懒加载的)单实例。这一步完成所有单实例的bean的创建工作,但不包括懒加载的bean。

  1. finishRefresh();

最后一步:发布相应的事件。这些事件包括ContextRefreshedEvent,告知相关监听者上下文刷新事件已经完成,以及如果web应用中定义了,则还包括RequestHandledEvent。

4.2 BeanFactoryPostProcessor 作用

先看一下原汁原味的注释不是吧,这八股文真能解决问题这个注释的的整体意思指的是 BeanFactoryPostProcessor 开放的目的是**允许开发者修改容器中的 BeanDefinition ,但是不允许在在此处实现 Bean 的实例化,**如果需要 hack 到 bean 的实例话过程,请考虑使用 BeanPostProcessor

5. 破案

来看一版简化版代码


@Component
public class TestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Map<String, BBean> beansOfType = beanFactory.getBeansOfType(BBean.class);
    }
}

@Component("bBean")
public class BBean {

    @Autowired
    private ABean aBean;

    public ABean getABean() {
        return aBean;
    }
}

此处 getABean 为空。那么经过上面两个八股复习,铁汁,你是否顿悟?@AutoWired起作用依赖于AutowiredAnnotationBeanPostProcessor。AutowiredAnnotationBeanPostProcessor 是 BeanPostProcessors 的实现。那 BeanPostProcessors在**registerBeanPostProcessors(beanFactory) **的时候被调用,在 postProcessBeanFactory 之后。也就是说BBean被触发提前初始化的时候,AutowiredAnnotationBeanPostProcessor还没有被注册自然也不会被执行到,自然 ABean=null。一图概之不是吧,这八股文真能解决问题

6.总结

总结一下,本文踩坑的点就在于,使用 BeanFactoryPostProcessor 的时候去实现了类的加载,当然这里再留下几个八股给大家

  • Bean 的加载顺序固定吗?
  • 当我想指定 Bean 的加载顺序有可能吗?

文章到这里结束啦,你的点赞评论收藏,是我持续更新的动力。

7. 闲言碎语

一个打工的下午,我说这个太阳好好看隔壁同事:这就是工作日吗

不是吧,这八股文真能解决问题

转载自:https://juejin.cn/post/7383992605548363827
评论
请登录