1.Servlet监听器
ServletContext、HttpSession、ServletRequest 的监听都是通过javax.servlet 包下定义的,他们都是servlet的特性,换句话说,他们不依赖于Spring,而依赖于web容器(tomcat容器),他们的加载优先于Spring容器- Servlet监听器有用处,但是没有Spring监听器用的多,用的方便;并且通常可以用
HttpServletRequest和HttpServletResponse实现相同的需求 - Servlet监听器提供了很多接口,在这些接口的实现类中需要重写接口方法,并且要制定对应的监听事件,监听事件也会直接影响监听目标
参考链接1 参考链接2 参考链接3统计在线人数 其实统计人数用httpServletRequest().getServletContext().setAttribute(); 来直接获取Servlet上下文实现
1.1Servlet上下文监听
- 补充:在Spring项目中,启动Spring会先启动Servlet(tomcat),Spring的init()紧跟其后
而关闭Spring项目会之后会立马destroyed()Servlet容器,因此这个ServletContext监听器可以完全被ApplicationListener监听器代替 ,ApplicationListener是Spring3.0沿用至今最流行的全局监听器
可以监听ServletContext对象的创建和删除以及属性的添加、删除和修改等操作。该监听器需要使用到如下两个接口类:
● ServletContextAttributeListener :监听对ServletContext属性的操作,如增加、删除、修改操作。
● ServletContextListener :监听ServletContext。
当创建ServletContext时,激发contextInitialized(ServletContextEvent sce) 方法;
当销毁ServletContext时,激发contextDestroyed(ServletContext- Event sce) 方法。
1.2Http会话监听
- 在Spring项目中对Session域进行监控仍然用的是
HttpSessionListener ,其来自于Servlet2.3 - 用来监听 Web 应用种的 Session 对象,通常用于统计在线情况。
可以监听Http会话活动情况、Http会话中属性设置情况,也可以监听Http会话的active、pasivate情况等。
该监听器需要使用到如下多个接口类:
● HttpSessionListener :监听HttpSession的操作。
当创建一个Session时,激发sessionCreated (SessionEvent se) 方法;
当销毁一个Session时,激发sessionDestroyed (HttpSessionEvent se) 方法。
● HttpSessionActivationListener :用于监听Http会话active、passivate情况。
● HttpSessionAttributeListener :监听HttpSession中属性的操作。
当在Session增加一个属性时,激发attributeAdded(HttpSessionBindingEvent se) 方法;
当在Session删除一个属性时,激发attributeRemoved(HttpSessionBindingEvent se)方法;
在Session属性被修改时,激发attributeReplaced(HttpSessionBindingEvent se) 方法。
1.3客户端Request请求监听
- 用来监听 Request 对象的属性操作。
- ServletRequestListener监听器来自于Servlet2.4
● ServletRequestListener 接口类。
● ServletRequestAttrubuteListener 接口类。
其实现类实现了ServletContextAttributeListener和ServletContextListener两个接口类中的5个方法:
● contextInitialized(ServletContextEvent s)方法用来初始化ServletContext对象。
● contextDestroyed(ServletContextEvent s)方法在上下文中删除某个属性时调用。
● attributeAdded(ServletContextAttributeEvent sa)方法在上下文中添加一个新的属性时调用。
● attributeReplaced(ServletContextAttributeEvent sa)方法在更新属性时调用。
● attributeRemoved(ServletContextAttributeEvent sa)方法在上下文中删除某个属性时会被调用。
2.Servlet监听事件
在实现了Servlet监听器接口的方法中,需要重写其接口抽象方法,而其抽象方法的形参,则对应了一个监听器对象
2.1不带Attribute监听事件的创建和销毁
@WebListener
public class MyServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
ServletContextListener.super.contextInitialized(sce);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContextListener.super.contextDestroyed(sce);
}}
————————————————————————————————————————————————————————————————————————
@WebListener
public class MyServletListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
HttpSessionListener.super.sessionCreated(se);
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSessionListener.super.sessionDestroyed(se);
}}
—————————————————————————————————————————————————————————————————————————
@WebListener
public class MyServletListener implements ServletRequestListener {
@Override
public void requestDestroyed(ServletRequestEvent sre) {
ServletRequestListener.super.requestDestroyed(sre);
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
ServletRequestListener.super.requestInitialized(sre);
}}
2.2带Attribute监听事件的增、删、改
@WebListener
public class MyServletListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent scae) {
ServletContextAttributeListener.super.attributeAdded(scae);
}
@Override
public void attributeRemoved(ServletContextAttributeEvent scae) {
ServletContextAttributeListener.super.attributeRemoved(scae);
}
@Override
public void attributeReplaced(ServletContextAttributeEvent scae) {
ServletContextAttributeListener.super.attributeReplaced(scae);
}}
其他两个关于Attribute监听的都是有三个抽象方法:added removed repalced
3.为什么大量出现ed过去式?
众所周知英语中ed结尾的动词都是过去完成式 我们发现 sessionCreated contextDestroyed Replaced等等 这些抽象方法都是ed结尾的过去式,那么可以 从中推断出:监听中的方法是在事件发生之后进行的。 因此我们可以解决一个疑惑:“我想在监听器方法中使用容器中的其他对象方法,会不会报错?” 答案是不会:“因为对context监听的ed方法一定触发在整个容器加载结束之后”
4. Spring监听器
两种方式:参考资料
4.1ApplicationListener接口实现监听
一般来说,在Spring项目中监听器只选择ApplicationListener,而对于监听事件spring提供了多种选择
ApplicationStartedEvent:spring boot启动监听类
ApplicationEnvironmentPreparedEvent:环境事先准备
ApplicationPreparedEvent:上下文context准备时触发
ApplicationReadyEvent:上下文已经准备完毕的时候触发
ApplicationFailedEvent:该事件为spring boot启动失败时的操作
- 他们
都是ApplicationEvent的抽象子类 ,触发的时机不同 - 例如用
ApplicationListener监听 ,那么以上5种子类事件都可以被监听到 - 你可以
自定义一个事件 ,继承于上面的子事件 ,来实现一些数据的绑定 ,不过一般不会用到
4.2@EventListener注解实现监听
@Component//需要注入IOC,无需实现任何接口
public class MySprigListenerAnno {
@EventListener//配置一个监听器,省去了写ApplicationListener,但必须明确指出形参中的监听事件
public void myEvent(ApplicationEvent event){
System.out.println("用注解的方式实现Spring监听器,当前:"+event.getClass().getName());
}
}
4.3重要的泛型
查看源码可知泛型决定了监听事件是谁 ,如果不指定,那么就是默认监听根父类ApplicationEvent
5.Servlet和Spring的初始化顺序
我们用一个案例直接看
5.1Serlvet:监听context
@WebListener
public class MyServletListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
/**
* ServletContextEvent的父类EventListener是没有contextInitialized(sce)方法的。
* 所以对于此处来说,调不调super.contextInitialized都无所谓
*/
ServletContextListener.super.contextInitialized(sce);
System.out.println("当前是Servlet初始化");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ServletContextListener.super.contextDestroyed(sce);
System.out.println("当前是Servlet销毁");
}}
5.2Spring:监听context
这里监听ApplicationEvent,相当于监听其所有子类事件的,他的子类很多,但是Spring启动的时候只有几个事件会被监听
@Component
@Order(1)
public class MySpringListener implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("当前是Spring的:"+event.getClass().getName()+"监听事件");
}}
5.3启动Spring
执行顺序大概是: tomcat初始化——Servlet启动——Servlet引擎——Servlet初始化完成——tomcat启动完成——Spring初始化
6.监听器的应用:Spring启动时就调用业务方法
-
通过监听ApplicationReadyEvent 或ApplicationStartedEvent 事件,就可以调用; -
注意started是过去式,即此时Spring的IOC容器已经启动完成,可以在此时调用IOC容器中的bean -
而Ready更是在Started之后,更可以直接@Autowired使用IOC容器中的bean @Service
public class TheFuckList {
@Autowired
RedisTemplate redisTemplate;
public List<String> getFuckList(){
// List fuckList = redisTemplate.boundListOps("list1").range(0, 10);
List fuckList = redisTemplate.opsForList().range("list1",0,10);
Iterator iterator = fuckList.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
return fuckList;
}
}
@Component
@Order(0)
public class MySpringListener2 implements ApplicationListener<ApplicationStartedEvent> {
@Autowired
TheFuckList theFuckList;
@Autowired
FuckListBean fuckListBean;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
fuckListBean.fuckList = theFuckList.getFuckList();
System.out.println(fuckListBean.fuckList.size());
}
}
7.多个Spring监听器执行顺序
以监听事件 为基准,监听器之间的顺序以@Order决定
@Component
@Order(-1)
public class MySpringListener implements ApplicationListener<ApplicationEvent> {//ApplicationStartedEvent
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("当前是Srp:"+event.getClass().getName()+"监听事件");
}
@Component
@Order(0)
public class MySpringListener2 implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("第二个监听器");
}
}
8.过滤器、拦截器、监听器执行顺序
- 注意,过滤器、拦截器是针对某个包下的controller接口被调用时而进行处理的;
- 监听器则可以深入到Servlet、Spring容器的创建之时执行。
- Filter过滤器的本质是:
回调 - Interceptor拦截器的本质是:
反射
参考资料
那么如果是过滤器,拦截器,监听器同时生效呢?
8.1Filter的过滤流程
- 在我们自定义的过滤器中都会实现一个 doFilter()方法,这个方法有一个FilterChain 参数,而实际上它是一个回调接口。ApplicationFilterChain是它的实现类, 这个实现类内部也有一个 doFilter() 方法就是回调方法。
- 回调的顺序由@Order来进行配置
- 过滤器Filter是在请求进入web容器后,但在进入servlet之前进行预处理,请求结束是在servlet处理完以后。
8.2Interceptor拦截器流程
- 对于Spring拦截器,一般直接实现
HandlerInterceptor接口 ,并重写三个抽象方法。 - 拦截器 Interceptor 是在请求进入servlet后,在进入Controller之前进行预处理的,Controller 中渲染了对应的视图之后请求结束。
参考资料1
8.3Controller请求调用顺序
controller 中所有的请求都要经过核心组件DispatcherServlet 路由,都会执行它的 doDispatch() 方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。
9.DispatcherServlet类:路由,中央调度器
-
HTTP请求处理程序/控制器的中央调度器
9.1doDispatch中央调度方法
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
try {
...........
try {
// 获取可以执行当前Handler的适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 注意: 执行Interceptor中PreHandle()方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 注意:执行Handle【包括我们的业务逻辑,当抛出异常时会被Try、catch到】
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
// 注意:执行Interceptor中PostHandle 方法【抛出异常时无法执行】
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
}
...........
}
9.2为何拦截器的pre和post执行顺序是栈顺序
看看两个方法applyPreHandle() 、applyPostHandle() 具体是如何被调用的,就明白为什么postHandle()、preHandle() 执行顺序是相反的了。
正是因为DispathcherServlet类中的doDispach()方法 进行调度的时候,实际上是调用了applyPreHandler()和applyPostHandler() ,而这两个方法中的for循环遍历interceptorList 数组时一个是i++ 一个是i-- ,所以表面上就成了入栈出栈的执行顺序
10.过滤器监听器几个注解及配置
@WebFilter
标注在 xxxxximplements Filter 的类上,即把Servlet过滤器 注入Servlet容器中,这是Servlet3.0的注解规范,被Spring5.0引入
@WebListener
标注在 xxxxx implements ServletContextListener 的类上,即把Servlet监听器 注入Servlet容器中,这是Servlet3.0的注解规范,被Spring5.0引入
@EventListener
@Component//需要注入IOC,无需实现任何接口
public class MySprigListenerAnno {
@EventListener//配置一个监听器,省去了写ApplicationListener,但必须明确指出形参中的监听事件
public void myEvent(ApplicationEvent event){
System.out.println("用注解的方式实现Spring监听器,当前:"+event.getClass().getName());
}
————————————如果不用注解就需要实现接口
@Component
@Order(0)
public class MySpringListener2 implements ApplicationListener<ApplicationEvent> {
@Override
public void onApplicationEvent(ApplicationEvent event) {
System.out.println("第二个监听器");
}
}
WebMvcConfigurer配置
为Spring容器注册HandlerInterceptor的实现类
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/login/web")
.excludePathPatterns("/login/no");
}
}
————————拦截器本身内容跟注册配置缺一不可
public class MyHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
long start = System.currentTimeMillis();
request.setAttribute("startTime", start);
return true;//直接放行
// return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
long start = (long) request.getAttribute("startTime");
long end = System.currentTimeMillis();
request.setAttribute("handleTime",end-start);
// HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info(
String.format("本次请求%s的%s接口耗时%s毫秒",
request.getRequestURL(),
request.getMethod(),
(long)request.getAttribute("handleTime")));
// HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
小结:
除了@EventListener可以不写实现类,其他都必须写
|