Spring监听事件详解

在谈Spring的事件监听之前,让我们先了解一下Spring容器,什么是ApplicationContext ?
它是Spring的核心,Context我们通常解释为上下文环境,但是理解成容器会更好些。
ApplicationContext则是应用的容器。
Spring把Bean(object)放在容器中,需要用就通过get方法取出来。

下面我们结合源码注视和ApplicationContext的类图来看看它到底提供给我们哪些能力
源码:

Copyright 2002-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.context;

import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.lang.Nullable;

/**
* Central interface to provide configuration for an application.
* This is read-only while the application is running, but may be
* reloaded if the implementation supports this.
*
* 

An ApplicationContext provides: *

    *
  • Bean factory methods for accessing application components. * Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}. *
  • The ability to load file resources in a generic fashion. * Inherited from the {@link org.springframework.core.io.ResourceLoader} interface. *
  • The ability to publish events to registered listeners. * Inherited from the {@link ApplicationEventPublisher} interface. *
  • The ability to resolve messages, supporting internationalization. * Inherited from the {@link MessageSource} interface. *
  • Inheritance from a parent context. Definitions in a descendant context * will always take priority. This means, for example, that a single parent * context can be used by an entire web application, while each servlet has * its own child context that is independent of that of any other servlet. *
* *

In addition to standard {@link org.springframework.beans.factory.BeanFactory} * lifecycle capabilities, ApplicationContext implementations detect and invoke * {@link ApplicationContextAware} beans as well as {@link ResourceLoaderAware}, * {@link ApplicationEventPublisherAware} and {@link MessageSourceAware} beans. * * @author Rod Johnson * @author Juergen Hoeller * @see ConfigurableApplicationContext * @see org.springframework.beans.factory.BeanFactory * @see org.springframework.core.io.ResourceLoader */ public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver { /** * Return the unique id of this application context. * @return the unique id of the context, or {@code null} if none */ @Nullable String getId(); /** * Return a name for the deployed application that this context belongs to. * @return a name for the deployed application, or the empty String by default */ String getApplicationName(); /** * Return a friendly name for this context. * @return a display name for this context (never {@code null}) */ String getDisplayName(); /** * Return the timestamp when this context was first loaded. * @return the timestamp (ms) when this context was first loaded */ long getStartupDate(); /** * Return the parent context, or {@code null} if there is no parent * and this is the root of the context hierarchy. * @return the parent context, or {@code null} if there is no parent */ @Nullable ApplicationContext getParent(); /** * Expose AutowireCapableBeanFactory functionality for this context. *

This is not typically used by application code, except for the purpose of * initializing bean instances that live outside of the application context, * applying the Spring bean lifecycle (fully or partly) to them. *

Alternatively, the internal BeanFactory exposed by the * {@link ConfigurableApplicationContext} interface offers access to the * {@link AutowireCapableBeanFactory} interface too. The present method mainly * serves as a convenient, specific facility on the ApplicationContext interface. *

NOTE: As of 4.2, this method will consistently throw IllegalStateException * after the application context has been closed. In current Spring Framework * versions, only refreshable application contexts behave that way; as of 4.2, * all application context implementations will be required to comply. * @return the AutowireCapableBeanFactory for this context * @throws IllegalStateException if the context does not support the * {@link AutowireCapableBeanFactory} interface, or does not hold an * autowire-capable bean factory yet (e.g. if {@code refresh()} has * never been called), or if the context has been closed already * @see ConfigurableApplicationContext#refresh() * @see ConfigurableApplicationContext#getBeanFactory() */ AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException; }

类图

image

由源码注释

此接口提供给Spring应用配置的能力,当应用启动时,此接口的实现是只读的,但是如果该实现支持,其内容也是可以重新载入的。
ApplicationContext大概提功能如下能力:

  1. 获取应用组件的bean工厂方法,此能力继承自org.springframework.beans.factory.ListableBeanFactory
  2. 加载资源文件的能力,此能力继承自org.springframework.core.io.ResourceLoader
  3. 发布事件到已注册的监听器,此能力继承自ApplicationEventPublisher
  4. 提供国际化的消息访问,此能力继承自MessageSource

