在spring中使用自定义注解注册监听器

接口回调

监听器本质上就是利用回调机制,在某个动作发生前或后,执行我们自己的一些代码。在Java语言中,可以使用接口来实现。

实现一个监听器案例

为了方便,直接在spring环境中定义:以工作(work)为例,定义工作开始时(或结束时)的监听器。

1. 定义回调的接口

package com.yawn.demo.listener;

/**
 * @author Created by yawn on 2018-01-21 13:53
 */
public interface WorkListener {

    void onStart(String name);
}

2. 定义动作

package com.yawn.demo.service;

import com.yawn.demo.listener.WorkListener;

/**
 * @author Created by yawn on 2018-01-21 13:39
 */
@Service
public class MyService {

    @Resource
    private PersonService personService;

    private WorkListener listener;
    public void setWorkListener(WorkListener workListener) {
        this.listener = workListener;
    }

    public void work(String name) {
        listener.onStart(name);
        personService.work();
    }
}

动作work为一个具体的方法,在work()方法的适当时机,调用前面定义的接口。此外,在这个动作定义类中,需要提高设置监听器的方法。

3. 监听测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoSpringAnnotationApplicationTests {

    @Resource
    private MyService myService;

    @Test
    public void test1() {
        // 接口设置监听器
        myService.setWorkListener(new WorkListener() {
            @Override
            public void onStart(String name) {
                System.out.println("Start work for " + name + " !");
            }
        });
//        // lambda 表达式设置监听器
//        myService.setWorkListener(name -> System.out.println("Start work for " + name + " !"));
        // 工作
        myService.work("boss");
    }

	@Test
    public void test2() {
	    // 继承实现类设置监听器
	    myService.setWorkListener(new myWorkListener());
	    // 工作
	    myService.work("boss");
    }

    class myWorkListener extends WorkListenerAdaptor {
        @Override
        public void onStart(String name) {
            System.out.println("Start work for " + name + " !");
        }
    }
}

使用以上两种方法测试,得到了结果为:

Start work for boss !
working hard ...

说明在动作work发生之前,执行了我们在测试类中写下的监听代码,实现类监听的目的。

这就是java使用接口回调的一个例子,我在大三时也写过一篇关于回调的博客可以参考:https://my.oschina.net/silenceyawen/blog/730494

使用注解实现监听器

在以上代码中,调用 setWorkListener(WorkListener listener)  方法一般称作设置(注册)监听器,就是将自己写好的监听代码,设置为动作的监听器。然而,在每次注册监听器时,一般需要写一个类,实现定义好的接口或继承实现接口的类,再重写接口定义的方法即可。因此,聪明的程序员就想简化这个过程,所以就想出了使用注解的方法。使用注解,将监听代码段写在一个方法中,使用一个注解标记这个方法即可。

的确,使用变得简单了,但实现却不见得。

1. 定义一个注解

package com.yawn.demo.anno;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WorkListener {

}

2. 解析注解

package com.yawn.demo.anno;

import com.yawn.demo.service.MyService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author Created by yawn on 2018-01-21 14:46
 */
@Component
public class WorkListenerParser implements ApplicationContextAware, InitializingBean {

    @Resource
    private MyService myService;

