(二)Spring关键接口之ApplicationContext上下文相关

目录

1.ServletContextListener接口

2.ApplicationContextInitializer接口

2.1 Springboot的集成方式

2.2 SpringMVC的集成方式

3.ApplicationListener接口及配套类

3.1 ApplicationListener接口和配套类ApplicationEvent

3.2 ApplicationEventMulticaster接口


本系列一共四篇,其它三篇传送门:

1.ServletContextListener接口

接口源码如下:

public interface ServletContextListener extends EventListener {
    public default void contextInitialized(ServletContextEvent sce) {
    }
    public default void contextDestroyed(ServletContextEvent sce) {
    }
}

接口说明:该接口属于JDK源码中Servlet包中的类,当程序使用Web服务器的方式启动Tomcat容器,初始化ServletContext后将会调用contextInitialized()方法,以前老式常规的Spring入口便是在这里进入的。

contextDestroyed()方法则是在ServletContext被销毁时调用。

2.ApplicationContextInitializer接口

接口源码如下:

public interface ApplicationContextInitializer
        <C extends ConfigurableApplicationContext> {
   void initialize(C applicationContext);
}

接口说明:接口的作用便是在Spring容器调用refresh()方法刷新上下文前对ApplicationContext进行一些自定义的操作,如手动加入自定义配置文件或者手动加入某个自定义实现类等,该接口能够使用泛型来确定某一个具体ApplicationContext实现类。接下来看看该接口在Springboot和SpringMVC框架下是如何集成进去的。

2.1 Springboot的集成方式

Springboot有两种集成方式,一种是在Springboot文件spring.factories配置,另一种是在application.yml文件中配置。

如现在定义了一个CustomInitializer自定义的ApplicationContextInitializer实现类,代码如下:

@Slf4j
public class CustomInitializer implements ApplicationContextInitializer {
    @Override
    public void initialize(ConfigurableApplicationContext 
            applicationContext) {
        log.info("invoke CustomInitializer initialize method.");
    }
}

第一种:采用在spring.factories中添加ApplicationContextInitializer配置。首先需要在resources目录下添加/META-INF/spring.factories文件,此文件为springboot的配置文件,只要路径正确就能被springboot读取加载,在其中配置如下:

org.springframework.context.ApplicationContextInitializer=\
  com.iboxpay.initializer.CustomInitializer

其配置读相关源码如下(建议先看过springboot源码,展示源码将会忽略没必要的流程):

public class SpringApplication {
    private List<ApplicationContextInitializer<?>> initializers;
    public SpringApplication(ResourceLoader resourceLoader, 
            Class<?>... primarySources) {
       ...
       setInitializers((Collection) getSpringFactoriesInstances(
               ApplicationContextInitializer.class));
       ...
    }
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
       return getSpringFactoriesInstances(type, new Class<?>[] {});
    }
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, 
            Class<?>[] parameterTypes, Object... args) {
       ClassLoader classLoader = getClassLoader();
       Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader
               .loadFactoryNames(type, classLoader));
       List<T> instances = createSpringFactoriesInstances(type, 
               parameterTypes, classLoader, args, names);
       AnnotationAwareOrderComparator.sort(instances);
       return instances;
    }
    public ConfigurableApplicationContext run(String... args) {
        ...
        prepareContext(context, environment, listeners, 
                applicationArguments, printedBanner);
        ...
    }
    private void prepareContext(ConfigurableApplicationContext context, 
            ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, 
            ApplicationArguments applicationArguments, 
            Banner printedBanner) {
        ...
        applyInitializers(context);
        ...
    }
    protected void applyInitializers(ConfigurableApplicationContext 
            context) {
       for (ApplicationContextInitializer initializer : getInitializers()){
          Class<?> requiredType = GenericTypeResolver
                  .resolveTypeArgument(initializer.getClass(),
                      ApplicationContextInitializer.class);
          Assert.isInstanceOf(requiredType, context, 
                  "Unable to call initializer.");
          initializer.initialize(context);
       }
    }
}

