Spring监听器的使用


  • 事件

我们不仅可以使用spring给我们定义好的事件,我们自己也可以自定义一个事件,只需要继承一个spirng的事件类ApplicationEvent就可以了,想了解更详细的可以参考上面的文章。

比如下面就是一个简单的事件

public class MyContextEvent extends ApplicationEvent {

	/**
	 * Create a new ApplicationEvent.
	 *
	 * @param source the object on which the event initially occurred (never {@code null})
	 */
	public MyContextEvent(Object source) {
		super(source);
	}
}
  • 监听器

同样的我们需要一个监听器去监听这个事件,在spring中提供了两种方式去监听事件,一种是继承的方式,一种是注解的方式,这两种方式形式上不同,但底层实现是一样的,这个会放在后面分析。

  1. 继承的方式

我们可以通过接口ApplicationListener接口的方式实现监听,在泛型中我们可以指定需要监听的事件。

下面的监听器就监听了上面我们自定义的事件,如果出发了事件,spring就会主动去调用下面的onApplicationEvent方法,通过MyContextEvent参数我们可以拿到事件源和事件对象。

@Component
public class MyContextEventListener implements ApplicationListener<MyContextEvent> {

    @Override
    public void onApplicationEvent(MyContextEvent myContextEvent) {
        System.out.println("事件源:"+myContextEvent.getSource());
        System.out.println("触发事件");
    }
}
  1. 注解的方式

这种方式更加简单,只需要在一个方法上加上@EventListener注解就可以了,然后在参数中指定需要监听的事件就可以了,这个方法就相当于上面的onApplicationEvent方法。

@Component
public class MyContextEventListener{
    @EventListener
    public void onEvent(MyContextEvent event){
        System.out.println("事件源:"+event.getSource());
        System.out.println("触发事件");
    }

}
  • 触发事件

最后就是触发事件了

@Component
public class Trigger {

    // spring容器
    @Autowired
    ApplicationContext applicationContext;

    /**
     * 触发事件
     */
    public void trigEvent(){
        // 触发事件
        applicationContext.publishEvent(new MyContextEvent(applicationContext));
    }
}

自己实现事件与监听

通过上面spring的监听器的使用,我们也可以自己实现一个监听器,同样实现spring的监听功能。

  • 事件

首先我们也需要像spring中的ApplicationEvent一样的事件父类,这里就定义了一个空的事件类。

public class ApplicationEvnt {
}

然后我们就可以创建一个事件类,同样是继承上面这个类。

public class AEvent extends ApplicationEvnt {
}
  • 监听器

监听器我们同样需要一个接口才能统一规定标准,也为了更好的抽象,下面就是监听器的接口

public interface ApplicationListener< E extends ApplicationEvnt  > {
//    void  onEvnts(String ... a);
    void onEvnt(E e);
}

然后创建一个监听AEvent事件的监听器

public class AListener  implements ApplicationListener<AEvent>{
    @Override
    public void onEvnt(AEvent aEvent) {
        System.out.println("监听到了A事件");
    }
}
  • 事件管理器

我们还需要一个事件管理器,这个类在spring中也是同样存在的,负责在我们在触发事件的时候将调用监听器的方法。

在这个类中会保存所有的监听器,并且事件的触发也就由它实现的,在触发的时候就会根据事件类型回调相应的监听器方法。

//事件管理器
public class ListenerManage {

    //保存所有的监听器
    static List<ApplicationListener<?>> list = new ArrayList<>();

    //添加监听器  //如果要做得优雅一点 可以考虑扫描项目
    //定义注解
    public static void   addListener(ApplicationListener listener){
        list.add(listener);
    }
    //????
    //判断一下有哪些人对这个时间感兴趣
    public static void pushEvent(ApplicationEvnt evnt){
        for (ApplicationListener applicationListener : list) {
            //拿泛型
            Class tClass = (Class)((ParameterizedType)applicationListener.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0];
            //判断一下泛型
//            tClass.isAssignableFrom()
            if (tClass.equals(evnt.getClass())) {
                applicationListener.onEvnt(evnt);
            }
        }
    }
}

使用示例

假设让你开发一个文件操作帮助类,定义一个文件读写方法,读取某个文件,写到某个类里面去,这个很简单。

下面这个方法就可以是实现我们的目的

