前言
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);
}
//...
}
启动
经过上述配置,就可以启动来观察结果了: