• spring相关的问题和原因分析


    1、Bean的初始化顺序导致的项目启动失败

      现象:shua-video项目中引用了配置中台bp-config的SDK,然后在mq消息监听类中使用。如上使用方式,在waterService中引用了bp-config。在测试环境mq中没有消息消费时项目能正常启动,但在线上有消息消费时项目启动报错,提示找不到bp-config类。

    @Component
    @Slf4j
    public class BpUserAfterWechatRegisterConsumer {
    
        public static final String CONSUMER_GROUP = "after_register_for_shua_farm";
    
        @Resource(name = CONSUMER_GROUP)
        private RocketMQBaseConsumer bpUserAfterWecahtRegister;
    
        @Autowired
        private WaterService waterService;
    
        @PostConstruct
        public void init() {
            new Thread(() -> {
                try {
                    // {"createTime":1571904938893,"appId":3,"wechatId":4,"userId":1000000547}
                    bpUserAfterWecahtRegister.setConsumerGroup(CONSUMER_GROUP);
                    bpUserAfterWecahtRegister.setMessageModel(MessageModel.CLUSTERING);
                    bpUserAfterWecahtRegister.subscribe(UserMQConstants.TOPIC_USER_AFTER_WECHAT_REGISTER);
                    bpUserAfterWecahtRegister.registerMessageListener((msgs, context) -> {
                        for (MessageExt messageExt : msgs) {
                            try {
                              
                                //加新手引导水滴
                                waterService.addWaterForRegister(userId);
    
                            } catch (UnsupportedEncodingException e) {
                                log.error("消费消息出现异常", e);
                                continue;
                            }
                        }
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    });
                    bpUserAfterWecahtRegister.start();
                    log.info("bpUserAfterWecahtRegister 消费者启动完成");
                } catch (Exception e) {
                    log.error("bp-user-consumer 初始化失败", e);
                }
            }).start();
        }

      问题分析:怀疑是spring类的初始化顺序问题

    (1)首先看一下@PostConstruct注解的调用时机

      参考文章:https://www.cnblogs.com/lay2017/p/11735802.html

      发现@PostConstruct是在bean初始化时,由前置处理器processor.postProcessBeforeInitialization中通过反射去调用的,bean初始化是在DI注入之后进行的,而bean又是在使用时才首次从容器中getBean获取时才创建,所以没有消息时不会触发bp-config的创建,因此项目能正常启动。

    (2)查看一下bp-config类加载时机

      在classpath下的spring.factoris文件中定义如下:

    org.springframework.context.ApplicationListener=
    com.coohua.bp.config.api.spring.ConfigContainerBootListener
    
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
    com.coohua.bp.config.api.spring.BpConfigAutoConfig

      ConfigContainerBootListener如下:

    public class ConfigContainerBootListener implements ApplicationListener<ApplicationEvent> {
    
        @Override
        public void onApplicationEvent(ApplicationEvent applicationEvent) {
            if(applicationEvent instanceof ApplicationReadyEvent){
                GlobalConfigContainer globalConfigContainer = ConfigContainerHolder.getGlobalConfigContainer();
                globalConfigContainer.init();
                globalConfigContainer.start();
    
                StrategyConfigContainer strategyConfigContainer = ConfigContainerHolder.getStrategyConfigContainer();
                strategyConfigContainer.init();
                globalConfigContainer.start();
            }
        }
    }

      可以看到,bp-config中类的初始化是依赖于spring的ApplicationReadyEvent事件的。

    springboot支持的几类事件:

    1. ApplicationFailedEvent:该事件为spring boot启动失败时的操作

    2. ApplicationPreparedEvent:上下文context准备时触发

    3. ApplicationReadyEvent:上下文已经准备完毕的时候触发

    4. ApplicationStartedEvent:spring boot 启动监听类

    5. SpringApplicationEvent:获取SpringApplication

    6. ApplicationEnvironmentPreparedEvent:环境事先准备

    (3)看一下springBoot中的IOC容器初始化顺序

    public ConfigurableApplicationContext run(String... args) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            ConfigurableApplicationContext context = null;
            Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
            configureHeadlessProperty();
    //获取spring.factories中定义的监听器 SpringApplicationRunListeners listeners
    = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class, new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); //初始化IOC容器 refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this.logStartupInfo) { new StartupInfoLogger(this.mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;

      spring在refresh初始化IOC容器时,bp-config还未来得及初始化,所以此时在@PostConstruct中调用bp-config会有提示找不到。

    (4)联系配置中台开发同学,告诉此处依赖spring事件初始化的不合理之处

    (5)为了快速解决项目上线问题,本项目中采用如下方式解决

    @Service
    @Slf4j
    public class OrderPaySuccessConsumerForShuaVideo implements ApplicationListener<ApplicationReadyEvent>{
        public final static String ORDER_PAY_SUCCESS_FOR_SHUA_VIDEO = "order_pay_success_for_shua_video";
    
        @Resource(name = ORDER_PAY_SUCCESS_FOR_SHUA_VIDEO)
        private RocketMQBaseConsumer orderPaySuccessForShuaVideo;
    
        //@PostConstruct
        private void runConsumer() {
            log.info("init {} consumer", ORDER_PAY_SUCCESS_FOR_SHUA_VIDEO);
            try {
                orderPaySuccessForShuaVideo.subscribe(MallMQConstants.TOPIC_ORDER_PAY_SUCCESS);
                orderPaySuccessForShuaVideo.setConsumerGroup(ORDER_PAY_SUCCESS_FOR_SHUA_VIDEO);
                orderPaySuccessForShuaVideo.registerMessageListener(new MessageListenerConcurrently() {
                    @Override
                    public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                        for (MessageExt messageExt : list) {
                            try {
                                            userFriendService.handleDividendValidFriend(userId,true);
                                        }
                                    } catch (Exception e) {
                                        log.warn("解构失败: {}", orderInfo, e);
                                    }
                                }
                            } catch (Exception e) {
                                log.error("不支持的编码集。", e);
                            }
                        }
                        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
                    }
                });
                orderPaySuccessForShuaVideo.start();
            } catch (Exception e) {
                log.error("消费者: {} 启动异常!", ORDER_PAY_SUCCESS_FOR_SHUA_VIDEO, e);
            }
        }
    
        @Override
        public void onApplicationEvent(ApplicationReadyEvent event) {
            Executors.newSingleThreadExecutor().submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.SECONDS.sleep(30);
                    } catch (InterruptedException e) {
                        runConsumer();
                    }
                    runConsumer();
                }
            });
        }
    }

      方式就是在bp-config初始化完成后再进行消息消费,同样也监听ApplicationReadyEvent事件,上线后问题解决。

      这里有一个知识点,就是监听器的注册时机:

        *  bp-config中的监听器是在spring.factories中定义的,在springboot的run方法中通过getRunListeners()注册。

        *  类级别自定义的监听器是在refresh方法中通过registerListeners()注册的,所以前者先注册会先执行。

    2、spring如何解决循环依赖 

    spring是如何检查有没有循环依赖的:

      当类被创建时会将对应的beanName放入Set<String> singletonsCurrentlyInCreation集合中,如何发现要创建的对象在集合中已经存在,说明存在循环依赖。

    Spring中循环依赖场景有:

    (1)构造器的循环依赖

      这种循环依赖spring本身没法解决,只能通过代码层面:

        *  使用注解 @Lazy

    @Component
    public class CircularDependencyA {
     
        private CircularDependencyB circB;
     
        public CircularDependencyA(@Lazy CircularDependencyB circB) {
            this.circB = circB;
        }
    }

        使用延迟加载,在注入依赖时,先注入代理对象,在首次使用时再创建对象完成注入。

        *  使用Setter注入——官方建议

    @Component
    public class CircularDependencyA {
     
        private CircularDependencyB circB;
     
        public void setCircB(CircularDependencyB circB) {
            this.circB = circB;
        }
     
        public CircularDependencyB getCircB() {
            return circB;
        }

      
    public CircularDependencyA(@Lazy CircularDependencyB circB) {
            this.circB = circB;
        }
    }

        当依赖被使用时才进行注入。

    (2)属性的循环依赖

      https://juejin.im/post/5dbb9fdef265da4d4c202483(讲的很透彻)

    3、spring中使用的设计模式
    • 工厂模式: spring中的BeanFactory就是简单工厂模式的体现,根据传入唯一的标识来获得bean对象;
    • 单例模式: 提供了全局的访问点BeanFactory;
    • 代理模式: AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)
    • 装饰器模式: 依赖注入就需要使用BeanWrapper;
    • 观察者模式: spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
    • 策略模式: Bean的实例化的时候决定采用何种方式初始化bean实例(反射或者CGLIB动态字节码生成)

    4、spring IOC初始化的整个流程

       https://juejin.im/post/5be976a76fb9a049fd0f5f31

  • 相关阅读:
    互联网流媒体直播点播平台报ioutil.WriteFile错误导致文件只读如何处理?
    互联网直播点播平台go语言搭建重定向和反向代理的区别及使用
    互联网视频直播点播平台EasyDSS如何集成流媒体平台调取登录及上传接口?
    互联网直播点播平台如何联合RTMP推流摄像头构建智慧消防方案?
    RTMP推流网关如何实现摄像头微信幼儿园直播?
    国标GB28181流媒体服务器gorm框架内表明重复添加前缀排查
    国标GB28181流媒体平台EasyGBS新版界面视频流更新时间显示错误问题解决
    视频流媒体播放器EasyPlayer-RTSP-Android 如何随意切换播放视频流?
    国标GB28181流媒体服务器分辨率会导致视频无法播放吗?
    国标GB28181流媒体平台集成后播放多个视频部分视频无法播放问题
  • 原文地址:https://www.cnblogs.com/jing-yi/p/12994792.html
Copyright © 2020-2023  润新知