public static void fileWrite(InputStream is, OutputStream os) throws Exception{
    BufferedInputStream bis = new BufferedInputStream(is);
    BufferedOutputStream bos  = new BufferedOutputStream(os);
    //文件总大小
    int fileSize = is.available();
    //一共读取了多少
    int readSize = 0;
    byte[] b = new byte[READ_SIZE];
    boolean f = true;
    while (f){
        //文件实在小于第一次读的时候
        if (fileSize<READ_SIZE){
            byte[] bytes = new byte[fileSize];
            bis.read(bytes);
            bos.write(bytes);
            readSize = fileSize;
            f = false;
//                break;
            //当你是最后一次读的时候
        }else if(fileSize<readSize+READ_SIZE){
            byte[] bytes = new byte[fileSize-readSize];
            readSize = fileSize;
            bis.read(bytes);
            bos.write(bytes);
            f = false;
//                break;
        }else{
            bis.read(b);
            readSize +=READ_SIZE;
            bos.write(b);
        }
    }
    bis.close();
    bos.close();
}

但是如果后期突然有了要求,需要在读写的时候显示一个进度条。我们知道这个要求和文件读写本身是没有什么关系的,这里我们就可以借助监听器实现,只要我们在每一次循环的时候将读取的字节和总字节通过事件传给监听器,监听器就可以将进度条需要的百分比传给前端或者需要的地方。

首先定义一个读写事件,主要就是存储两个变量,总的字节和已经读写的字节

public class FileUploadEvent extends ApplicationEvnt {


    private int fileSize;

    private int readSize;


    public FileUploadEvent(int fileSize, int readSize) {
        this.fileSize = fileSize;
        this.readSize = readSize;
    }

    public int getFileSize() {
        return fileSize;
    }

    public void setFileSize(int fileSize) {
        this.fileSize = fileSize;
    }

    public int getReadSize() {
        return readSize;
    }

    public void setReadSize(int readSize) {
        this.readSize = readSize;
    }
}

其次就是它的监听器,主要就是算出进度条的百分比

public class FileUploadListener implements ApplicationListener<FileUploadEvent> {
    @Override
    public void onEvnt(FileUploadEvent evnt) {
        double i1 = evnt.getFileSize();
        double d = evnt.getReadSize()/i1;
//                map.put("文件ID",d*100);
//                map.remove("文件ID")
        System.out.println("当前文件上传进度百分比:"+d*100+"%");
    }
}

最后对于读写的实现也需要做一些改动,而且为了不改变原有的方法,我们将之间的方法实现一个方法的重载。

//有时候调用文件读取不需要进度条
//有时候需要进度条 如何实现?
public class FileUtil {

    public static int READ_SIZE= 100;


    public  static  void fileWrite(InputStream is, OutputStream os) throws Exception{
        fileWrite(is,os,null);
    }

    public static void fileWrite(InputStream is, OutputStream os,FileListener fileListener) throws Exception{
        BufferedInputStream bis = new BufferedInputStream(is);
        BufferedOutputStream bos  = new BufferedOutputStream(os);
        //文件总大小
        int fileSize = is.available();
        //一共读取了多少
        int readSize = 0;
        byte[] b = new byte[READ_SIZE];
        boolean f = true;
        while (f){
            //文件实在小于第一次读的时候
            if (fileSize<READ_SIZE){
                byte[] bytes = new byte[fileSize];
                bis.read(bytes);
                bos.write(bytes);
                readSize = fileSize;
                f = false;
//                break;
                //当你是最后一次读的时候
            }else if(fileSize<readSize+READ_SIZE){
                byte[] bytes = new byte[fileSize-readSize];
                readSize = fileSize;
                bis.read(bytes);
                bos.write(bytes);
                f = false;
//                break;
            }else{
                bis.read(b);
                readSize +=READ_SIZE;
                bos.write(b);
            }



            if (fileListener!=null){
                fileListener.updateLoad(fileSize,readSize);
            }


           ListenerManage.pushEvent(new FileUploadEvent(fileSize,readSize));
        }
        bis.close();
        bos.close();
    }


  static  Map<String,Double>  map ;
    public static void main(String[] args) throws Exception {
        ListenerManage.addListener(new FileUploadListener());
        ListenerManage.addListener(new AListener());
        ListenerManage.addListener(new BListener());
        ListenerManage.addListener(new AAListener());

        Scanner scanner = new Scanner(System.in);


        while (true){
            System.out.println("你要发布什么事件:");
            String scan = scanner.next();
            if (scan.equals("A")){
                ListenerManage.pushEvent(new AEvent());
            }else{
                ListenerManage.pushEvent(new BEvent());
            }
        }
//        File file = new File("c://写我.txt");
//        if (!file.exists()) {
//            file.createNewFile();
//        }
//        fileWrite(new FileInputStream(new File("c://读我!!!!!.txt")), new FileOutputStream(file));
    }
}

Spring监听器的底层实现

想要分析spring的监听器是如何实现的,核心同样的上面我们模拟实现的事件管理器,spring中同样也有一个类似的类。

不过我们先从事件触发的方法开始分析

