Spring 监听器listener原理-手写监听器(二)

  1. Spring 监听器listener原理-基本使用(一)
  2. Spring 监听器listener原理-手写监听器(二)
  3. Spring 监听器listener原理-spring监听器源码分析(三)

原本计划从0开始,实现spring的扫描注入,然后在自定义我们的监听器。但是spring容器并不是我们关注的重点。所以最终还是决定使用spring框架来探究监听器的实现原理

基于接口的监听器实现原理

接口定义

  • 配置类
@ComponentScan({"com.zhu.demo.customer"})
@Configuration
public class CustomerListenerConfig {
}
  • 定义事件接口
public interface MyApplicationEvent {
}
  • 定义监听器接口
public  interface MyApplicationListener<E extends MyApplicationEvent> {

    /**
     * 用于实现监听逻辑
     * @param event
     */
    void onEvent(MyApplicationEvent event);

    /**
     * 判断监听器可以监听的事件类型
     * @param eventType
     * @return
     */
    boolean supportsEventType(Class<?> eventType);
}

接口实现

  • 实现事件接口
public class AEvent implements MyApplicationEvent {
    //定义发布事件的内容
}

public class BEvent implements MyApplicationEvent {
    //定义发布事件的内容
}

  • 监听器接口实现
@Component
public class AListener implements MyApplicationListener<AEvent> {

    @Override
    public void onEvent(MyApplicationEvent event) {
        System.out.println(event.getClass());
    }

    /**
     * 判断需要监听何种事件,只是核心方法
     * @param eventType
     * @return
     */
    @Override
    public boolean supportsEventType(Class<?> eventType) {
        //获得泛型类型
        Class annotationClass = (Class) ((ParameterizedType) this.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
        //父类.class.isAssignableFrom(子类.class)
        return annotationClass.isAssignableFrom(eventType);
    }
}

supportsEventType是核心方法,用于判断我们监听器需要监听那些事件。判断的方法时获取监听器上的泛型是否与事件类型一致。如果一致就是我们需要监听的事件。

事件发布器

@Component("myApplicationEventPublisher")
public class MyApplicationEventPublisher {

    /**
     * 找到我们自定义的所有监听器
     */
    @Autowired
    private List<MyApplicationListener> applicationListeners ;

    public void pushEvent(MyApplicationEvent event){
        for (MyApplicationListener applicationListener : applicationListeners) {
            //判断是否需要监听的事件
            if (applicationListener.supportsEventType(event.getClass())){
                applicationListener.onEvent(event);
            }
        }
    }

    public List<MyApplicationListener> getApplicationListeners() {
        return applicationListeners;
    }

    public void setApplicationListeners(List<MyApplicationListener> applicationListeners) {
        this.applicationListeners = applicationListeners;
    }
}

执行发布事件

public class Main {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(CustomerListenerConfig.class);
        MyApplicationEventPublisher myApplicationEventPublisher = (MyApplicationEventPublisher) applicationContext.getBean("myApplicationEventPublisher");
        MyApplicationEvent Aevent = new AEvent();
        MyApplicationEvent Bevent = new BEvent();

        myApplicationEventPublisher.pushEvent(Aevent);
        myApplicationEventPublisher.pushEvent(Bevent);
    }
}

结果

class com.zhu.demo.customer.inter.impl.AEvent

可以看到因为我们只监听了AEvent所以只打印了AEvent。至此我们基于接口的监听器就实现完了。代码的重点是supportsEventType方法。用于判断我们需要监听的类型。后面文章在关于分析spring监听器源码的时候我们也会看到类似的逻辑。

基于注解的监听器实现原理

  • 定义注解类
@Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyEventListener {
}

使用注解标注方法

@ComponentScan({"com.zhu.demo.customer"})
@Configuration
public class CustomerListenerConfig {

    @MyEventListener
    public void test(AEvent aEvent){
        System.out.println("使用注解监听器" + aEvent.getClass());
    }
}

大家可能有一个疑问,被注解MyEventListener标注的监听器只是一个方法,没有实现MyApplicationListener接口,是否也可以监听MyApplicationEventPublisher 发布的事件或者说如何实现监听MyApplicationEventPublisher 的事件。这里我们引入一个设计模式---适配器模式

  • 适配器类
    适配器类也实现了MyApplicationListener接口,并且成员属性中包含beanName和method方法。

