前言

Listener是非常基本的Java Servlet组件,通过Listener与TimerTask的结合使用可以非常便利地实现定时器的功能。在SpringMVC的时代,可以通过在web.xml配置文件中直接指定多个Listener来实现该功能,如下图所示:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
    <listener-class>com.demo.HelloListener</listener-class>
</listener>

实际上,在上面的代码片断中,可以看到Spring自身也是通过最基本的ServletContextListener来实现它的功能的。

在SpringBoot中,仅需通过注解即可代替上述的xml配置,而其它代码则无需变化。

开发环境

操作系统:Ubuntu 18.04

Java版本:JDK 1.8

开发工具:MyEclipse

中间件:Tomcat 9.x

TimerTask

与SpringMVC时代一样,我们也需要一个TimerTask来实现需要定时处理的业务逻辑:

package com.freezingxu.timer;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.util.TimerTask;

import javax.servlet.ServletContext;

public class HelloTimer extends TimerTask {
    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(HelloTimer.class.getName());
	
    private ServletContext servletContext;
	
    public HelloTimer(ServletContext servletContext) {
	super();
        this.servletContext = servletContext;
    }

    @Override
    public void run() {
	this.sayHello();
    }
	
    private void sayHello() {
	logger.info("Hello ^.^! I am a 'HelloTimer' running on the web listener! And,the web listener is implements ServletContextListener, based on Spring Boot Annotation!");
    }

}

上述代码片断中,类HelloTimer继承自java.util.TimerTask类,并且重载了它的run方法,在run方法内部,做了业务逻辑处理。

需要注意的是,这段代码特意使用了一个自定义的构造器,该构造器有一个ServletContext的入参,在web应用中,能否顺利在环境中获取到ServletContext有时候是至关重要的。

在这段代码中,虽然并没有真正使用到ServletContext,但是它演示了如何最简单地在TimerTask实例中获取到当前的ServletContext。

Listener

我们需要创建一个Listener,来帮助我们在项目启动时就能够及时调度起TimerTask类,进而以定时方式来处理我们的业务逻辑:

package com.freezingxu.web.listener;

import org.slf4j.LoggerFactory;

import com.freezingxu.timer.HelloTimer;

import java.util.Timer;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

import org.slf4j.Logger;

@WebListener
public class HelloListener implements ServletContextListener {
    /**
     * Logger for this class
     */
    private static final Logger logger = LoggerFactory.getLogger(HelloListener.class.getName());

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
	logger.info("HelloListener is destroyed");
	ServletContextListener.super.contextDestroyed(sce);
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
	logger.info("HelloListener is initialized and ready!");
	HelloTimer helloTimer = new HelloTimer(sce.getServletContext());
		
	/**
	 * 每1秒处理一次
	 */
	Timer timer = new Timer();
	timer.schedule(helloTimer, 0, 1000);
    }

	
}

如同上面的代码片段所展示的那样,这是一个最为基本的Listener类,它实现了ServletContextLitener接口,而该接口其实非常古老,时至今日仍然能够像过去一样被使用。

唯一不同的是,在这个类的头部,使用了“@WebListener”注解。这个注解就代替了过去在web.xml配置文件中配置Listener的功效。

启动类

最后一步,只需在SpringBoot启动类的头部添加注解“@ServletComponentScan”即可:

@ServletComponentScan
public class Application extends SpringBootServletInitializer {
    //...

    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);  
    }

    //...
}

一个常见的问题是,如果在项目工程中的包被设计得较为复杂,可能会忘记指明SpringBoot去扫描Listener所在的包。这个时候定时器类并不会被扫描到,于是也无法被正常启动。我们会看不到任何错误,这对新手来说不是太容易发现。

我们可以在SpringBoot启动类的头部,通过注解“@ComponentScan”来手动标明需要进行注解扫描的包:

@ComponentScan(
    basePackages = {
	"com.freezingxu.demo.......",    //一些其它需要扫描的包
	"com.freezingxu.web.listener"    //listener所在的包
    }
) 
@ServletComponentScan
public class Application extends SpringBootServletInitializer {
    //...

    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);  
    }

    //...
}

启动

经过上述配置,就可以启动来观察结果了:

springboot设置超时时间重新登录 springboot timer_spring

springboot设置超时时间重新登录 springboot timer_spring_02