一.Servlet域?qū)ο笈c屬性變化監(jiān)聽
1.1 Servlet監(jiān)聽器定義
Servlet 監(jiān)聽器是 Servlet 規(guī)范中定義的一種特殊類摹蘑,用于監(jiān)聽 ServletContext剑勾、HttpSession 和 ServletRequest 等作用域?qū)ο蟮膭?chuàng)建與銷毀事件,以及監(jiān)聽這些作用域?qū)ο笾袑傩园l(fā)生修改的事件笙蒙。監(jiān)聽器使用了設(shè)計模式中的觀察者模式唯卖,它關(guān)注特定事物的創(chuàng)建黍瞧、銷毀以及變化并做出回調(diào)動作尚困,因此監(jiān)聽器具有異步的特性。
Servlet Listener 監(jiān)聽三大域?qū)ο蟮膭?chuàng)建和銷毀事件劳翰,三大對象分別是:
- ServletContext Listener:application 級別敦锌,整個應(yīng)用只存在一個,所有用戶使用一個ServletContext
- HttpSession Listener:session 級別佳簸,同一個用戶的瀏覽器開啟與關(guān)閉生命周期內(nèi)使用的是同一個session
- ServletRequest Listener:request 級別乙墙,每一個HTTP請求為一個request
除了監(jiān)聽域?qū)ο蟮膭?chuàng)建和銷毀,還可以監(jiān)聽域?qū)ο笾袑傩园l(fā)生修改的事件生均。
- HttpSessionAttributeListener
- ServletContextAttributeListener
- ServletRequestAttributeListener
1.2 Servlet監(jiān)聽器實現(xiàn):
@Slf4j
@WebListener
public class CustomListener implements ServletContextListener,
ServletRequestListener,
HttpSessionListener,
ServletRequestAttributeListener {
@Override
public void contextInitialized(ServletContextEvent se) {
log.info("==============context創(chuàng)建");
}
@Override
public void contextDestroyed(ServletContextEvent se) {
log.info("==============context銷毀");
}
@Override
public void requestDestroyed(ServletRequestEvent sre) {
log.info(" ++++++++++++++++++request監(jiān)聽器:銷毀");
}
@Override
public void requestInitialized(ServletRequestEvent sre) {
log.info(" ++++++++++++++++++request監(jiān)聽器:創(chuàng)建");
}
@Override
public void sessionCreated(HttpSessionEvent se) {
log.info("----------------session創(chuàng)建");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
log.info("----------------session銷毀");
}
@Override
public void attributeAdded(ServletRequestAttributeEvent srae) {
log.info("----------------attributeAdded");
}
@Override
public void attributeRemoved(ServletRequestAttributeEvent srae) {
log.info("----------------attributeRemoved");
}
@Override
public void attributeReplaced(ServletRequestAttributeEvent srae) {
log.info("----------------attributeReplaced");
}
}
在啟動類中加入@ServletComponentScan進行自動注冊即可听想。
1.3 Servlet監(jiān)聽器測試:
@Slf4j
@RequestMapping("/api")
@RestController
public class DemoController {
@GetMapping("/hello")
public String hello(HttpServletRequest request, HttpSession session){
request.setAttribute("name", "William");
request.setAttribute("name", "Jerry");
request.getAttribute("name");
request.removeAttribute("age");
session.setAttribute("name", "William");
session.getAttribute("name");
session.invalidate();
return "hello";
}
}
二.Servlet過濾器
2.1 定義及使用場景
Servlet 過濾器是可用于 Servlet 編程的 Java 類,有以下目的:
- 在客戶端的請求訪問后端資源之前马胧,攔截這些請求汉买。
- 在服務(wù)器的響應(yīng)發(fā)送回客戶端之前,處理這些響應(yīng)佩脊。
在實際的應(yīng)用開發(fā)中蛙粘,我們經(jīng)常使用過濾器做以下的一些事情
- 基于一定的授權(quán)邏輯,對HTTP請求進行過濾威彰,從而保證數(shù)據(jù)訪問的安全出牧。比如:判斷請求的來源IP是否在系統(tǒng)黑名單中
- 對于一些經(jīng)過加密的HTTP請求數(shù)據(jù),進行統(tǒng)一解密抱冷,方便后端資源進行業(yè)務(wù)處理
- 或者我們社交應(yīng)用經(jīng)常需要的敏感詞過濾崔列,也可以使用過濾器梢褐,將觸發(fā)敏感詞的非法請求過濾掉
過濾器主要的特點在于:一是可以過濾所有請求旺遮,二是它能夠改變請求的數(shù)據(jù)內(nèi)容赵讯。
2.2 過濾器的實現(xiàn)
@WebFilter(filterName="customFilter",urlPatterns={"/*"})
@Slf4j
public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("filter 初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
log.info("customFilter 請求處理之前----doFilter方法之前過濾請求");
//對request、response進行一些預(yù)處理
// 比如設(shè)置請求編碼
// request.setCharacterEncoding("UTF-8");
// response.setCharacterEncoding("UTF-8");
//鏈路 直接傳給下一個過濾器
chain.doFilter(request, response);
log.info("customFilter 請求處理之后----doFilter方法之后處理響應(yīng)");
}
@Override
public void destroy() {
log.info("filter 銷毀");
}
}
然后在啟動類加入@ServletComponentScan注解即可耿眉。
三.Spring攔截器
3.1 攔截器定義
在 Servlet 規(guī)范中并沒有攔截器的概念边翼,它是在Spring框架內(nèi)衍生出來。
Spring中攔截器有三個方法:
- preHandle 表示被攔截的URL對應(yīng)的控制層方法鸣剪,執(zhí)行前的自定義處理邏輯
- postHandle 表示被攔截的URL對應(yīng)的控制層方法组底,執(zhí)行后的自定義處理邏輯,此時還未將modelAndView進行頁面渲染筐骇。
- afterCompletion 表示此時modelAndView已做頁面渲染债鸡,執(zhí)行攔截器的自定義處理。
3.2 攔截器與過濾器的核心區(qū)別
從請求處理的生命周期上看铛纬,攔截器Interceptor和過濾器filter的作用是類似的厌均。過濾球能做的事情,攔截器幾乎也都能做告唆。
但是二者使用場景還是有一些區(qū)別的:
- 規(guī)范不同:Filter是在Servlet規(guī)范中定義的組件棺弊,在servlet容器內(nèi)生效。而攔截器是Spring框架支持的擒悬,在Spring 上下文中生效模她。
- 攔截器可以獲取并使用Spring IOC容器中的bean,但過濾器就不行懂牧。因為過濾器是Servlet的組件侈净,而IOC容器的bean是Spring框架內(nèi)使用,攔截器恰恰是Spring框架內(nèi)衍生出來的僧凤。
- 攔截器可以訪問Spring上下文值對象用狱,如ModelAndView,過濾器不行拼弃∠囊粒基于與上一點同樣的原因。
- 過濾器在進入servlet容器之前處理請求吻氧,攔截器在servlet容器之內(nèi)處理請求溺忧。過濾器比攔截器的粒度更大,比較適合系統(tǒng)級別的所有API的處理動作盯孙。比如:權(quán)限認(rèn)證鲁森,Spring Security就大量的使用了過濾器。
- 攔截器相比于過濾器粒度更小振惰,更適合分模塊歌溉、分范圍的統(tǒng)一業(yè)務(wù)邏輯處理。比如:分模塊的、分業(yè)務(wù)的記錄審計日志痛垛。(后面在日志的管理的那一章草慧,我們會為介紹使用攔截器實現(xiàn)統(tǒng)一訪問日志的記錄)
3.3 攔截器的實現(xiàn)
1.定義攔截器:
@Slf4j
@Component
public class CustomHandlerInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle:請求前調(diào)用");
//返回 false 則請求中斷
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle:請求后調(diào)用");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion:請求調(diào)用完成后回調(diào)方法,即在視圖渲染完成后回調(diào)");
}
}
2.通過實現(xiàn)WebMvcConfigurer接口完成攔截器的注冊:
@Configuration
public class MyWebMvcConfigurer implements WebMvcConfigurer {
@Resource
private CustomHandlerInterceptor customHandlerInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注冊攔截器 攔截規(guī)則
//多個攔截器時 以此添加 執(zhí)行順序按添加順序
registry.addInterceptor(customHandlerInterceptor);
}
}
四.Spring事件監(jiān)聽
4.1.事件監(jiān)聽的角色
首先我們要理解事件監(jiān)聽中需要的幾個角色
- 事件發(fā)布者 (即事件源)
- 事件監(jiān)聽者
- 事件本身
4.2. 事件監(jiān)聽的使用場景
為了將技術(shù)問題簡單化匙头,為大家舉一個簡單的例子漫谷。比如居委會發(fā)布停水通知。居委會就是事件源蹂析、停水就是事件本身舔示、該居委會的轄區(qū)居民就是事件監(jiān)聽者。大家看這個例子电抚,有這樣幾個特點:
- 異步處理:居委會工作人員發(fā)布通知之后惕稻,就可以去忙別的工作了,不會原地等待所有居民的反饋蝙叛。
- 解耦:居委會和居民之間是解耦的缩宜,互相不干擾對方的工作狀態(tài)與生活狀態(tài)。
- 不規(guī)律性:對于停水的事件發(fā)生頻率是不規(guī)律的甥温,觸發(fā)規(guī)則相對隨機锻煌。
當(dāng)你在一個系統(tǒng)的業(yè)務(wù)需求中,滿足上面的幾個特點中的2點姻蚓,就應(yīng)該考慮使用事件監(jiān)聽機制實現(xiàn)業(yè)務(wù)需求宋梧。當(dāng)然實現(xiàn)事件監(jiān)聽機制有很的方法,比如:
- 使用消息隊列中間件的發(fā)布訂閱模式
- JDK自帶的java.util.EventListener
- 本節(jié)為大家介紹的是:Spring環(huán)境下的實現(xiàn)事件發(fā)布監(jiān)聽的方法
4.3 代碼實現(xiàn)
1.自定義事件
繼承自ApplicationEvent抽象類狰挡,然后定義自己的構(gòu)造器捂龄。
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
2.自定義事件監(jiān)聽器
@Slf4j
@Component
public class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info(String.format("%s監(jiān)聽到事件源:%s.", MyListener.class.getName(), event.getSource()));
}
}
4.4 測試監(jiān)聽事件的發(fā)布
@Slf4j
@RequestMapping("/api")
@RestController
public class DemoController {
@Resource
private ApplicationContext applicationContext;
@GetMapping("/publish")
public void publishEvent() {
applicationContext.publishEvent(new MyEvent("測試事件."));
}
}
五.SpringBoot啟動監(jiān)聽器
5.1 簡介
Spring Boot提供了兩個接口:CommandLineRunner、ApplicationRunner加叁,用于啟動應(yīng)用時做特殊處理倦沧,這些代碼會在SpringApplication的run()方法運行完成之前被執(zhí)行。相對于之前介紹的Spring的ApplicationListener接口自定義監(jiān)聽器它匕、Servlet的ServletContextListener監(jiān)聽器展融。使用二者的好處在于,可以方便的使用應(yīng)用啟動參數(shù)豫柬,根據(jù)參數(shù)不同做不同的初始化操作告希。
5.2 常用場景介紹
實現(xiàn)CommandLineRunner、ApplicationRunner接口烧给。通常用于應(yīng)用啟動前的特殊代碼執(zhí)行燕偶,比如:
- 將系統(tǒng)常用的數(shù)據(jù)加載到內(nèi)存
- 應(yīng)用上一次運行的垃圾數(shù)據(jù)清理
- 系統(tǒng)啟動成功后的通知的發(fā)送
5.3 啟動監(jiān)聽器實現(xiàn)
1.CommandLineRunner:參數(shù)是字符串?dāng)?shù)組
@Slf4j
@Component
public class CommandLineStartupRunner implements CommandLineRunner {
@Override
public void run(String... args){
log.info("CommandLineRunner傳入?yún)?shù):{}", Arrays.toString(args));
}
}
2.ApplicationRunner:參數(shù)被放入ApplicationArguments,通過getOptionNames()础嫡、getOptionValues()指么、getSourceArgs()獲取參數(shù)
@Slf4j
@Component
public class AppStartupRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
log.info("ApplicationRunner參數(shù)名稱: {}", args.getOptionNames());
log.info("ApplicationRunner參數(shù)值: {}", args.getOptionValues("age"));
log.info("ApplicationRunner參數(shù): {}", Arrays.toString(args.getSourceArgs()));
}
}
5.4總結(jié)
CommandLineRunner、ApplicationRunner的核心用法是一致的,就是用于應(yīng)用啟動前的特殊代碼執(zhí)行伯诬。ApplicationRunner的執(zhí)行順序先于CommandLineRunner晚唇;ApplicationRunner將參數(shù)封裝成了對象,提供了獲取參數(shù)名姑廉、參數(shù)值等方法,操作上會方便一些翁涤。
當(dāng)定義多個監(jiān)聽器時桥言,SpringBoot是通過遍歷來啟動所有的Runner,只有上一個Runner執(zhí)行完成之后葵礼,才會執(zhí)行下一個Runner号阿,是同步執(zhí)行的。如果在某個Runner實現(xiàn)是run方法體中調(diào)用了同步阻塞的API或者是一個 while(true) 循環(huán)鸳粉,在遍歷中處于該Runner之后的其他實現(xiàn)將不會被執(zhí)行扔涧。