浅谈Spring事件监听

浅谈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.
*
* <p>An ApplicationContext provides:
* <ul>
* <li>Bean factory methods for accessing application components.
* Inherited from {@link org.springframework.beans.factory.ListableBeanFactory}.
* <li>The ability to load file resources in a generic fashion.
* Inherited from the {@link org.springframework.core.io.ResourceLoader} interface.
* <li>The ability to publish events to registered listeners.
* Inherited from the {@link ApplicationEventPublisher} interface.
* <li>The ability to resolve messages, supporting internationalization.
* Inherited from the {@link MessageSource} interface.
* <li>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.
* </ul>
*
* <p>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.
    * <p>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.
    * <p>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.
    * <p><b>NOTE: As of 4.2, this method will consistently throw IllegalStateException
    * after the application context has been closed.</b> 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.png

由源码注释

此接口提供给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<MsgEvent> {
    @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.png
  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<MsgEvent> {
    @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.png
  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<ApplicationEvent>, 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<ApplicationListener<ApplicationEvent>> delegates = getListeners(
               ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());
         if (delegates.isEmpty()) {
            return;
         }
         this.multicaster = new SimpleApplicationEventMulticaster();
         for (ApplicationListener<ApplicationEvent> listener : delegates) {
            this.multicaster.addApplicationListener(listener);//将监听事件加入spring容器中
         }
      }
      if (this.multicaster != null) {
         this.multicaster.multicastEvent(event);
      }
   }
   @SuppressWarnings("unchecked")
   private List<ApplicationListener<ApplicationEvent>> getListeners(
         ConfigurableEnvironment environment) {
      if (environment == null) {
         return Collections.emptyList();
      }
      String classNames = environment.getProperty(PROPERTY_NAME);//获取配置文件中配置的监听器
      List<ApplicationListener<ApplicationEvent>> 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<ApplicationEvent>) 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.png
  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:aijun.fu@mtime.com
 * @version 创建时间:2018-11-29 14:48
 */

@Component
public class MsgEventHandle {

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

运行结果:


image.png

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

image.png

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

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

protected void processBean(
      final List<EventListenerFactory> factories, final String beanName, final Class<?> targetType) {
 
   if (!this.nonAnnotatedClasses.contains(targetType)) {
      Map<Method, EventListener> annotatedMethods = null;
      try {
         annotatedMethods = MethodIntrospector.selectMethods(targetType,
               (MethodIntrospector.MetadataLookup<EventListener>) 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.png

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


image.png

这段注释大概的意思是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.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>{@link org.springframework.context.support.ApplicationObjectSupport} is a
* convenience base class for application objects, implementing this interface.
*
* <p>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.
    * <p>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:aijun.fu@mtime.com
 * @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:aijun.fu@mtime.com
 * @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.png

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

image.png

这段注释可以看出,如果仅仅想获取发布事件的能力,只需要实现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;
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341