    private ApplicationContext applicationContext;

    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, Object> listenerBeans = getExpectListenerBeans(Controller.class, RestController.class, Service.class, Component.class);
        for (Object listener : listenerBeans.values()) {
            for (Method method : listener.getClass().getDeclaredMethods()) {
                if (!method.isAnnotationPresent(WorkListener.class)) {
                    continue;
                }
                myService.setWorkListener(name -> {
                    try {
                        method.invoke(listener, name);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
        }
    }

    /**
     * 找到有可能使用注解的bean
     * @param annotationTypes 需要进行扫描的类级注解类型
     * @return 扫描到的beans的map
     */
    private Map<String, Object> getExpectListenerBeans(Class<? extends Annotation>... annotationTypes) {
        Map<String, Object> listenerBeans = new LinkedHashMap<>();
        for (Class<? extends Annotation> annotationType : annotationTypes) {
            Map<String, Object> annotatedBeansMap = applicationContext.getBeansWithAnnotation(annotationType);
            listenerBeans.putAll(annotatedBeansMap);
        }
        return listenerBeans;
    }

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

在注解的解析过程中,设置监听器。

在解析类中,实现了接口ApplicationContextAware,为了在类中拿到ApplicationContext的引用,用于得到 IOC 容器中的 Bean;而实现接口InitializingBean,则是为了在一个合适的时机执行解析注解、设置监听器的代码。 如果不这样做,可以在CommandLineRunner执行时调用解析、设置的代码,而ApplicationContext也可以自动注入。

3. 测试

在执行完以上代码后,监听器就已经设置好了,可以进行测试了。

package com.yawn.demo.controller;

import com.yawn.demo.anno.WorkListener;
import com.yawn.demo.service.MyService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author Created by yawn on 2018-01-21 13:28
 */
@RestController
public class TestController {

    @Resource
    private MyService myService;

    @GetMapping("/work")
    public Object work() {
        myService.work("boss");
        return "done";
    }

    @WorkListener
    public void listen(String name) {
        System.out.println("Start work for " + name + " !");
    }
}

写一个监听方法,参数类型和个数与接口相同,然后加上自定义的注解即可。当启动环境后,监听器就已经设置好了。

然后通过url调用myService的work()方法,可以看到结果:

Start work for boss !
working hard ...

已经调用了监听方法。在接下来的开发中,就可以使用这个注解注册监听器了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏hbbliyong

asp.net :使用jquery 的ajax +WebService+json 实现无刷新去后台值

首先贴上Jquery的ajax: $.ajax({ url: 'ws_Ajax.asmx/BindDictByUppe...

3106
来自专栏Java学习网

java开发中spring和springboot常用注解总结,开发人员必学

这个注解可以用于类和方法上,用于类上,表示父路径,如类上是demo,方法上是/demo1,那么访问路径就是demo/demo1

845
来自专栏技术墨客

Spring核心——Stereotype组件与Bean扫描

在注解自动装载中介绍了通过注解(Annotation)自动向Bean中注入其他Bean的方法,本篇将介绍通过注解(Annotation)向容器添加Bean的方法...

692
来自专栏小灰灰

SpringMVC返回图片的几种方式

主要借助的是 HttpServletResponse这个对象,实现case如下

2237
来自专栏Java技术栈

获取Spring的ApplicationContext的几种方式

Application Context定义 简单来说就是Spring中的高级容器,可以获取容器中的各种bean组件,注册监听事件,加载资源文件等功能。 具体定...

2607
来自专栏我杨某人的青春满是悔恨

封装一个 Swift-Style 的网络模块

Swift 跟 OC 有着完全不同的设计哲学,它鼓励你使用 protocol 而不是 super class,使用 enum 和 struct 而不是 clas...

613
来自专栏青青天空树

spring基础(2:最小化XML配置)

  byType在出现多个匹配项时不会自动选择一个然是报错,为避免报错,有两种办法:1.使用<bean>元素的primary属性,设置为首选Bean,但所有be...

842
来自专栏Java Web

Spring(3)——装配 Spring Bean 详解

装配 Bean 的概述 前面已经介绍了 Spring IoC 的理念和设计,这一篇文章将介绍的是如何将自己开发的 Bean 装配到 Spring IoC 容器中...

3624
来自专栏Java成神之路

【转】零基础写Java知乎爬虫之进阶篇

说到爬虫,使用Java本身自带的URLConnection可以实现一些基本的抓取页面的功能,但是对于一些比较高级的功能,比如重定向的处理,HTML标记的去除,仅...

903
来自专栏青枫的专栏

day16_ajax学习笔记

JavaScript 中 XMLHttpRequest对象是整个Ajax技术的核心,它提供了异步发送请求的能力。

843

扫码关注云+社区