spring boot 监听的event是线程安全的吗 spring事件监听线程池_监听器


这里底层调用的就是AbstractApplicationContext类的publishEvent方法

spring boot 监听的event是线程安全的吗 spring事件监听线程池_后端_02


主要注意下面的这个方法,这个方法得到了事件管理器去处理事件

spring boot 监听的event是线程安全的吗 spring事件监听线程池_java_03


这里就需要介绍一下spring的事件管理器ApplicationEventMulticaster的实现类

spring boot 监听的event是线程安全的吗 spring事件监听线程池_java_04


下面是监听器的处理方法multicastEvent,可以看出来,这里的逻辑几乎和上面模拟实现的事件管理器一致,只不过多了一个线程池的使用,可以异步处理监听器的回调方法。这种处理比单线程执行更好,因为可以避免监听器程序的错误给主线程带来的影响,还可以提高效率。

需要注意的是这里对事件感兴趣的监听器会封装在一个对象中,这个对象最后放在一个缓存中,如果是第二次获取会直接从缓存中拿到这个对象。

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
	// 拿到事件的类型
	ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
	// 遍历监听器,如果符合条件就回调监听器的方法
	for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
		// 如果配置了spring的线程池会使用线程执行监听器的回调方法
		Executor executor = getTaskExecutor();
		if (executor != null) {
			executor.execute(() -> invokeListener(listener, event));
		}
		else {
			invokeListener(listener, event);
		}
	}
}

现在我们只有一个问题了,就是上面监听器的集合是从哪里得来的,我们模拟实现的是直接通过一个add方法添加,但显然我们并没有在通过spring的类似方法添加监听器。

那么这个集合必定是spring帮我们创建并添加的,那么问题来了,spring是何时完成的呢?

我们知道spring中总共有两类监听器,一种是继承实现的,一种是注解实现的,这两种监听器主要的区别就是形式不同所带来的扫描查找方式不同。

这两个监听器都是在spring初始化阶段就放入的监听器集合。

  1. 继承实现的监听器

这种监听器是在refresh方法的registerListeners放入集合中的

spring boot 监听的event是线程安全的吗 spring事件监听线程池_java_05


spring boot 监听的event是线程安全的吗 spring事件监听线程池_java_06

  1. 注解实现的监听器

这种监听器则是在finishBeanFactoryInitialization方法中加入的集合,是属于bean的实例化的过程中实现的。

spring boot 监听的event是线程安全的吗 spring事件监听线程池_监听器_07


是在preInstantiateSingletons方法中实例化bean之后处理的,这里也是通过一个后置处理器实现的,EventListenerMethodProcessor后置处理器的afterSingletonsInstantiated方法

spring boot 监听的event是线程安全的吗 spring事件监听线程池_监听器_08


首先将所有带有@EventListener注解的方法找出来

spring boot 监听的event是线程安全的吗 spring事件监听线程池_后端_09


然后封装成一个ApplicationListener对象,就是前面封装继承实现的监听器的对象,所以说这两中监听器并没有什么区别。

spring boot 监听的event是线程安全的吗 spring事件监听线程池_后端_10


最后加入到集合中

spring boot 监听的event是线程安全的吗 spring事件监听线程池_监听器_11

配置spring中的线程池

在上面我们看到了如果我们配置了线程池spring会使用这个线程池去执行监听器的逻辑,这个关键就是注入一个SimpleApplicationEventMulticaster类,需要注意的是这个bean的名字必须是applicationEventMulticaster

至于ThreadPoolTaskExecutor则是我们配置线程池类

//BeanName 必须是这个
@Bean("applicationEventMulticaster")
public SimpleApplicationEventMulticaster simpleApplicationEventMulticaster(BeanFactory beanFactory, ThreadPoolTaskExecutor poolTaskExecutor){
    SimpleApplicationEventMulticaster simpleApplicationEventMulticaster
            = new SimpleApplicationEventMulticaster(beanFactory);
    simpleApplicationEventMulticaster.setTaskExecutor(poolTaskExecutor);
    return simpleApplicationEventMulticaster;
}

@Bean
public ThreadPoolTaskExecutor poolTaskExecutor(){
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setMaxPoolSize(15);
    threadPoolTaskExecutor.setCorePoolSize(10);
    threadPoolTaskExecutor.setQueueCapacity(30);
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
    threadPoolTaskExecutor.initialize();
    return threadPoolTaskExecutor;
}

这个线程池不只是在上面异步执行监听者的逻辑,对于spring中的需要异步操作都是有支持的,比如下面加上了@Async注解的方法spring就会异步执行,使用的就是我们上面配置的线程池。

spring boot 监听的event是线程安全的吗 spring事件监听线程池_java_12