wsywpp个人博客

愿居于一城,与卿所见美好......

推荐文章

H2数据库使用教程-3- springboot四种控制h2启动 bean加载顺序的方案

    Spring 中如何控制2个bean中的初始化顺序?

    开发过程中有这样一个场景,需要集成H2数据库到springboot中,但是子模块中,存在连接池初始化的时候需要连接数据源,即H2数据库启动必须要在连接池初始化之前进行启动,需要控制二者的初始化顺序。实现方式可以有多种,本文结合目前对 Spring 的理解,尝试列出几种思路。

    场景

    假设A,B两个 bean 都需要在初始化的时候从本地磁盘读取文件,其中B加载的文件,依赖A中加载的全局配置文件中配置的路径,所以需要A先于B初始化,此外A中的配置改变后也需要触发B的重新加载逻辑,所以A,B需要注入彼此。
    对于下面的模型,问题简化为:我们需要initA()先于initB()得到执行。
    @Service
    public class A {
        @Autowired
        private B b;
    
        public A() {
            System.out.println("A construct");
        }
    
        @PostConstruct
        public void init() {
            initA();
        }
    
        private void initA() {
            System.out.println("A init");
        }
    }
    
    @Service
    public class B  {
        @Autowired
        private A a;
    
        public B() {
            System.out.println("B construct");
        }
    
        @PostConstruct
        public void init() {
            initB();
        }
    
        private void initB(){
            System.out.println("B init");
        }
    }

    方案一:立Flag

    我们可以在业务层自己控制A,B的初始化顺序,在A中设置一个“是否初始化的”标记,B初始化前检测A是否得以初始化,如果没有则调用A的初始化方法,所谓的check-and-act。对于上述模型,实现如下:

    @Service
    public class A {
    
        private static volatile boolean initialized;
    
        @Autowired
        private B b;
    
        public A() {
            System.out.println("A construct");
        }
    
        @PostConstruct
        public void init() {
            initA();
        }
    
        public boolean isInitialized() {
            return initialized;
        }
    
        public void initA() {
            if (!isInitialized()) {
                System.out.println("A init");
            }
            initialized = true;
        }
    }
    
    @Service
    public class B {
    
        @Autowired
        private A a;
    
    
        public B() {
            System.out.println("B construct");
        }
    
        @PostConstruct
        public void init() {
            initB();
        }
    
    
        private void initB() {
            if (!a.isInitialized()) {
                a.initA();
            }
            System.out.println("B init");
        }

    执行效果:

    A construct
    B construct
    A init
    B init
    这种立flag的方法好处是可以做到lazy initialization,但是如果类似逻辑很多的话代码中到处充斥着类似代码,不优雅,所以考虑是否框架本身就可以满足我们的需要。

    方案二:使用DependsOn


    Spring 中的 DependsOn 注解可以保证被依赖的bean先于当前bean被容器创建,但是如果不理解Spring中bean加载过程会对 DependsOn 有误解,自己也确实踩过坑。对于上述模型,如果在B上加上注解@DependsOn({"a"}),得到的执行结果是:、

    A construct
    B construct
    B init
    A init

    在这里问题的关键是:bean属性的注入是在初始化方法调用之前。

    // 代码位置:AbstractAutowireCapableBeanFactory.doCreateBean
    // 填充 bean 的各个属性,包括依赖注入
    populateBean(beanName, mbd, instanceWrapper);
    if (exposedObject != null) {
        // 调用初始化方法,如果是 InitializingBean 则先调用 afterPropertiesSet 然后调用自定义的init-method 方法
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }

    结合本例,发生的实际情况是,因为出现了循环依赖,A依赖B,加载B,B依赖A,所以得到了一个提前暴露的A,然后调用B的初始化方法,接着回到A的初始化方法。具体源码分析过程如下:


    ApplicationContext 在 refresh 过程中的最后会加载所有的 no-lazy 单例。

    本例中,先加载的bean A,最终通过无参构造器构造,然后,继续属性填充(populateBean),发现需要注入 bean B。所以转而加载 bean B(递归调用 getBean())。此时发现 bean B 需要 DependsOn("a"),在保存依赖关系(为了防止循环 depends)后,调用 getBean("a"),此时会得到提前暴露的 bean A ,所以继续 B 的加载,流程为: 初始化策略构造实例 -> 属性填充(同样会注入提前暴露的 bean A ) -> 调用初始化方法。

    // 代码位置:AbstractBeanFactory.doGetBean
    // Guarantee initialization of beans that the current bean depends on. 实例化依赖的 bean
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dep : dependsOn) {
                    if (isDependent(beanName, dep)) {
                        throw new BeanCreationException(mbd.getResourceDescription(),
                                beanName, "Circular depends-on relationship between '"
                                + beanName + "' and '" + dep + "'");
                    }
                    registerDependentBean(dep, beanName); // 缓存 bean 依赖的关系
                    getBean(dep);
                }
            }

    得到提前暴露的 bean A的过程为:

    此时此刻,bean A 的属性注入完成了, 返回到调用初始化方法,所以表现的行为是:构造A -> 构造B -> B初始化 -> A初始化。

    DependsOn只是保证的被依赖的bean先于当前bean被实例化,被创建,所以如果要采用这种方式实现bean初始化顺序的控制,那么可以把初始化逻辑放在构造函数中,但是复杂耗时的逻辑放在构造器中是不合适的,会影响系统启动速度。


    方案三:容器加载bean之前

    Spring 框架中很多地方都为我们提供了扩展点,很好的体现了开闭原则(OCP)。其中 BeanFactoryPostProcessor 可以允许我们在容器加载任何bean之前修改应用上下文中的BeanDefinition(从XML配置文件或者配置类中解析得到的bean信息,用于后续实例化bean)。

    1) 在本例中,就可以把A的初始化逻辑放在一个 BeanFactoryPostProcessor 中。

    @Component
    public class ABeanFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
            A.initA();
        }
    }

    执行效果:

    A init
    A construct
    B construct
    B init

    这种方式把A中的初始化逻辑放到了加载bean之前,很适合加载系统全局配置,但是这种方式中初始化逻辑不能依赖bean的状态。

    2) 第二种方案是使用ApplicationContextInitializer实现与使用,本项目就是最终采用的这种方案!!!!!!

    二、扩展实现方式

    2.1、编程方式

    先定义ApplicationContextInitializer,如下:

    package com.transsnet.palmpay.controller;
    
    import org.springframework.context.ApplicationContextInitializer;
    import org.springframework.context.ConfigurableApplicationContext;
    import org.springframework.core.annotation.Order;
    
    //@Order的value值越小->越早执行。注:在类上标注,不是方法上
    @Order(111)
    
    @Slf4j
    public class H2StarterInitializer implements ApplicationContextInitializer {

    private final static String portKey = "spring.h2.port";
    private final static String baseDirKey = "spring.h2.baseDir";
    private final static String dbKey = "spring.h2.db";

    @Override
    public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
    ConfigurableEnvironment environment = configurableApplicationContext.getEnvironment();
    String db = environment.getProperty(dbKey);
    String port = environment.getProperty(portKey);
    String h2Path = environment.getProperty(baseDirKey);

    H2Config.startServer(db, port);
    log.info("############# 哈哈,H2获得优先择偶权:{}", h2Path);
    }
    }

    启动类里手动增加initializer

    //fixed: 允许扫描Servlet原生注解
    @ServletComponentScan
    //fixed: druid-spring-boot-starter 会再次扫描数据源,将druid踢出公共pom文件或者添加到exclude中!!!
    @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
    @EnableScheduling
    @EnableAsync
    public class Application {
    public static void main(String[] args) {
    //注入扩展点
    SpringApplication springApplication = new SpringApplication(Application.class);
    springApplication.addInitializers(new H2StarterInitializer());

    springApplication.run(args);
    System.out.println("(♥◠‿◠)ノ゙ 启动成功 ლڡ`)\n");
    }
    }


    方案四:事件监听器的有序性

    Spring 中的 Ordered 也是一个很重要的组件,很多逻辑中都会判断对象是否实现了 Ordered 接口,如果实现了就会先进行排序操作。比如在事件发布的时候,对获取到的 ApplicationListener 会先进行排序。

    // 代码位置:AbstractApplicationEventMulticaster.ListenerRetriever.getApplicationListeners()
    public Collection<ApplicationListener<?>> getApplicationListeners() {
            LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>();
            for (ApplicationListener<?> listener : this.applicationListeners) {
                allListeners.add(listener);
            }
            if (!this.applicationListenerBeans.isEmpty()) {
                BeanFactory beanFactory = getBeanFactory();
                for (String listenerBeanName : this.applicationListenerBeans) {
                    try {
                        ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class);
                        if (this.preFiltered || !allListeners.contains(listener)) {
                            allListeners.add(listener);
                        }
                    } catch (NoSuchBeanDefinitionException ex) {
                        // Singleton listener instance (without backing bean definition) disappeared -
                        // probably in the middle of the destruction phase
                    }
                }
            }
            AnnotationAwareOrderComparator.sort(allListeners); // 排序
            return allListeners;
        }

    所以可以利用事件监听器在处理事件时的有序性,在应用上下文 refresh 完成后,分别实现A,B中对应的初始化逻辑。

    @Component
    public class ApplicationListenerA implements ApplicationListener<ApplicationContextEvent>, Ordered {
        @Override
        public void onApplicationEvent(ApplicationContextEvent event) {
            initA();
        }
    
        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE; // 比 ApplicationListenerB 优先级高
        }
    
        public static void initA() {
            System.out.println("A init");
        }
    }
    
    @Component
    public class ApplicationListenerB implements ApplicationListener<ApplicationContextEvent>, Ordered{
        @Override
        public void onApplicationEvent(ApplicationContextEvent event) {
            initB();
        }
    
        @Override
        public int getOrder() {
            return Ordered.HIGHEST_PRECEDENCE -1;
        }
    
        private void initB() {
            System.out.println("B init");
        }
    }

    执行效果:

    A construct
    B construct
    A init
    B init

    这种方式就是站在事件响应的角度,上下文加载完成后,先实现A逻辑,然后实现B逻辑。

    总结

    在平时的开发中使用的可能都是一个语言,一个框架的冰山一角,随着对语言,对框架的不断深入,你会发现更多的可能。本文只是基于目前对于 Spring 框架的理解做出的尝试,解决一个问题可能有多种方式,其中必然存在权衡选择,取决于对业务对技术的理解。

    转载地址

    https://zhuanlan.zhihu.com/p/42975038

    阅读全文>>

作者:wsywpp分类:【javaEE浏览(141评论(0

2021-11-29 14:53:22

springboot整合rocketmq-spring-boot-starter:2.10

    前言:

    这里有点坑,之前的springboot与rocketmq的依赖,maven中央仓库并没有收纳,所以要想使用,需要自己去自己通过源码install到本地仓库的。但这里我一直没有弄成功,我不死心,去maven官网一直查找,终于找到了.



    如图,maven中央仓库是在今年一月份才开始收纳springboot与rocketmq的依赖,到现在为止也只有三个版本,而且使用的人很少,所以接下来我们就开心的使用吧
    导入依赖:这里也坑啊,我只有导入2.0.3版本才可以测试成功

    #依赖
    <dependency>
        <groupId>org.apache.rocketmq</groupId>
        <artifactId>rocketmq-spring-boot-starter</artifactId>
        <version>2.0.3</version>
    </dependency>
    
    #配置文件,一定要写这两个
    #而且注意这里不要写为spring.rocketmq.producer.group,这个版本的写法不一样
    rocketmq.name-server=192.168.44.151:9877
    rocketmq.producer.group=shuai
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    
    **
     * 生产者
     * springboot整合rocketmq
     */
    @Component
    public class SpringProducer {
    
      @Autowired
      private RocketMQTemplate rocketMQTemplate;
    
      @Autowired
      private DefaultMQProducer defaultMQProducer;
    
      public void sendMsg(String topic,String msg){
    
        rocketMQTemplate.convertAndSend(topic,msg);
      }
    }
    
    #测试用例
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class TestSpringRocketMQ {
        @Autowired
        private SpringProducer springProducer;
        @Test
        public void testSendMsg(){
            String msg = "我的第1个SpringRocketMQ消息!";
            this.springProducer.sendMsg("spring-my-topic", msg);
            System.out.println("发送成功");
        }
    
    
    }
    阅读全文>>

作者:wsywpp分类:【javaEE浏览(217评论(0

2020-11-29 21:50:20