在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 条评论
登录 后参与评论

相关文章

来自专栏Java后端生活

JavaWeb(六)JSP-1

1513
来自专栏程序猿DD

程序员你为什么这么累【续】:编写简陋的接口调用框架 - 动态代理学习

导读: 程序员你为什么这么累? 我的编码习惯 - 接口定义 我的编码习惯 - Controller规范 我的编码习惯 - 日志建议 我的编码习惯 - 异常处理 ...

4157
来自专栏猿天地

Spring Boot中整合Sharding-JDBC单库分表示例

本文是Sharding-JDBC采用Spring Boot Starter方式配置第二篇,第一篇是读写分离讲解,请参考:《Spring Boot中整合Shard...

2853
来自专栏移动开发之家

Dagger2的轻松愉悦解析

  Dagger2,依赖注入框架,一个刚接触时感觉麻烦,用久了就会“嘴上说不要,身体却很诚实”的开发润滑剂(◐‿◑)。(本文为拖更而生)

561
来自专栏芋道源码1024

注册中心 Eureka 源码解析 —— EndPoint 与 解析器

目前有多种 Eureka-Server 访问地址的配置方式,本文只分享 Eureka 1.x 的配置,不包含 Eureka 1.x 对 Eureka 2.x 的...

1010
来自专栏你不就像风一样

史上超全面的Elasticsearch使用指南

elasticsearch简写es,es是一个高扩展、开源的全文检索和分析引擎,它可以准实时地快速存储、搜索、分析海量的数据。

1.1K2
来自专栏JadePeng的技术博客

RPC框架原理与实现

RPC,全称 Remote Procedure Call(远程过程调用),即调用远程计算机上的服务,就像调用本地服务一样。那么RPC的原理是什么呢?了解一个技术...

6587
来自专栏chenssy

【死磕Netty】-----服务端启动过程分析

原文出处http://cmsblogs.com/ 『chenssy』 转载请注明原创出处,谢谢! 上篇博客(【死磕Netty】----Netty的核心组件及其设...

4517
来自专栏Kubernetes

Kubernetes Node Controller源码分析之执行篇

Author: xidianwangtao@gmail.com Node Controller的执行 Node Controller的Run方法如下,这是...

44511
来自专栏日常分享

Spring 学习笔记(五)—— Bean之间的关系、作用域、自动装配

  Spring提供了配置信息的继承机制,可以通过为<bean>元素指定parent值重用已有的<bean>元素的配置信息。

922

扫码关注云+社区