1. 程式人生 > 其它 >在spring中使用自定義註解註冊監聽器

在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 ...

已經呼叫了監聽方法。在接下來的開發中,就可以使用這個註解註冊監聽器了。