大致流程如下:

  1. springboot的启动main函数调用SpringApplication.run()方法;
  2. 初始化SpringApplication类,在其构造函数中会调用getSpringFactoriesInstances方法来获取在spring.factories文件中配置的所有ApplicationContextInitializer类,其具体读取流程略;
  3. 初始化SpringApplication类之后调用run()方法,在该方法中会依次调用到applyInitializers()方法中;
  4. 方法applyInitializers()会依次调用从spring.factories文件中读取ApplicationContextInitializer类的initialize方法。

第二种:采用在application.yml文件配置:

context:
  initializer:
    classes: com.iboxpay.initializer.CustomInitializer

这个配置方法的原理和在spring.factoreis文件中配置ApplicationContextInitializer类是差不多的,前面会读取文件中配置的DelegatingApplicationContextInitializer类,该类会在会调用到applyInitializers()中,进而调用DelegatingApplicationContextInitializer类的initialize()方法,在该方法流程中会读取配置在application.yml配置中的initializer类。其源码如下:

public class DelegatingApplicationContextInitializer
      implements ApplicationContextInitializer
              <ConfigurableApplicationContext>, Ordered {
    private static final String PROPERTY_NAME = 
            "context.initializer.classes";
    @Override
    public void initialize(ConfigurableApplicationContext context) {
       ConfigurableEnvironment environment = context.getEnvironment();
       List<Class<?>> initializerClasses = 
               getInitializerClasses(environment);
       if (!initializerClasses.isEmpty()) {
          applyInitializerClasses(context, initializerClasses);
       }
    }
    private List<Class<?>> getInitializerClasses(
            ConfigurableEnvironment env) {
       String classNames = env.getProperty(PROPERTY_NAME);
       List<Class<?>> classes = new ArrayList<>();
       if (StringUtils.hasLength(classNames)) {
          for (String className : 
                  StringUtils.tokenizeToStringArray(classNames, ",")) {
             classes.add(getInitializerClass(className));
          }
       }
       return classes;
    }
}

可以看到方法流程十分简洁,直接在initialize方法中获取environment对象,再将对象传入getInitializerClasses方法,在该方法中读取environment对象的context.initializer.classes属性,而该属性便是我们在application.yml文件中配置的自定义initializer类CustomInitializer。读取到自定义的类之后便可以在该类中进行调用,完成自定义initializer类的嵌入集成。

2.2 SpringMVC的集成方式

SpringMVC由于其是依赖于web.xml文件的配置来启动的,因此想要在ApplicationContext刷新前将initializer类配置进程序上下文中猜想也只能通过web.xml文件配置来实现。现在实例Demo类还是上一节的CustomInitializer类。

而实际上SpringMVC确实是通过web.xml文件来实现配置的,也有两种方式,一种是将initializer类配成globalInitializerClasses属性,而另一种则是在SpringMVC的核心类DispatcherServlet配置初始化参数contextInitializerClasses。接下来说一下这两种配置方式(前提看过SpringMVC启动源码):

第一种:使用globalInitializerClasses属性来配置,在web.xml文件中的配置如下:

<context-param>
    <param-name>globalInitializerClasses</param-name>
    <param-value>com.iboxpay.initializer.CustomInitializer</param-value>
</context-param>

其启动时加载源码如下:

public abstract class FrameworkServlet 
        extends HttpServletBean implements ApplicationContextAware {
    private String contextInitializerClasses;
    public static final String GLOBAL_INITIALIZER_CLASSES_PARAM = 
            "globalInitializerClasses";
    public void setContextInitializerClasses(String 
            contextInitializerClasses) {
       this.contextInitializerClasses = contextInitializerClasses;
    }
    @Override
    protected final void initServletBean() throws ServletException {
        ...
        this.webApplicationContext = initWebApplicationContext();
        ...
    }
    protected WebApplicationContext initWebApplicationContext() {
        ...
        if (wac == null) {
           wac = createWebApplicationContext(rootContext);
        }
        ...
    }
    protected WebApplicationContext createWebApplicationContext(
            WebApplicationContext parent) {
       return createWebApplicationContext((ApplicationContext) parent);
    }
    protected WebApplicationContext createWebApplicationContext(
            ApplicationContext parent) {
        ...
        configureAndRefreshWebApplicationContext(wac);
        ...
    }
    protected void configureAndRefreshWebApplicationContext(
            ConfigurableWebApplicationContext wac) {
        ...
        applyInitializers(wac);
        wac.refresh();
    }
    protected void applyInitializers(ConfigurableApplicationContext wac) {
       // 获取web.xml文件中globalInitializerClasses配置
       String globalClassNames = getServletContext()
               .getInitParameter(ContextLoader
                   .GLOBAL_INITIALIZER_CLASSES_PARAM);
       if (globalClassNames != null) {
          for (String className : 
                  StringUtils.tokenizeToStringArray(globalClassNames, 
                          INIT_PARAM_DELIMITERS)) {
             this.contextInitializers.add(loadInitializer(className, wac));
          }
       }
       // 这是个成员变量,代表着这个变量是在初始化的时候set进去的
       if (this.contextInitializerClasses != null) {
          for (String className : 
                  StringUtils.tokenizeToStringArray(this
                          .contextInitializerClasses, 
                              INIT_PARAM_DELIMITERS)) {
             this.contextInitializers.add(loadInitializer(className, wac));
          }
       }
       AnnotationAwareOrderComparator.sort(this.contextInitializers);
       for (ApplicationContextInitializer<ConfigurableApplicationContext> 
               initializer : this.contextInitializers) {
          initializer.initialize(wac);
       }
    }
}

对于如何进入到FrameworkServlet类的initServletBean便不做过多的讲解,可以看到根据其通用的调用链一路下去最终还是到了熟悉的方法名称来:applyInitializers(),该方法是进行调用initializer类的最终方法。都知道在web.xml文件中的配置全都会保存在ServletContext中,因此获取initializer类直接调用getServletContext().getInitParameter()便能够获得,接着将获取到的类配置实例化添加到contextInitializers成员变量即可。

第二种:配置在DispatcherServlet的初始变量中:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:spring-mvc.xml</param-value>
    </init-param>
    <!-- 配置初始化变量,将会调用set方法set进去 -->
    <init-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.iboxpay.initializer.CustomInitializer</param-value>
    </init-param>
</servlet>

第二种的方法调用链和一种一样,只是在初始化DispatcherSerlvet的时候便会将contextInitializerClasses配置调用setContextInitializerClasses方法set进去,为其成员变量contextInitializerClasses直接赋值,和成员变量contextConfigLocation的赋值方式一样,如果有多个可支持“,; \t\n”这些符号分割开来。接着再将String路径实例化成具体的类,添加进contextInitializers变量,最终排序逐个调用方法initialize(),完成initializer类的集成。

3.ApplicationListener接口及配套类

3.1 ApplicationListener接口和配套类ApplicationEvent

其组合源码如下:

public interface ApplicationListener<E extends ApplicationEvent> 
        extends EventListener {
   void onApplicationEvent(E event);
}
public abstract class ApplicationEvent extends EventObject {
   private static final long serialVersionUID = 7099057708183571937L;
   private final long timestamp;
   public ApplicationEvent(Object source) {
      super(source);
      this.timestamp = System.currentTimeMillis();
   }
   public final long getTimestamp() {
      return this.timestamp;
   }
}

接口说明:这套组合是Spring容器为开发者提供的事件驱动监听功能,看过Springboot启动源码对于这个应该不算陌生,Springboot中application.yml文件的读取解析便是使用事件监听来完成的。

示例关键代码如下:

public class ConfigFileApplicationListener 
        implements EnvironmentPostProcessor, SmartApplicationListener, 
        Ordered {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
       if (event instanceof ApplicationEnvironmentPreparedEvent) {
          onApplicationEnvironmentPreparedEvent(
                  (ApplicationEnvironmentPreparedEvent) event);
       }
       if (event instanceof ApplicationPreparedEvent) {
          onApplicationPreparedEvent(event);
       }
    }
}
public class ApplicationEnvironmentPreparedEvent 
        extends SpringApplicationEvent {
   private final ConfigurableEnvironment environment;
   public ApplicationEnvironmentPreparedEvent(SpringApplication application,
           String[] args, ConfigurableEnvironment environment) {
      super(application, args);
      this.environment = environment;
   }
   public ConfigurableEnvironment getEnvironment() {
      return this.environment;
   }
}

示例说明:ConfigFileApplicationListener和ApplicationEnvironmentPreparedEvent则是一套监听驱动事件功能组合,声明一个ConfigFileApplicationListener监听器,再将相应的Event声明传入进去即可根据事件来进行相应的操作。onApplicationEnvironmentPreparedEvent()方法则是读取解析application.yml文件的方法。

3.2 ApplicationEventMulticaster接口

当然这上述的只是一个监听器,如果需要一组监听器组来组合完成某一个功能,也可以使用到Spring的另外一个接口:ApplicationEventMulticaster。

ApplicationEventMulticaster接口源码如下:

public interface ApplicationEventMulticaster {
   void addApplicationListener(ApplicationListener<?> listener);
   void addApplicationListenerBean(String listenerBeanName);
   void removeApplicationListener(ApplicationListener<?> listener);
   void removeApplicationListenerBean(String listenerBeanName);
   void removeAllListeners();
   void multicastEvent(ApplicationEvent event);
   void multicastEvent(ApplicationEvent event, 
           @Nullable ResolvableType eventType);
}

接口说明:该接口大体提供了三种功能:新增监听器、删除监听器及传入监听事件调用监听器。这相当于可以将所有的监听器都放入这个实现类中,当需要根据某个事件来进行相应的操作时只需要传入事件,开发者可以在监听器组实现类中自定义来根据事件协调各个监听器之间的搭配以完成某个具体的功能。

使用示例可以参照Springboot的启动运行监听器EventPublishingRunListener,该类的类信息及方法大致源码如下:

public class EventPublishingRunListener 
        implements SpringApplicationRunListener, Ordered {
    private final SimpleApplicationEventMulticaster initialMulticaster;
    @Override
    public void starting() {
       this.initialMulticaster.multicastEvent(
               new ApplicationStartingEvent(this.application, this.args));
    }
    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
       this.initialMulticaster
             .multicastEvent(
                 new ApplicationEnvironmentPreparedEvent(this.application, 
                     this.args, environment));
    }
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
       this.initialMulticaster
             .multicastEvent(new ApplicationContextInitializedEvent(
                 this.application, this.args, context));
    }
    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
       ...
       this.initialMulticaster.multicastEvent(
               new ApplicationPreparedEvent(this.application, this.args, 
                       context));
    }
    @Override
    public void failed(ConfigurableApplicationContext context, 
            Throwable exception) {
       ApplicationFailedEvent event = new ApplicationFailedEvent(
               this.application, this.args, context, exception);
       if (context != null && context.isActive()) {
          context.publishEvent(event);
       }
       else {
          ...
          this.initialMulticaster.multicastEvent(event);
       }
    }
}

示例说明:可以看到,当调用EventPublishingRunListener类的几种不同方法时,在方法中直接声明某一事件,直接传入SimpleApplicationEventMulticaster监听器组即可,而无需关心到底应该由哪个监听器来监听处理这个事件,具体的处理逻辑都在initialMulticaster的multicastEvent被封装,增加了使用的便捷性。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值