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

时间:2022-05-04
本文章向大家介绍在spring中使用自定义注解注册监听器,主要内容包括接口回调、实现一个监听器案例、2. 定义动作、3. 监听测试、使用注解实现监听器、2. 解析注解、3. 测试、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

接口回调

监听器本质上就是利用回调机制,在某个动作发生前或后,执行我们自己的一些代码。在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 ...

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