好对ApplicationContext有一定了解之后我们再来看看Spring提供的事件监听。

为了实现事件监听的能力Spring为我们提供了两个顶层接口/抽象类
ApplcationEvent:是个抽象类,里面只有一个构造函数和一个长整型的timestamp。我们自定义的Application event 需要继承这个抽象类.
ApplicationListener:是一个接口,里面只有一个方法onApplicationEvent ,每一个实现改接口的类都要自己实现这个方法。

Spring的事件监听是基于标准的观察者模式的,如果在ApplicationContext部署了一个实现了ApplicationListener的bean,那么当一个ApplicationEvent发布到
ApplicationContext时,这个bean得到通知并作特定的处理。
从上面这段话我们很容易产生两点思考:

  1. 实现了ApplicationListener的bean如何部署到ApplicationContext
  2. 一个ApplicationEvent如何发布到ApplicationContext

下面我们就通过具体的代码来看看这两个问题

先自定义一个MsgEvent,它本身提供一个print()方法:

package com.faunjoe.demo.springbootstart.event;

import org.springframework.context.ApplicationEvent;

public class MsgEvent extends ApplicationEvent {
    private String text;

    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public MsgEvent(Object source) {
        super(source);
    }

    public MsgEvent(Object source, String text) {
        super(source);
        this.text = text;
    }

    public void print() {
        System.out.println("print event content:" + this.text);
    }
}

再自定义一个PringListener实现ApplicationListener:

package com.faunjoe.demo.springbootstart.event;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

public class PrintListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(MsgEvent event) {
        System.out.println("调用MsgEvent的print方法输出其内容:");
        event.print();
    }
}

现在自定义事件和监听器都好了,我们就来看看第一个问题,监听器如何部署到ApplicationContext,有四种方式可以实现,我们一个一个看:

  1. 应用启动之前调用SpringApplication的addListeners方法将自定义的监听器加入。
@SpringBootApplication
public class SpringBootStartApplication1 {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
        springApplication.setWebEnvironment(true);
        //配置事件监听
        springApplication.addListeners(new PrintListener());
        //配置事件监听
        ConfigurableApplicationContext context = springApplication.run(args);
        //发布事件
        context.publishEvent(new MsgEvent(new Object(),"hello world."));
    }
}

运行结果:

image
  1. 监听器部署到ApplicationContext,实际上就是将将监听器交给Spring 容器管理,所以最简单的方法只需在自定义的PrintListener上加上@Component注解就行了,代码如下
package com.faunjoe.demo.springbootstart.event;

import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class PrintListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(MsgEvent event) {
        System.out.println("调用MsgEvent的print方法输出其内容:");
        event.print();
    }
}

启动处的代码注掉配置事件监听一行,如下:

@SpringBootApplication
public class SpringBootStartApplication1 {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
        springApplication.setWebEnvironment(true);
        //配置事件监听
        //springApplication.addListeners(new PrintListener());
        //配置事件监听
        ConfigurableApplicationContext context = springApplication.run(args);
        //发布事件
        context.publishEvent(new MsgEvent(new Object(),"hello world."));
    }
}

启动运行,结果如下:

image
  1. 在配置文件中加入

context.listener.classes: com.faunjoe.demo.springbootstart.event.PrintListener

注释掉PrintListener上的@Component注解

源码分析:

Spring内置DelegatingApplicationListener会监听ApplicationEnvironmentPreparedEvent事件(Environment已经准备好但是Context还没有创建事件),

读取Environment中的context.listener.classes属性值(监听器类全路径,多个以逗号隔开)来创建ApplicationListener,详情见如下源码中我的注释。

