Spring 源码学习(七)扩展功能 下篇

大家好,我是本周的带班编辑 子悠(这个月来的早了点),本周由于轮班小哥懿在出差,所以就由我为大家排版并送出技术干货,大家可以在公众号后台回复“java”,获得作者 Java 知识体系/面试必看资料。


在前面的 Spring 源码学习系列的文章中,深入分析和学习了​​BeanFactoryPostProcessor​​,主体是​​BeanFactory​​的后处理器,这次来学习主体是​​Bean​​的后处理器:​​BeanPostProcessor​​。​定义:它也是Spring对外提供的接口,用来给用户扩展自定义的功能。执行的时机在bean实例化阶段前后喜马拉雅

本篇思路:

  1. BeanPostProcessor定义
  2. 如何使用
  3. 代码实现分析
  4. 介绍剩余系统运维工作内容的扩展功能其他业务收入

    前言

    与​​BeanFactoryPostProcessor​​不同的是,​​BeanFactoryPostProcessor​​的注册和执行都在同一个方法内,而​​BeanPostProc监听器essor​​分开两个方法,分为注册调用两个步骤。​常规的BeanFactory中是没有实现后处理器的自动注册,所以在调用其他综合收益的时候没有进行手动注册是无法使用的,但在ApplicationContext中添加了自动注册功能(在这个registerBeanPostProcessors方法中),最后在bean实例化时执行BeanPostProcessor对应的方法。​本次主要介绍​xml​BeanPostProcessor​​,同时也会将剩下的​​context​​扩展功能一起学习spring面试题~


    BeanPostProcessor

    经过上一篇文章的学习,应该对​​bean​​的后处理理解起来更顺利,下面直奔主题,来看下它是如何使用和结合源码分析

    如何使用

    新建一个 bean 后处理器

    这个后处springboot面试题理器需要引用​​InstantiationAwareBeanPostPro西门龙霆cessor​​接口(实际继承自​​BeanPostProcess系统运维工程师or​​),然后重载以下两个方法:

    public class CarBeanPostProcessor implements InstantiationAwareBeanPostProcessor {


    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    // 这里没有区分 bean 类型,只是用来测试打印的顺序和时间
    System.out.println("Bean name : " + beanName + ", before, time : " + System.currentTimeMillis());
    return null;
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("Bean name : " + beanName + ", after time : " + System.currentTimeMillis());
    return null;
    }
    }

    在配置文件中注册 bean-post-processor.xml

    在配置文件配置我们写的自spring面试题定义后处理器和两个普通​​bean​​,用来测试打印时间和顺序

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- beanPostProcessor -->
    <bean id="carPostProcessor" class="context.bean.CarBeanPostProcessor"/>


    <!--用以下两个 bean 进行测试打印时间和顺序-->
    <bean id="car" class="base.factory.bean.Car">
    <property name="price" value="10000"/>
    <property name="brand" value="奔驰"/>
    </bean>


    <bean id="book" class="domain.ComplexBook"/>


    </beans>

    启动代喜马拉雅山高度多少米码和打印结果

    public class CarBeanPostProcessorBootstrap {


    public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/bean-post-processor.xml");
    Car car = (Car) context.getBean("car");
    ComplexBook book = (ComplexBook) context.getBean("book");
    System.out.println(car);
    System.out.println(book);
    }
    }

    输出:

    Bean name : car, before Initialization, time : 1560772863996
    Bean name : car, after Initialization, time : 1560772863996
    Bean name : book, before Initialization, time : 1560772863999
    Bean name : book, after Initialization, time : 1560772863999
    Car{maxSpeed=0, brand='奔驰', price=10000.0}
    domain.ComplexBook@77be656f

    从输出接口看出,打印顺序是先框架内部,再到应用层,框架内部中,在顺序实例化每个​​b其他综合收益属于什么科目espring翻译an​​时,前面也提到​执行时机:先执行postProcessBeforeInitialization方法,然后实例化bean后,执行postProcessAfterInitialization。

    所以我们重载的两个接口按照前后顺序打印出来了~

    注册 BeanPostProcessor

    上面介绍了使用例子,应该不难理解,接着来看下源码注册的方法:

    org.springframework.context.support.AbstractApplic系统运维工程师ationContext#registerBe其他综合收益anPspring面试题ostProcessors

    实际委托给了​​PostProcessorRegistrationDelegate.regi系统运维工程师sterBeanPostProc许慕离essors(beanFactory, this);​

    public static void registerBeanPostProcessors(
    ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
    // 注释 7.2 从注册表中取出 class 类型为 BeanPostProcessor 的 bean 名称列表
    String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);


    int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
    beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
    // 将带有 权限顺序、顺序和其余的 beanPostProcessor 分开
    List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
    // 类型是 MergedBeanDefinitionPostProcessor
    List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
    List<String> orderedPostProcessorNames = new ArrayList<>();
    List<String> nonOrderedPostProcessorNames = new ArrayList<>();
    for (String ppName : postProcessorNames) {
    // 分类,添加到对应数组
    ...
    }
    // 首先,注册实现了 PriorityOrdered 接口的 bean 后处理器
    sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
    // 下一步,注册实现了 Ordered 接口的 bean 后处理器
    List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
    for (String ppName : orderedPostProcessorNames) {
    BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    orderedPostProcessors.add(pp);
    if (pp instanceof MergedBeanDefinitionPostProcessor) {
    internalPostProcessors.add(pp);
    }
    }
    sortPostProcessors(orderedPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, orderedPostProcessors);
    // 现在,注册常规 bean 后处理器,其实就是不带顺序
    List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
    for (String ppName : nonOrderedPostProcessorNames) {
    BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
    nonOrderedPostProcessors.add(pp);
    if (pp instanceof MergedBeanDefinitionPostProcessor) {
    internalPostProcessors.add(pp);
    }
    }
    registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
    // 最后,重新注册 MergedBeanDefinitionPostProcessor 类型的后处理器
    // 看起来是重复注册了,但是每次注册调用的底层方法都会先移除已存在的 beanPostProcessor,然后再加进去,最后还是保存唯一
    sortPostProcessors(internalPostProcessors, beanFactory);
    registerBeanPostProcessors(beanFactory, internalPostProcessors);
    // 添加 ApplicationContext 探测器
    beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
    }

    跟之前的​​Bespring翻译anFactoryPostProcessor​​处理是不是很相似,也是进行spring分类,将带有系统/运维权重顺序、顺序和普通​​BeanPostProces其他货币资金包括哪些内容sospringbootr​​添加到对应的列表后,然后排序,统一注册到​​beanPo其他综合收益属于什么科目stProcessors​​列表末尾。将​​BeanPostProcessor​​与之前的​​BeanFactoryPospringstProcessor​​进行对比后发现,少了硬编码注册的代码星门老鹰,只处理了配置文件方式的注册​​bean​​。通过书中阐释,对少了硬编码的处理有些理解:

    对于 BeanFactoryPostProcessor 的处理,在一个方法内实现了注册和实现,所以需要载入配置中的定义,并进行激活;而对于 BeanPostProcessor 并不需要马上调用,硬编码的方式实现的功能是将后处理器提取并调用,对于 B监听器eanPostProcessor,注册阶段不需要调其他垃圾用,所以没有考虑处理硬编码,在这里只需要将配置文件的 B喜马拉雅听书免费版官方下载eanPostProcessor 提取出来并注册进入 bean小米路由器Fspringbootactory 就可以了。

    而且我在测试过程,想在应用代码中进行硬编码注册,​发现由于Cla监听器ssPathXmlApplicationContext最后一个方法是实例化非延迟加载的bean,在上下文创建好时,BeanPostProcessor就已经执行完成凶猛领主了,于是硬编码注册的后处理器无法执行​,只能通过设定延迟加载或者在配置文件配置中进行注册,或者其它 Bean喜马拉雅山高度多少米Factory 能支springcloud持硬编码。剩下顺序​​Order​​类型的后处理器注册​​BeanFactoryPostProcessor​​类似就其他业务收入不重复多讲解了,这段代码的逻辑挺清晰的~


    小结

    结束两个扩展功能,​​BeanFactoryPostProcessor​​和​​BeanPostProcessor​​的学习使用后,还有其它的扩展功能没学习到,在一开始基础机构篇就提到剩下的方法:

    这这些扩展功能中,个人感觉事件传播器、监听器和发送广播事件这三个会用得比较凶猛领主多,所以下面的内容会花比较大篇幅讲这三个扩展。


    初始化消息资源

    根据书中的内容介绍,这个消息资源​​messageSource​​是跟​​Spring​​国际化相关。小米路由器

    例如中美之间的中英文差别,在不同地区显示不同的资源。对于有国际化需求的系统,要为每种提供一套相应的资源文件,并以规范化命名的形式保存在特定的目录中,由系统自动根据客户端的语言或者配置选择合适的资源文件。

    举个????:定义了两个资源文件,简单配置如下

    • 中文地区:test=测试
    • 英文地区:test=test

      所以可以通过Applicationcontext.getMessage()方法访问国际化信息,在不同的环境中获取对应的数据。​由于个人感系统运维工作内容觉这种配置相关的西门龙霆,可以通过其他应付款​profile​​切换来实现其他应收款,所以没有去细看和使用,具体实现和使用请感兴趣的同学们深spring是什么意思入了解吧。


      事件监听

      事件传播器的使用很像我们设计模式中的观察者模式,被观察者变动后通知观察者进行相应的逻辑处理。

      在了解​spring框架Spring​​如何初始化事件传播器之前,来看下​​Spring​​监听的简单用法。

      定义监听事件 Event

      新建一个类,继承于​​ApplicationEvent​​,并且需要在构造方法中调用父类的构造函数​​supre(source)​​:

      public class CarEvent extends ApplicationEvent {


      /**
      * 自定义一个消息
      */
      private String msg;


      public CarEvent(Object source) {
      super(source);
      }


      public CarEvent(Object source, String msg) {
      super(source);
      this.msg = msg;
      }
      }

      定义监听器 Listener

      新建一个类,引用​​ApplicationListener​​接口,然后重载​​onApplicationEvent​​方法:

      public class CarEventListener implements ApplicationListener {
      @Override
      public void onApplicationEvent(ApplicationEvent event) {
      if (event instanceof CarEvent) {
      CarEvent carEvent = (CarEvent) event;
      System.out.println("source : " + event.getSource() + ", custom message : " + carEvent.getMsg());
      }
      }
      }

      由于​​Spring​​的消息监听器不像​​kafka​​等其他垃圾主流​​MQ​​可以指定发送队列或者监听主题,只要发送消息后,所有注册的监听器都会收到消息进行处理,所以这边加了一个判断,如喜马拉雅听书免费版官方下载果是我业务上需要的消息,才会进行处理。


      配置文件

        <bean id="testListener" class="context.event.CarEventListener"/>

        将刚才写的监听器注册到​​Spring​​容器中


        测试代码

        public class EventBootstrap {
        public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("factory.bean/bean-post-processor.xml");
        // 第一个参数是来源,第二个参数是自定义
        CarEvent carEvent = new CarEvent("hello", "world");
        context.publishEvent(carEvent);
        // 消息发送之后,打印以下内容
        // source : hello, custom message : world
        }
        }

        由于在配置其他应付款文件中注册了监听器,然后在启动代码汇总初始化了监听事件其他综合收益属于什么科目,最终通过​​context​​发送消息,发现输出结果与预想的一致。这种观察者模式实现很经典,使用起来也很简单,下面来结合源码分析一spring框架下​​Spring​​是如何实现消息监听的功能。


        消息监听代码分析

        从源码中分析,发现主要是下面三个步骤:

        初始化 ApplicationEvenMulticaster

        protected void initApplicationEventMulticaster() {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        // 如有有自己注册class Name 是 applicationEventMulticaster,使用自定义广播器
        if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
        this.applicationEventMulticaster =
        beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
        }
        }
        else {
        // 没有自定义,使用默认的事件广播器
        this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
        beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
        }
        }

        广播器的作用是用来广播消息,在默认的广播器​​SimpleApplicationEventMulspring翻译ticaster​​类中发现了这个其他应收款是什么科目方法​​multicastEvent​​:

        public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        Executor executor = getTaskExecutor();
        // 遍历注册的消息监听器
        for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
        if (executor != null) {
        executor.execute(() -> invokeListener(listener, event));
        }
        else {
        invokeListener(listener, event);
        }
        }
        }

        可以看到,在广播事件时,会遍历所有注册的监听器进行调用invokeListener方法,底层调用的是监听器重载的listenerspring是什么意思.onApplicationEvent(event),所以再次强小米路由器登录入口调一次,如果使用Spring自带的事件监听,请在业务处理方判断事件来源,避免处理其他和其它的区别错误。


        注册监听器

        在上一步中,已经初始化好了广播器,所以下一步来看下,监听器的注册流程,入口方其他垃圾法如下:

        org.系统运维工作内容springspringcloud五大组件framework.context.supp许慕离ort.AbstractApplicationC监听器ontext#registerspring是什么意思Listeners

        protected void registerListeners() {
        // 这里是硬编码注册的监听器
        for (ApplicationListener<?> listener : getApplicationListeners()) {
        getApplicationEventMulticaster().addApplicationListener(listener);
        }
        // 不要在这里初始化 factoryBean : 我们需要保留所有常规 bean 未初始化,以便让后处理程序应用于它们!
        // 这一步是配置文件中注册的监听器
        String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
        for (String listenerBeanName : listenerBeanNames) {
        getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
        }


        // 发布早期的应用程序事件,现在我们终于有了一个多播器=-=
        Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
        this.earlyApplicationEvents = null;
        if (earlyEventsToProcess != null) {
        for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
        getApplicationEventMulticaster().multicastEvent(earlyEvent);
        }
        }
        }

        这一个方法代码不多,也没啥嵌套功能,按照注释顺序将流程梳理了一遍,将我小米路由器们注册的监听器加入到​​applic厦门理工学院ationEventMulticaster​​列表中,其他垃圾等待之后调用。


        publishEvent

        广播器和监听器都准备好了,剩下的就是发送事件,通知监听器做相应的处理:

        org.springframework.星门老鹰context.support.AbstractApplicat其他应付款ionContext#publishEvent(java.lang.Object,org.springframework.core.ResolvableType)

        核心是这行代码:

          getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);

          通过获取事件广播器,调用​​multicastEvent​​方法,进行广播事件,这一步前面也介绍过了,不再细说。


          总结

          这次学习,省略了书中的一些内容,有关属性编辑器、​​SPEL​​语言和初始化非延迟加载等内容,请感兴趣的同学继续深入了解~​我们也能从Sprin监听器g提供的这些扩展功能中学习到,通过预留后处理器,可以在bean实例化之前修改配置信息,或者做其他的自定义操作spring,例如替换占位符、过滤敏感信息等;

          也可以通过广播事件,定义事件和监听器,springboot面试题在监听器中实现业务逻辑,由于不是直接调用监听器,而是通过事件广播器进行中转,达到了代码解耦的效果其他垃圾

          所以在之后的代码设计和编写中,在整体设计上,有必要的话,考虑在更高的抽象层要预留扩展功能,然后让子类重载或者实现,实现扩厦门理工学院展的功能。


          由于个人技术有限,其他如果有理解不到位或者错误的地方,请留spring下评论,我会根据朋友们的建议进行修正

          代码和注释都在里面,小伙伴们可以下载我上传的代码,亲测可运行~

          Gitee 地址:springboothttps://gitee.com/vip-augus/s其他货币资金pring-analysis-note.git

          Github 地址:https://github.com/Vip-Augus/spring-analysis-note

          参考资料

          1. Spring 源码深度解析 / 郝佳编著. – 北京 : 人民邮电出版社系统运维工程师

          Java 极客技术公众号,是其他由一群热爱 Java 开发的技术人组建成立,专注分享原创、高质量的 Java 文章。

          
                                            Spring 源码学习(七)扩展功能 下篇