springboot捕获应用上下文以及如何加载第三方bean

前言

2020/12/3 14:41.周五. 美好的周末要来临了,今天公司没什么业务需要我做出处理。那么就简单的来聊一下标题提及的,springboot捕获应用上下文以及如何加载第三方bean的问题。

springboot捕获应用上下文

之前在聊起来spring的观察者模式的时候,大家还记得事件是怎么进行推送的吗? applicationContext.pushEvent(XXX); 这里的applicationContext,这个Bean我们是直接注入的。

image.png

这样其实没有什么问题,但是这样直接注入其实不太好,因为你并不确定什么时候捕获到了这个上下文对象。那怎么确定呢?当这个上下文在被springboot加载的时候,尝试使用监听器将其捕获下。首先来实现下spring的监听器接口:

package com.cmdc.utils;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

import java.util.LinkedList;

@Slf4j
public class XyatticSpringApplicationRunListener implements SpringApplicationRunListener {

    private static final LinkedList<ConfigurableApplicationContext> APPLICATION_CONTEXTS = Lists.newLinkedList();

    public XyatticSpringApplicationRunListener(final SpringApplication application, final String[] args) {
    }

    @Override
    public void starting() {
    }

    @Override
    public void environmentPrepared(final ConfigurableEnvironment environment) {
    }

    @Override
    public void contextPrepared(final ConfigurableApplicationContext context) {
    }

    @Override
    public void contextLoaded(final ConfigurableApplicationContext context) {
        log.info("we get applicationContext,is:{}",context);
        APPLICATION_CONTEXTS.add(context);
    }

    @Override
    public void started(final ConfigurableApplicationContext context) {
    }

    @Override
    public void running(final ConfigurableApplicationContext context) {
    }

    @Override
    public void failed(final ConfigurableApplicationContext context, final Throwable exception) {
    }

    public static ConfigurableApplicationContext getApplicationContext() {
        if (APPLICATION_CONTEXTS.isEmpty()) {
            return null;
        }
        ConfigurableApplicationContext applicationContext = APPLICATION_CONTEXTS.getLast();
        try {
            applicationContext.getAutowireCapableBeanFactory();
        } catch (IllegalStateException e) {
            APPLICATION_CONTEXTS.removeLast();
            return getApplicationContext();
        }
        return applicationContext;
    }

}
复制代码

注意,咱们在contextLoaded,即上下文已经加载的情况下将其添加到集合中,这个集合是一个静态的全局变量,不属于类的任何实例,而是属于这个类。

image.png

接着,注册这个监听器,怎么注册呢,利用下spring.factories文件。 在resources目录下新建MATA-INF文件夹,然后在里面新建spring.factories文件

image.png

内容如下:

org.springframework.boot.SpringApplicationRunListener=com.cmdc.utils.XyatticSpringApplicationRunListener
复制代码

image.png

ok的,接下来启动下项目试一试。

image.png ok,看来contextLoadeed方法执行成功。并且还知道了,实际的applicationContext对象是configurableApplicationContext一个非常重要的间接子类 AnnotationConfigServletWebServerApplicationContext,不妨来看下继承关系图:

image.png

实际的之前在玩观察者模式的时候咱们捕获到的applicationContext的真正类型也是这个AnnotationConfigServletWebServerApplicationContext。

往后咱们再捕获applicationContext就可以使用下面的这个方法捕获了。

image.png

springboot项目加载三方bean

在实际的业务场景中,要加载第三方bean的情形是不多见的。但是真的需要这么去做的时候,最好能知道怎么办,加载三方bean虽然业务中遇到不多,但是有一天你自己写框架的时候这却是非常常见的。 怎么做呢。咱们首先自己写两个类来模仿下,假装它们是第三方的bean. 第一个类:

package com.cmdc.utils;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;

public class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
    private static Logger logger = LoggerFactory.getLogger(ApplicationReadyEventListener.class);

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
       logger.info("springBoot service start successfully!");
    }
}
复制代码

第二个类:

package com.cmdc.utils;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationListener;

public class ApplicationFailedEventListener implements ApplicationListener<ApplicationFailedEvent> {
    private static Logger logger = LoggerFactory.getLogger(ApplicationFailedEventListener.class);
    @Override
    public void onApplicationEvent(ApplicationFailedEvent event) {
        logger.error("springBoot service start failed!");
    }
}
复制代码

现在打算将他们注入,但是因为它们是三方库里面的,无法在上面直接写@Component。咱们这么去做:

package com.cmdc.utils;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author : wuwensheng
 * @date : 10:05 2021/12/3
 */
public class ApplicationEventListenerConfiguration {

    @Bean
    @ConditionalOnMissingBean(value = ApplicationFailedEventListener.class)
    public ApplicationFailedEventListener getApplicationFailedEventListener(){
        return new ApplicationFailedEventListener();
    }

    @Bean
    @ConditionalOnMissingBean(value = ApplicationReadyEventListener.class)
    public ApplicationReadyEventListener getApplicationReadyEventListener(){
        return new ApplicationReadyEventListener();
    }
}
复制代码

ok,现在假设的情景是什么?ApplicationReadyEventListener和ApplicationFailedEventListener都在三方库里面,只有ApplicationEventListenerConfiguration是存在于自己项目下的。

另外解释下@ConditionalOnMissingBean的含义,当且仅当不存在指定的bean的时候才加载指定的bean。

注意,我并未在ApplicationEventListenerConfiguration类上添加@Configuration注解。而实将此类声明在spring.factories里面。 声明如下:

image.png ok,我的意思就是,尽管没添加@Conuration注解,这个类依旧是我的配置类!

那么咱们启动起来看一下:

image.png ok,已经注入成功。

总结

写业务代码的思想和框架代码层面的思想差太多了,基本一个阳春白雪、一个下里巴人。大家平时也要多看框架源码。

猜你喜欢

转载自juejin.im/post/7037375263285444645