/*
* Copyright 2012-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.context.config;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
* {@link ApplicationListener} that delegates to other listeners that are specified under
* a {@literal context.listener.classes} environment property.
*
* @author Dave Syer
* @author Phillip Webb
*/
public class DelegatingApplicationListener
      implements ApplicationListener, Ordered {

   // NOTE: Similar to org.springframework.web.context.ContextLoader

   private static final String PROPERTY_NAME = "context.listener.classes”;//配置文件中配置的配置
   private int order = 0;
   private SimpleApplicationEventMulticaster multicaster;
   @Override
   public void onApplicationEvent(ApplicationEvent event) {
      if (event instanceof ApplicationEnvironmentPreparedEvent) {//监听ApplicationEnvironmentPreparedEvent事件
         List> delegates = getListeners(
               ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
         if (delegates.isEmpty()) {
            return;
         }
         this.multicaster = new SimpleApplicationEventMulticaster();
         for (ApplicationListener listener : delegates) {
            this.multicaster.addApplicationListener(listener);//将监听事件加入spring容器中
         }
      }
      if (this.multicaster != null) {
         this.multicaster.multicastEvent(event);
      }
   }
   @SuppressWarnings("unchecked")
   private List> getListeners(
         ConfigurableEnvironment environment) {
      if (environment == null) {
         return Collections.emptyList();
      }
      String classNames = environment.getProperty(PROPERTY_NAME);//获取配置文件中配置的监听器
      List> listeners = new ArrayList<>();
      if (StringUtils.hasLength(classNames)) {
         for (String className : StringUtils.commaDelimitedListToSet(classNames)) {
            try {
               Class clazz = ClassUtils.forName(className,
                     ClassUtils.getDefaultClassLoader());
               Assert.isAssignable(ApplicationListener.class, clazz, "class ["
                     + className + "] must implement ApplicationListener");
               listeners.add((ApplicationListener) BeanUtils
                     .instantiateClass(clazz));
            }
            catch (Exception ex) {
               throw new ApplicationContextException(
                     "Failed to load context listener class [" + className + "]",
                     ex);
            }
         }
      }
      AnnotationAwareOrderComparator.sort(listeners);
      return listeners;
   }

   public void setOrder(int order) {
      this.order = order;
   }

   @Override
   public int getOrder() {
      return this.order;
   }

}

运行结果:

image
  1. 使用@EventListener注解,先看代码,建立一个普通的java类并交给spring容器,其中一个处理event的方法,加上该注解,删掉配置文件中的配置。
package com.faunjoe.demo.springbootstart.event;

import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * @author faunjoe E-mail:[email protected]
 * @version 创建时间:2018-11-29 14:48
 */

@Component
public class MsgEventHandle {

    @EventListener
    public void handle(MsgEvent event){
        System.out.println("调用MsgEvent的print方法输出其内容:");
        event.print();
    }
}

运行结果:

image

源码分析:
我们首先看@EventListener注解的源码,里面有这样一段注释:

image

它指引我们去看EventListenerMethodProcessor类,这个类是application启动时自动注册执行的。该类的功能是扫描@EventListener注解并生成一个ApplicationListener实例。

其中有个这样的方法:就是用来扫描容器中bean的方法上所有的@EventListener,循环创建ApplicationListener。

protected void processBean(
      final List factories, final String beanName, final Class targetType) {

   if (!this.nonAnnotatedClasses.contains(targetType)) {
      Map annotatedMethods = null;
      try {
         annotatedMethods = MethodIntrospector.selectMethods(targetType,
               (MethodIntrospector.MetadataLookup) method ->
                     AnnotatedElementUtils.findMergedAnnotation(method, EventListener.class));
      }
      catch (Throwable ex) {
         // An unresolvable type in a method signature, probably from a lazy bean - let's ignore it.
         if (logger.isDebugEnabled()) {
            logger.debug("Could not resolve methods for bean with name '" + beanName + "'", ex);
         }
      }
      if (CollectionUtils.isEmpty(annotatedMethods)) {
         this.nonAnnotatedClasses.add(targetType);
         if (logger.isTraceEnabled()) {
            logger.trace("No @EventListener annotations found on bean class: " + targetType.getName());
         }
      }
      else {
         // Non-empty set of methods
         ConfigurableApplicationContext context = getApplicationContext();
         for (Method method : annotatedMethods.keySet()) {
            for (EventListenerFactory factory : factories) {
               if (factory.supportsMethod(method)) {
                  Method methodToUse = AopUtils.selectInvocableMethod(method, context.getType(beanName));
                  ApplicationListener applicationListener =
                        factory.createApplicationListener(beanName, targetType, methodToUse);
                  if (applicationListener instanceof ApplicationListenerMethodAdapter) {
                     ((ApplicationListenerMethodAdapter) applicationListener).init(context, this.evaluator);
                  }
                  context.addApplicationListener(applicationListener);
                  break;
               }
            }
         }
         if (logger.isDebugEnabled()) {
            logger.debug(annotatedMethods.size() + " @EventListener methods processed on bean '" +
                  beanName + "': " + annotatedMethods);
         }
      }
   }
}

以上四点就回答了我们开头提出的第一个问题:
实现了ApplicationListener的bean如何部署到ApplicationContext。接下来轮到第二个问题了,

一个ApplicationEvent如何发布到ApplicationContext。我们还是来看测试的主代码:

@SpringBootApplication
public class SpringBootStartApplication1 {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
        springApplication.setWebEnvironment(true);
        //配置事件监听
        //springApplication.addListeners(new PrintListener());
        //配置事件监听
        ConfigurableApplicationContext context = springApplication.run(args);
        //发布事件
        context.publishEvent(new MsgEvent(new Object(),"hello world."));
    }
}

