在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 ...
已經呼叫了監聽方法。在接下來的開發中,就可以使用這個註解註冊監聽器了。