在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 ...
已经调用了监听方法。在接下来的开发中,就可以使用这个注解注册监听器了。
- 数据城堡参赛代码实战篇(六)---使用sklearn进行数据标准化及参数寻优
- 震惊!Vector两行代码求逆序对,六行代码过普通平衡树
- 数据城堡参赛代码实战篇(五)---使用sklearn解决分类问题
- 洛谷P1894 [USACO4.2]完美的牛栏The Perfect Stall
- [编程经验]Python生成器、迭代器与yield语句小结
- TensorFlow从0到1 - 12 - TensorFlow构建3层NN玩转MNIST
- 数据城堡参赛代码实战篇(四)---使用pandas合并数据表
- HDU 2586 How far away ?
- HDU 3078 Network
- 数据城堡参赛代码实战篇(三)---我们来探究一个深奥的问题!
- 数据城堡参赛代码实战篇(二)---使用pandas进行数据去重
- 洛谷P3375 【模板】KMP字符串匹配
- Day5下午解题报告1
- [编程经验] Python中处理时间的方法小结
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- CORS跨域资源共享及解决方案
- Vue 父子组件通信 兄弟组件通信 深层组件通信 方式一览
- Vue使用Element实现增删改查+打包
- 状态管理之Vuex (二) 异步管理
- Spring中AOP相关的API及源码解析,原来AOP是这样子的
- 状态管理之Vuex (一) 基操勿六
- 形式化分析工具(五)使用CAS +语法轻松编写HLPSL规范
- 你知道Spring是怎么将AOP应用到Bean的生命周期中的吗?
- 太实用了!自己动手写软件——密码验证器的界面实现
- 【TBase开源版测评】深度测评TBase的shard分片和冷热分离存储特性
- Python爬虫练手,一个简单的Python资讯采集案例
- 直播带货软件开发过程中,如何实现图片上传
- 太实用了!自己动手写软件——邮件用户名密码验证
- 太实用了!自己动手写软件——SSH、FTP和SQL server的密码破解
- Kaggle Tweet Sentiment Extraction 第七名复盘