上面的这段代码可以看出ConfigurableApplicationContext具有发布事件的能力,而通过下面的类图我们知道ConfigurableApplicationContextApplicationContext的子接口。
文章开头对ApplicationContext的分析我们知道ApplicationContext具有发布事件的能力,这项能力是通过继承ApplicationEventPublisher获得的。

image

通过以上分析,我们不难发现,只要我们在一个普通的java bean注入ApplicationContext的实例,那么这个bean就获得了发布事件的能力。
那么怎样才能在一个普通的bean中注入ApplicationContext的实例呢?看ApplicationContext的源码,源码中有这一段注释:

image

这段注释大概的意思是ApplicationContext的实例可以探测和唤起AppliationContextAware ,ResourceLoaderAware,ApplicationEventPublisherAware,MessageSourceAware等,
我们顺势看下AppliationContextAware的代码,如下:

/*
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.context;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.Aware;

/**
* Interface to be implemented by any object that wishes to be notified
* of the {@link ApplicationContext} that it runs in.
*
* 

Implementing this interface makes sense for example when an object * requires access to a set of collaborating beans. Note that configuration * via bean references is preferable to implementing this interface just * for bean lookup purposes. * *

This interface can also be implemented if an object needs access to file * resources, i.e. wants to call {@code getResource}, wants to publish * an application event, or requires access to the MessageSource. However, * it is preferable to implement the more specific {@link ResourceLoaderAware}, * {@link ApplicationEventPublisherAware} or {@link MessageSourceAware} interface * in such a specific scenario. * *

Note that file resource dependencies can also be exposed as bean properties * of type {@link org.springframework.core.io.Resource}, populated via Strings * with automatic type conversion by the bean factory. This removes the need * for implementing any callback interface just for the purpose of accessing * a specific file resource. * *

{@link org.springframework.context.support.ApplicationObjectSupport} is a * convenience base class for application objects, implementing this interface. * *

For a list of all bean lifecycle methods, see the * {@link org.springframework.beans.factory.BeanFactory BeanFactory javadocs}. * * @author Rod Johnson * @author Juergen Hoeller * @author Chris Beams * @see ResourceLoaderAware * @see ApplicationEventPublisherAware * @see MessageSourceAware * @see org.springframework.context.support.ApplicationObjectSupport * @see org.springframework.beans.factory.BeanFactoryAware */ public interface ApplicationContextAware extends Aware { /** * Set the ApplicationContext that this object runs in. * Normally this call will be used to initialize the object. *

Invoked after population of normal bean properties but before an init callback such * as {@link org.springframework.beans.factory.InitializingBean#afterPropertiesSet()} * or a custom init-method. Invoked after {@link ResourceLoaderAware#setResourceLoader}, * {@link ApplicationEventPublisherAware#setApplicationEventPublisher} and * {@link MessageSourceAware}, if applicable. * @param applicationContext the ApplicationContext object to be used by this object * @throws ApplicationContextException in case of context initialization errors * @throws BeansException if thrown by application context methods * @see org.springframework.beans.factory.BeanInitializationException */ void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }

通过ApplicationContextAware的源码我们可以看出其有一个setApplicationContext方法可以将ApplicationContext实例注入,下面我们写一个java类去实现 ApplicationContextAware看看。
代码如下:

package com.faunjoe.demo.springbootstart.event;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Component;

/**
 * @author faunjoe E-mail:[email protected]
 * @version 创建时间:2018-11-29 12:50
 */

@Component
public class FaunjoeApplicationContextAware implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void publish(ApplicationEvent event){
        applicationContext.publishEvent(event);
    }
}

package com.faunjoe.demo.springbootstart.controller;

import com.faunjoe.demo.springbootstart.domain.Person;
import com.faunjoe.demo.springbootstart.event.FaunjoeApplicationContextAware;
import com.faunjoe.demo.springbootstart.event.MsgEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author faunjoe E-mail:[email protected]
 * @version 创建时间:2018-11-27 14:40
 */

@RestController
public class PersonRestController {

    private final Person person;

    @Autowired
    private FaunjoeApplicationContextAware faunjoeApplicationContextAware;

    @Autowired
    public PersonRestController(Person person) {
        this.person = person;
    }

    @GetMapping("/person")
    public Person person() {
        faunjoeApplicationContextAware.publish(new MsgEvent(new Object(),"有人访问我,我得跟他们说:hello"));
        return person;
    }
}

@SpringBootApplication
public class SpringBootStartApplication1 {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(SpringBootStartApplication.class);
        springApplication.setWebEnvironment(true);
        //配置事件监听
        //springApplication.addListeners(new PrintListener());
        //配置事件监听
        ConfigurableApplicationContext context = springApplication.run(args);
        //发布事件
        //context.publishEvent(new MsgEvent(new Object(),"hello world."));
    }
}

image

我们注意到ApplicationContext的事件发布能力是继承自ApplicationEventPublisher,并且ApplicationContextAware中有这样一段注释:

image

这段注释可以看出,如果仅仅想获取发布事件的能力,只需要实现ApplicationEventPublisherAware就行了。
照着这样的思路代码如下:定义一个事件发布器

package com.faunjoe.demo.springbootstart.event;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;

@Component
public class FaunjoeEventPublisher implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    public void publish(ApplicationEvent event){
        publisher.publishEvent(event);
    }
}


@RestController
public class PersonRestController {

    private final Person person;

    @Autowired
    private FaunjoeApplicationContextAware faunjoeApplicationContextAware;

    @Autowired
    private FaunjoeEventPublisher faunjoeEventPublisher;

    @Autowired
    public PersonRestController(Person person) {
        this.person = person;
    }

    @GetMapping("/person")
    public Person person() {
        faunjoeApplicationContextAware.publish(new MsgEvent(new Object(),"有人访问我,我得跟他们说:hello"));

        faunjoeEventPublisher.publish(new MsgEvent(new Object(),"有人访问我,我得跟他们说:hello"));
        return person;
    }
}

你可能感兴趣的:(Spring监听事件详解)