public class MyApplicationListenerMethodAdapter implements MyApplicationListener<MyApplicationEvent> {

    /**
     * bean的名字
     */
    private final String beanName;

    /**
     * 被MyEventListener注解标注的方法
     */
    private final Method method;

    /**
     * 用于获取我们执行method方式时获取对象
     */
    @Nullable
    private ApplicationContext applicationContext;

    public MyApplicationListenerMethodAdapter(String beanName, Method method, @Nullable ApplicationContext applicationContext) {
        this.beanName = beanName;
        this.method = method;
        this.applicationContext = applicationContext;
    }



    protected void doInvoke(Object... args) {
        Object bean = applicationContext.getBean(beanName);
        // Detect package-protected NullBean instance through equals(null) check
        if (bean.equals(null)) {
            return;
        }
        ReflectionUtils.makeAccessible(this.method);
        try {
            //反射调用MyEventListener注解标注的方法
            this.method.invoke(bean, args);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 执行事件方法
     * @param event
     */
    @Override
    public void onEvent(MyApplicationEvent event) {
        doInvoke(new Object[] {event});
    }

    /**
     * 判断我们方法支持的事件类型
     * @param eventType
     * @return
     */
    @Override
    public boolean supportsEventType(Class<?> eventType) {
        Class<?>[] getTypeParameters = method.getParameterTypes();
        if (getTypeParameters[0].isAssignableFrom(eventType)) {
            return true;
        }
        return false;
    }
}

适配器类已经有了,那么如何扫描被MyEventListener注解标注的方法并进行解析呢。我们使用spring提供的一个扩展点SmartInitializingSingleton。实现SmartInitializingSingleton的接口后,当所有单例 bean 都初始化完成以后, Spring的IOC容器会回调该接口的 afterSingletonsInstantiated()方法。我们可以在这个扩展点把所有被MyEventListener标注的方法都找出来。


@Component
public class MyEventListenerMethodProcessor implements SmartInitializingSingleton, ApplicationContextAware {

    @Nullable
    private ConfigurableApplicationContext applicationContext;



    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //获取spring上下文
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;

    }


    @Override
    public void afterSingletonsInstantiated() {
        //获取beanFactory工厂,beanFactory可以理解为就是spring容器
        ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
        //获取beanFactory所有的对象
        String[] beanNames = beanFactory.getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            Class<?> type = beanFactory.getType(beanName);
            //判断类中的方法是否被MyEventListener注解所标识
            Map<Method, MyEventListener> annotatedMethods = null;
            annotatedMethods = MethodIntrospector.selectMethods(type,
                    (MethodIntrospector.MetadataLookup<MyEventListener>) method ->
                            AnnotatedElementUtils.findMergedAnnotation(method, MyEventListener.class));
            //没有就直接返回
            if (CollectionUtils.isEmpty(annotatedMethods)) {
                continue;
            }
            //获取我们自己的事件发布器
            MyApplicationEventPublisher myApplicationEventPublisher = (MyApplicationEventPublisher)beanFactory.getBean("myApplicationEventPublisher");
            for (Method method : annotatedMethods.keySet()) {
                //如果发现有被MyEventListener注解标注,那么实例化一个适配器类把beanName和method、spring上下文传入。
                MyApplicationListenerMethodAdapter myApplicationListenerMethodAdapter = new MyApplicationListenerMethodAdapter(beanName, method, applicationContext);
                //把封装的适配器也加入到我们的监听器集合中
                myApplicationEventPublisher.getApplicationListeners().add(myApplicationListenerMethodAdapter);
            }
        }
    }
}

执行

    public static void main(String[] args) {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(CustomerListenerConfig.class);
        MyApplicationEventPublisher myApplicationEventPublisher = (MyApplicationEventPublisher) applicationContext.getBean("myApplicationEventPublisher");
        MyApplicationEvent Aevent = new AEvent();
        MyApplicationEvent Bevent = new BEvent();

        myApplicationEventPublisher.pushEvent(Aevent);
        myApplicationEventPublisher.pushEvent(Bevent);
    }

执行结果如下。

class com.zhu.demo.customer.inter.impl.AEvent
使用注解监听器class com.zhu.demo.customer.inter.impl.AEvent

至此我们定义的监听器就已经实现完毕。后面一篇文章将分析spring的实现。

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

推荐阅读更多精彩内容