當前位置:
首頁 >
前端技术
> javascript
>内容正文
javascript
springmvc dao怎么可以不写实现类_SpringMVC(一)细聊ContextLoaderListener 是怎么被加载的...
生活随笔
收集整理的這篇文章主要介紹了
springmvc dao怎么可以不写实现类_SpringMVC(一)细聊ContextLoaderListener 是怎么被加载的...
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
點擊上方“藍字”關注我們,每天點亮一個技能點。
本文作者:burgxun
云析學院VIP學員
前言
為什么要寫博客呢?其實還是源于我在知乎上面的 看到的一個回答。問題是這樣的:生活中最浪費時間的事情有哪些?我看到下面的一個貼子回答是:學而不思,猶豫不決,看到這個 我想到 哇偶 總結的好到位!好了 廢話不多說,為什么要寫Spring MVC 呢 當然是因為我博主我最近在看Spring MVC 的源碼了!為什么開篇想講 2個容器呢?隨著Spring Boot 的越來越流行 我們之前配置XML 的方式也逐漸 被舍棄。我們剛使用Spring MVC的時候 一定也知道怎么去標準的配置 比如ContextLoaderListener DispatcherServlet等等 那為什么要有這些配置,這些配置又去怎么運行的呢?今天 我就從自己的理解 去分析下 , 理解不當的地方 請各位見諒,留言指出 多謝!PS: 這篇文章 有點跑題 重點講了一個程序 tomcat 是怎么去加載web配置 又是怎么樣將我們的ContextLoaderListener監聽類加入到監聽列表中的 最后串聯了下整個Servlet 容器怎么去啟動一個應用程序的~下面一片文章 我會重點去看下ContextLoaderListener 里面怎么去創建WebApplication容器的,這里面我也走了一點彎路 一開始以為 我們的ContextLoaderListene 是通過ApplicationContext中的addListener添加的 最終 我一步步發現 我想錯了!標準配置
來,首先回顧下我們的標準配置: org.springframework.web.context.ContextLoaderListener contextConfigLocation WEB-INF/configs/spring/applicationContext.xml mvc-dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/configs/spring/mvc-dispatcher-servlet.xml 1 mvc-dispatcher / 從上面的我的注釋中 也可以看看出 一個是Spring 的 一個是Spring MVC 的Spring Root容器
Spring Root容器 一般都是配置了非Controller的Bean 比如 Dao,Service 等等Bean都會在這個容器里面。我們看到 這個配置contextparam 中的節點 設置了ApplicationContext的config路徑 這個后面在源碼中 我會講到,先過~~ContextLoaderListener 是被怎么執行的
listener我們看到listener 這個節點配置了一個ContextLoaderListener類,那我們來詳細聊下 這個類 是做什么的首先 我們知道 服務在啟動的時候 會給我們應用提供了一個容器的上下文環境ServletContext 這個上下文環境 就是我們應用程序的宿主環境這邊簡答的說下ServletContenxt: 當我們的Servlet容器 也就是我們經常用的Tomcat或者Jetty 等 在web應用啟動的時候 會創建一個Servlet對象,這個對象可以被 web應用下面的所有的Servlet所訪問。也就是說一個web應用只有一個ServletContent.記得這點很重要,到后面的代碼中 我們就能知道 我們的WebApplicationContext 其實就是ServletContent的一個屬性Attribute。我們可以從Spring 項目的的一個ContextLoaderTests 單元測試類中 可以看出 @Test public void testContextLoaderListenerWithDefaultContext() { MockServletContext sc = new MockServletContext(""); sc.addInitParameter(ContextLoader.CONFIG_LOCATION_PARAM, "/org/springframework/web/context/WEB-INF/applicationContext.xml " + "/org/springframework/web/context/WEB-INF/context-addition.xml"); ServletContextListener listener = new ContextLoaderListener(); ServletContextEvent event = new ServletContextEvent(sc); listener.contextInitialized(event); WebApplicationContext context = (WebApplicationContext) sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); ...忽略???}仔細看下 WebApplicationContext的獲取方式 當然這個我后面也會在ContextLoaderListener的源碼中講到這邊配置的節點 就是把ContextLoaderListener 加入到 啟動的監聽列表里面 當程序啟動后 會用ServletContextEvent作為參數 去初始化listener 上面的單元測試代碼 也寫很清楚了ContextLoaderListener
public class ContextLoaderListener extends ContextLoader implements ServletContextListener{ public ContextLoaderListener() { } public ContextLoaderListener(WebApplicationContext context) { super(context); } /** * Initialize the root web application context. */ @Override public void contextInitialized(ServletContextEvent event) { initWebApplicationContext(event.getServletContext()); } /** * Close the root web application context. */ @Override public void contextDestroyed(ServletContextEvent event) { closeWebApplicationContext(event.getServletContext()); ContextCleanupListener.cleanupAttributes(event.getServletContext()); }}?ContextLoaderListener 繼承了ContextLoader類 實現了ServletContextListener接口其中contextInitialized,contextDestroyed 方法都是ServletContextListener接口里面的方法ServletContextListener
那我們看下ServletContextListener 這個類public interface ServletContextListener extends EventListener { public void contextInitialized(ServletContextEvent sce); public void contextDestroyed(ServletContextEvent sce);}這個接口也是很簡單 2個方法 而且接口繼承了 一個空的EventListener接口 這樣的寫法 是為了標識某一類的接口,程序有的地方 判斷的時候 用到這個。這樣的寫法 源碼中很多地方用到過。這邊這樣寫是為了 程序在啟動的時候 從xml 讀取到Listener節點的配置的類 只有實現了ServletContextListener接口的類 才能加入到監聽列表中那又是怎么加入 怎么去調用的呢?帶著 這個問題 我們一步步去看看我在上面 提到過 Servlet容器會在每個web程序啟動的時候會分配一個ServletContext的上下文,那下面我們看下這個ServletContext 是什么ServletContext首先我們看下 這個類 位于什么地方 javax.servlet-api-3.0.1-sources.jar!\javax\servlet\ServletContext.java這個類是位于javax.servlet-api 這個包里面的 我們都知道 這個包 定義了servlet的標準 web容器都是根據這些標準去實現的public interface ServletContext { public void addListener(String className); public void addListener(Class extends EventListener> listenerClass); public void addListener(T t); }這個是我截取了 我關心的方法看到這個方法的時候 我們就能明白 為什么 上面的ServletContextListener 要繼承了一個空的EventListener接口了吧!容器在啟動的時候 就是把web.xml 中配置的監聽類 加入到 分配到上下文環境ServletContext中的那我懷著好奇心 繼續尋找ServletContext的實現類ApplicationContext此類位于tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\core\ApplicationContext.java這個就是在tomcat中的了 tomcat本質就是一個Servlet容器 所以一定是要按照javax.servlet-api里面的定義的標準接口去實現的,話不多說 我們去看下代碼是怎么寫的,我截取了部分有關我講的代碼 有興趣的對照著源碼 看看 private final StandardContext context; public ApplicationContext(StandardContext context) { super(); this.context = context; this.service = ((Engine) context.getParent().getParent()).getService(); this.sessionCookieConfig = new ApplicationSessionCookieConfig(context); // Populate session tracking modes populateSessionTrackingModes(); } public void addListener(T t) { if (!context.getState().equals(LifecycleState.STARTING_PREP)) { throw new IllegalStateException( sm.getString("applicationContext.addListener.ise", getContextPath())); } boolean match = false; if (t instanceof ServletContextAttributeListener || t instanceof ServletRequestListener || t instanceof ServletRequestAttributeListener || t instanceof HttpSessionIdListener || t instanceof HttpSessionAttributeListener) { context.addApplicationEventListener(t); match = true; } if (t instanceof HttpSessionListener || (t instanceof ServletContextListener && newServletContextListenerAllowed)) { // Add listener directly to the list of instances rather than to // the list of class names. context.addApplicationLifecycleListener(t); match = true; } if (match) return;????}我們看到 ApplicationContext 初始化的是傳入了一個StandardContext對象 而且最終addListener 也是調用的StandardContext類中的addApplicationEventListener方法 看了下上面的代碼 監聽事件 還做了區分 放入了2個集合中StandardContext
那我們就來看下StandardContext 是怎么做的 public class StandardContext extends ContainerBase implements Context, NotificationEmitter { //這邊是一個對象鎖 后面添加applicationListeners的時候會用到 private final Object applicationListenersLock = new Object(); private String applicationListeners[] = new String[0]; private List applicationEventListenersList = new CopyOnWriteArrayList<>(); public void addApplicationEventListener(Object listener) { applicationEventListenersList.add(listener); } private Object applicationLifecycleListenersObjects[] = new Object[0]; public void addApplicationLifecycleListener(Object listener) { int len = applicationLifecycleListenersObjects.length; Object[] newListeners = Arrays.copyOf( applicationLifecycleListenersObjects, len + 1); newListeners[len] = listener; applicationLifecycleListenersObjects = newListeners; } @Override public void addApplicationListener(String listener) { synchronized (applicationListenersLock) { String results[] = new String[applicationListeners.length + 1]; for (int i = 0; i < applicationListeners.length; i++) { if (listener.equals(applicationListeners[i])) { log.info(sm.getString("standardContext.duplicateListener",listener)); return; } results[i] = applicationListeners[i]; } results[applicationListeners.length] = listener; applicationListeners = results; } fireContainerEvent("addApplicationListener", listener); } /** * Configure the set of instantiated application event listeners * for this Context. * @return true if all listeners wre * initialized successfully, or false otherwise. */ public boolean listenerStart() { // Instantiate the required listeners String listeners[] = findApplicationListeners();//就是返回 applicationListeners[]數組 Object results[] = new Object[listeners.length];//這邊是存儲 我們監聽類的是實例化后的對象 boolean ok = true; for (int i = 0; i < results.length; i++) { String listener = listeners[i]; results[i] = getInstanceManager().newInstance(listener);//實例化 我們的監聽類 } // 這個是吧 實例化的類 拆分成了2個監聽數組對象 不知道為啥 ServletContextListener 是單獨的一個 // 這個也對應了 applicationContext中的方法 也是按照這樣的類型 拆分了存儲的 ArrayList eventListeners = new ArrayList<>(); ArrayList lifecycleListeners = new ArrayList<>(); for (int i = 0; i < results.length; i++) { if ((results[i] instanceof ServletContextAttributeListener) || (results[i] instanceof ServletRequestAttributeListener) || (results[i] instanceof ServletRequestListener) || (results[i] instanceof HttpSessionIdListener) || (results[i] instanceof HttpSessionAttributeListener)) { eventListeners.add(results[i]); } if ((results[i] instanceof ServletContextListener) || (results[i] instanceof HttpSessionListener)) { lifecycleListeners.add(results[i]); } } for (Object eventListener: getApplicationEventListeners()) { eventListeners.add(eventListener); } setApplicationEventListeners(eventListeners.toArray()); for (Object lifecycleListener: getApplicationLifecycleListeners()) { lifecycleListeners.add(lifecycleListener); if (lifecycleListener instanceof ServletContextListener) { noPluggabilityListeners.add(lifecycleListener); } } setApplicationLifecycleListeners(lifecycleListeners.toArray()); Object instances[] = getApplicationLifecycleListeners(); if (instances == null || instances.length == 0) { return ok; } ServletContextEvent event = new ServletContextEvent(getServletContext()); ServletContextEvent tldEvent = null; if (noPluggabilityListeners.size() > 0) { noPluggabilityServletContext = new NoPluggabilityServletContext(getServletContext()); tldEvent = new ServletContextEvent(noPluggabilityServletContext); } for (int i = 0; i < instances.length; i++) { if (!(instances[i] instanceof ServletContextListener)) continue; ServletContextListener listener = (ServletContextListener) instances[i]; fireContainerEvent("beforeContextInitialized", listener); if (noPluggabilityListeners.contains(listener)) { listener.contextInitialized(tldEvent); } else { listener.contextInitialized(event); } fireContainerEvent("afterContextInitialized", listener); } return ok; } }看到這邊 我們才知道 ContextLoaderListener#contextInitialized 是怎么被調用起來的 listenerStart的方法 就是處理監聽類的地方最后 關注下 addApplicationListener 這個類方法 我的注釋 注釋上也說了 看到了這個方法 我才明白 我前面想的是錯的,存放我們的XML的監聽類的地方 其實是applicationListeners[] 數組那既然addApplicationListener是重寫的方法 他的父類是Context類最后我有查找了下addApplicationListener方法時 是在哪些地方使用的 這樣我們就知道我們的XML中配置的監聽類是什么加進入 剛才上面的數組的最后我在 tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\startup\ContextConfig.java 類中找到了這個方法的使用 看名字要也能知道 這個是一個Context的配置類這也能符合我們的想法 監聽類就是在配置Context的是加入的 那我們看下是否 值這樣的ContextConfig
configureContext public class ContextConfig implements LifecycleListener{ protected Context context = null; @Override public void lifecycleEvent(LifecycleEvent event) { context = (Context) event.getLifecycle(); if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) { configureStart();//開始配置 } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) { beforeStart(); } else if (event.getType().equals(Lifecycle.AFTER_START_EVENT)) { // Restore docBase for management tools if (originalDocBase != null) { context.setDocBase(originalDocBase); } } else if (event.getType().equals(Lifecycle.CONFIGURE_STOP_EVENT)) { configureStop(); } else if (event.getType().equals(Lifecycle.AFTER_INIT_EVENT)) { init(); } else if (event.getType().equals(Lifecycle.AFTER_DESTROY_EVENT)) { destroy(); } } /** * Process a "contextConfig" event for this Context. */ protected synchronized void configureStart() { webConfig();//開始webConfig配置 if (!context.getIgnoreAnnotations()) { applicationAnnotationsConfig(); } if (ok) { validateSecurityRoles(); } // Configure an authenticator if we need one if (ok) { authenticatorConfig(); } } protected void webConfig() { WebXml webXml = createWebXml();//創建WebXml對象 // Parse context level web.xml InputSource contextWebXml = getContextWebXmlSource();//獲取webXML的地址 if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) { ok = false; } ServletContext sContext = context.getServletContext(); // Step 9. Apply merged web.xml to Context if (ok) { configureContext(webXml);//這邊是拿到了webXml 開始配置 } } private void configureContext(WebXml webxml) { for (FilterDef filter : webxml.getFilters().values()) { if (filter.getAsyncSupported() == null) { filter.setAsyncSupported("false"); } context.addFilterDef(filter); } for (FilterMap filterMap : webxml.getFilterMappings()) { context.addFilterMap(filterMap); } for (String listener : webxml.getListeners()) { context.addApplicationListener(listener);//這邊就是 添加我們監聽類的地方 } } }仔細看下這個ContextConfig這個類 從中我們可以看到 Context的創建也是從這個類中完成的 這個記住下在繼續深入下
到了這邊 其實 已經講的差不多了 但是 小伙伴們 一定想知道Tomcat 究竟是怎么去調用到 上面的執行方法的我們知道listenerStart 中處理了監聽事件 那這個又是怎么去運行的呢 和Tomcat 又有什么關系呢,由于篇幅問題 我就不一一列出代碼了 大概的描述下1.tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\startup\Tomcat.java 中有一個Start()方法 protected Server server; public void start() throws LifecycleException { getServer(); getConnector(); server.start(); }??public?interface?Server?extends?Lifecycle這其中 server.start();方法是Lifecycle接口中
2. tomcat-embed-core-8.5.45-sources.jar!\org\apache\catalina\util\LifecycleBase.java
public abstract class LifecycleBase implements Lifecycle{ public final synchronized void start() throws LifecycleException{ startInternal();//這邊執行startInternal方法 } protected abstract void startInternal() throws LifecycleException;}public abstract class LifecycleMBeanBase extends LifecycleBasepublic abstract class ContainerBase extends LifecycleMBeanBase implements Container3.StandardContext 類 我們應該很熟悉了吧public class StandardContext extends ContainerBase implements Context, NotificationEmitter{ protected synchronized void startInternal() throws LifecycleException { // Configure and call application event listeners if (ok) { if (!listenerStart()) { log.error(sm.getString("standardContext.listenerFail")); ok = false; } } }}看到這個listenerStart 應該清楚了吧 ~結合我上面講的 就能串聯上了~到此 我們應該知道了 從Tomcat 到ContextLoaderListener.java 是怎么運行起來的吧最后還是給大家畫個小圖吧 簡單明了 方便記憶 畫圖工具用的markdown 自帶的 有點low 見諒哈總結一下
通過以上的分析 我們知道 ContextLoaderListener 是怎么被加入監聽列表 contextInitialized 方法時怎么執行的。●帶你領略史上最全—編譯部署EasyDarwin源碼【二次開發】【Linux】
●Thread類源碼(2)
●Thread類源碼分析(1)
●線程池(1)——線程池的使用
Java技術直播
點擊圖片直達課堂
覺得有幫助的話,點個“在看”吧!總結
以上是生活随笔為你收集整理的springmvc dao怎么可以不写实现类_SpringMVC(一)细聊ContextLoaderListener 是怎么被加载的...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 周三多管理学第七版pdf_为什么说管理学
- 下一篇: 王者荣耀明世隐怎么玩(如何玩好《王者荣耀