1.1、监听器介绍

什么是web监听器? web监听器是一种Servlet中特殊的类,它们能帮助开发者监听web中特定的事件,比如ServletContext,

HttpSession,ServletRequest的创建和销毁. 变量的创建、销毁和修改等,可以在某些动作前后增加处理,实现监控

1.2、监听器使用例子

web 监听器的使用场景很多,比如监听 servlet 上下文用来初始化一些数据、监听 http session 用来获取当前在线的人数、监听客

户端请求的 servlet request 对象来获取用户的访问信息等等. 这一节中,我们主要通过这三个实际的使用场景来学习一下 Spring Boot

中监听器的使用.

1.2.1、监听Servlet上下文对象

监听 servlet 上下文对象可以用来初始化数据,用于缓存. 什么意思呢?我举一个很常见的场景,比如用户在点击某个站点的首页时,

一般都会展现出首页的一些信息,而这些信息基本上或者大部分时间都保持不变的,但是这些信息都是来自数据库. 如果用户的每次

点击,都要从数据库中去获取数据的话,用户量少还可以接受,如果用户量非常大的话,这对数据库也是一笔很大的开销.

针对这种首页数据,大部分都不常更新的话,我们完全可以把它们缓存起来,每次用户点击的时候,我们都直接从缓存中拿,这样既可以

提高首页的访问速度,又可以降低服务器的压力. 如果做的更加灵活一点,可以再加个定时器,定期的来更新这个首页缓存. 就类似与

CSDN 个人博客首页中排名的变化一样.

下面我们针对这个功能,来写一个 demo,在实际中,读者可以完全套用该代码,来实现自己项目中的相关逻辑. 首先写一个 Service,模

拟一下从数据库查询数据

@Service
public class UserService {
User getUser(){
return new User(1, "lwh", 25);
}
}

然后写一个监听器,实现ApplicationListener接口,重写onApplicationEvent方法,将

ContextRefreshedEvent对象传进去. 如果我们想在加载或刷新应用上下文时,也重新刷新下我们预加载的资源,就可

以通过监听ContextRefreshedEvent来做这样的事情.如下:

@Component
public class MyServletContextListener implements ApplicationListener {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 先获取到applicationContext上下文
ApplicationContext ctx = event.getApplicationContext();
UserService userService = ctx.getBean(UserService.class);
User user = userService.getUser();
// 获取application域对象,将其放入application域中
ServletContext application = ctx.getBean(ServletContext.class);
application.setAttribute("user", user);
}
}

正如注释中描述的一样,首先通过contextRefreshedEvent来获取application上下文,再通过application上下文来获取

UserService这个bean,项目中可以根据实际业务场景,也可以获取其他的bean,然后再调用自己的业务代码获取相应的数据,

最后存储到application域中,这样前端在请求相应数据的时候,我们就可以直接从application域中获取信息,减少数据库的压力.

下面写一个Controller直接从application域中获取user信息来测试一下.

@Controller
public class ListenerController {
@GetMapping("/listener/user")
@ResponseBody
public User getUser(HttpServletRequest request){
ServletContext application = request.getServletContext();
return (User) application.getAttribute("user");
}
}
// 源码分析
// Check for listener beans and register them.
// 在Refresh方法中,这一步会将我们定义的监听器注册到容器中
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
// 这里面会发布ContextRefreshedEvent事件,我们自定义的监听器就可以获取到了
finishRefresh();
// registerListeners();
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
protected void finishRefresh() {
// Publish the final event.
// 删掉其他代码,这里面发布事件,会调用到我们自定义的监听方法,具体可以看第二篇监听器模式
publishEvent(new ContextRefreshedEvent(this));
}

1.2.2、监听HTTP会话Session对象

监听器还有一个比较常用的地方就是用来监听 session 对象,来获取在线用户数量,现在有很多开发者都有自己的网站,监听

session 来获取当前在下用户数量是个很常见的使用场景,下面来介绍一下如何来使用.

@Component
public class MyHttpSessionListener implements HttpSessionListener {
// 记录在线用户数量
public Integer count = 0;
@Override
public void sessionCreated(HttpSessionEvent httpSessionEvent) {
System.out.println("用户上线了");
count++;
HttpSession session = httpSessionEvent.getSession();
session.setMaxInactiveInterval(1);
session.getServletContext().setAttribute("count", count);
}
@Override
public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
System.out.println("用户下线了");
count--;
httpSessionEvent.getSession().getServletContext().setAttribute("count", count);
}
}
// 让服务器记得原来那个session,即把原来的sessionId记录在浏览器中,下次再打开时,把这个 sessionId 传过去,
// 这样服务器就不会重新再创建了
@GetMapping("/listener/total2")
@ResponseBody
public String getTotalUser(HttpServletRequest request, HttpServletResponse response){
Cookie cookie;
try {
cookie = new Cookie("JSESSIONID", URLEncoder.encode(request.getSession().getId(), "UTF-8"));
cookie.setPath("/");
// 有效期设置两天
cookie.setMaxAge(48 * 60 * 60);
response.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Integer count = (Integer) request.getSession().getServletContext().getAttribute("count");
return "当前在线人数: " + count;
}

1.3、自定义监听器

在实际项目中,我们往往需要自定义一些事件和监听器来满足业务场景,比如在微服务中会有这样的场景:微服务 A 在处理完某个逻辑之后,

需要通知微服务 B 去处理另一个逻辑,或者微服务 A 处理完某个逻辑之后,需要将数据同步到微服务 B,这种场景非常普遍,这个时候,

我们可以自定义事件以及监听器来监听,一旦监听到微服务 A 中的某事件发生,就去通知微服务 B 处理对应的逻辑.

public class MyEvent extends ApplicationEvent {
private User user;
public MyEvent(Object source, User user) {
super(source);
this.user = user;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
@Component
public class MyEventListener implements ApplicationListener {
@Override
public void onApplicationEvent(MyEvent event) {
// 把事件的信息获取到
User user = event.getUser();
// 处理事件,实际项目中可以通知别的微服务或者处理其他逻辑等等
System.out.println("用户名: " + user.getName());
System.out.println("年龄:" + user.getAge());
}
}
@Service
public class UserService {
@Resource
private ApplicationContext ctx;
User publishEvent(){
User user = new User(1, "lwh", 25);
// 发布事件
MyEvent myEvent = new MyEvent(this, user);
ctx.publishEvent(myEvent);
return user;
}
}