麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁(yè) > 開(kāi)發(fā) > Java > 正文

一個(gè)applicationContext 加載錯(cuò)誤導(dǎo)致的阻塞問(wèn)題及解決方法

2024-07-14 08:42:50
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

問(wèn)題為對(duì)接一個(gè)sso的驗(yàn)證模塊,正確的對(duì)接姿勢(shì)為,接入一個(gè) filter, 然后接入一個(gè) SsoListener 。

  然而在接入之后,卻導(dǎo)致了應(yīng)用無(wú)法正常啟動(dòng),或者說(shuō)看起來(lái)很奇怪,來(lái)看下都遇到什么樣的問(wèn)題,以及是如何處理的?

還是 web.xml, 原本是這樣的: (很簡(jiǎn)潔!)

<?xml version="1.0" encoding="UTF-8" ?><web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"     version="3.0"> <display-name>xx-test</display-name> <filter>  <filter-name>encodingFilter</filter-name>  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  <init-param>   <param-name>encoding</param-name>   <param-value>UTF-8</param-value>  </init-param>  <init-param>   <param-name>forceEncoding</param-name>   <param-value>true</param-value>  </init-param> </filter> <filter-mapping>  <filter-name>encodingFilter</filter-name>  <url-pattern>/*</url-pattern> </filter-mapping> <servlet>  <servlet-name>spring</servlet-name>  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  <init-param>   <param-name>contextConfigLocation</param-name>   <param-value>classpath:spring/spring-servlet.xml</param-value>  </init-param>  <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping>  <servlet-name>spring</servlet-name>  <url-pattern>/</url-pattern> </servlet-mapping></web-app>

而需要添加的 filter 如下:

 <filter>  <filter-name>SessionFilter</filter-name>  <filter-class>com.xxx.session.RedisSessionFilter</filter-class> </filter> <filter-mapping>  <filter-name>SessionFilter</filter-name>  <url-pattern>/*</url-pattern> </filter-mapping> <listener>  <listener-class>com.xx.session.SSOHttpSessionListener</listener-class> </listener> <filter>  <filter-name>SSOFilter</filter-name>  <filter-class>com.xxx.auth.SSOFilter</filter-class> </filter> <filter-mapping>  <filter-name>SSOFilter</filter-name>  <url-pattern>/*</url-pattern> </filter-mapping> <context-param>  <param-name>configFileLocation</param-name>  <param-value>abc</param-value> </context-param>

  另外再加幾個(gè)必要的配置文件掃描!對(duì)接完成!不費(fèi)事! 

  然后,我坑哧坑哧把代碼copy過(guò)來(lái),準(zhǔn)備 commit 搞定收工!

  結(jié)果,不出所料,server 起不來(lái)了。也不完全是啟不來(lái)了,就只是啟起來(lái)之后,啥也沒(méi)有了。

  sso 中也沒(méi)啥東西,就是攔截下 header 中的值,判定如果沒(méi)有登錄就的話,就直接返回到 sso 的登錄頁(yè)去了。

  那么,到底是哪里的問(wèn)題呢?思而不得后,自然就開(kāi)啟了飛行模式了!

下面,開(kāi)啟debug模式!

  本想直接 debug spring 的,結(jié)果,很明顯,失敗了。壓根就沒(méi)有進(jìn)入 spring 的 ClassPathXmlApplicationContext 中,得出一個(gè)結(jié)論,spring 沒(méi)有被正確的打開(kāi)!

  好吧,那讓我們退回一步,既然 servlet 啟不來(lái),那么,可能就是 filter 有問(wèn)題了。

  不過(guò),請(qǐng)稍等,filter 不是在有請(qǐng)求進(jìn)來(lái)的時(shí)候,才會(huì)起作用嗎?沒(méi)道理在初始化的時(shí)候就把應(yīng)用給搞死了啊!(不過(guò)其實(shí)這是有可能的)

  那么,到底問(wèn)題出在了哪里?

簡(jiǎn)單掃略下代碼,不多,還有一個(gè) listener 沒(méi)有被引起注意,去看看吧。

先了解下,web.xml 中的 listener 作用:

  listener 即 監(jiān)聽(tīng)器,其實(shí)也是 tomcat 的一個(gè)加載節(jié)點(diǎn)。加載順序與它們?cè)?web.xml 文件中的先后順序無(wú)關(guān)。即不會(huì)因?yàn)?filter 寫在 listener 的前面而會(huì)先加載 filter。

  其加載順序?yàn)? listener -> filter -> servlet

  接下來(lái),就知道, listener 先加載,既然沒(méi)有到 servlet, 也排除了 filter, 那就 debug listener 唄!

  果然,debug進(jìn)入無(wú)誤!單步后,發(fā)現(xiàn)應(yīng)用在某此被中斷,線程找不到了,有點(diǎn)懵。(其實(shí)只是因?yàn)榫€程中被調(diào)用了線程切換而已)

  我想著,可能是某處發(fā)生了異常,而此處又沒(méi)有被 try-catch, 所以也是很傷心。要是能臨時(shí)打 try-catch 就好了。

其實(shí) idea 中 是可以對(duì)沒(méi)有捕獲的異常進(jìn)行收集的,即開(kāi)啟當(dāng)發(fā)生異常時(shí)就捕獲的功能就可以了。

  然而,這大部分情況下捕獲的異常,僅僅正常的 loadClass() 異常,這在類加載模型中,是正常拋出的異常。

 // 如: java.net.URLClassLoader.findClass() 拋出的異常  protected Class<?> findClass(final String name)    throws ClassNotFoundException  {    final Class<?> result;    try {      result = AccessController.doPrivileged(        new PrivilegedExceptionAction<Class<?>>() {          public Class<?> run() throws ClassNotFoundException {            String path = name.replace('.', '/').concat(".class");            Resource res = ucp.getResource(path, false);            if (res != null) {              try {                return defineClass(name, res);              } catch (IOException e) {                throw new ClassNotFoundException(name, e);              }            } else {              return null;            }          }        }, acc);    } catch (java.security.PrivilegedActionException pae) {      throw (ClassNotFoundException) pae.getException();    }    if (result == null) {      // 此處拋出的異常可以被 idea 捕獲      throw new ClassNotFoundException(name);    }    return result;  }

  由于這么多無(wú)效的異常,導(dǎo)致我反復(fù)換了n個(gè)姿勢(shì),總算到達(dá)正確的位置。

  然而當(dāng)跟蹤到具體的一行時(shí),還是發(fā)生了錯(cuò)誤。

既然用單步調(diào)試無(wú)法找到錯(cuò)誤,那么是不是在我沒(méi)有單步的地方,出了問(wèn)題?

對(duì)咯,就是 靜態(tài)方法塊!這個(gè)地方,是在首次調(diào)用該類的任意方法時(shí),進(jìn)行初始化的!也許這是我們的方向。

最后,跟蹤到了一個(gè)靜態(tài)塊中,發(fā)現(xiàn)這里被中斷了!

  static {    // 原罪在這里    CAS_EDIS_CLIENT_TEMPLATE = CasSpringContextUtils.getBean("casRedisClientTemplate", CasRedisClientTemplate.class);  }

  這一句看起來(lái)是向 spring 的 bean工廠請(qǐng)求一個(gè)實(shí)例,為什么能被卡死呢?
只有再深入一點(diǎn),才能了解其情況:

  public static <T> T getBean(String name, Class<T> beanType) {    return getApplicationContext().getBean(name, beanType);  }

這句看起來(lái)更像是 spring 的bean獲取,不應(yīng)該有問(wèn)題啊!不過(guò)接下來(lái)一句會(huì)讓我們明白一切:

  public static ApplicationContext getApplicationContext() {    synchronized (CasSpringContextUtils.class) {      while (applicationContext == null) {        try {          // 沒(méi)錯(cuò),就是這里了, 這里設(shè)置了死鎖,線程交出,等待1分鐘超時(shí),繼續(xù)循環(huán)          CasSpringContextUtils.class.wait(60000);        } catch (InterruptedException ex) {        }      }      return applicationContext;    }  }

  很明顯,這里已經(jīng)導(dǎo)致了某種意義上的死鎖。因?yàn)?web.xml 在加載到此處時(shí),使用的是一個(gè) main 線程,而加載到此處時(shí),卻被該處判斷阻斷。

那么我們可能想, applicationContext 是一個(gè) sping 管理的類,那么只要他被加載后,不可以了嗎?就像下面一樣:

  沒(méi)錯(cuò),spring 在加載到此類時(shí),會(huì)調(diào)用一個(gè) setApplicationContext, 此時(shí) applicationContext 就不會(huì)null了。然后想像還是太美,原因如上:

  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {    synchronized (CasSpringContextUtils.class) {      CasSpringContextUtils.applicationContext = applicationContext;      // 夢(mèng)想總是很美好,當(dāng)加載完成后,通知 wait()      CasSpringContextUtils.class.notifyAll();    }  }

  ok, 截止這里,我們已經(jīng)找到了問(wèn)題的根源。是一個(gè)被引入的jar的優(yōu)雅方式阻止了你的前進(jìn)。冬天已現(xiàn),春天不會(huì)遠(yuǎn)!

如何解決?

很明顯,你是不可能去改動(dòng)這段代碼的,那么你要做的,就是想辦法繞過(guò)它。

  即:在執(zhí)行 getApplicationContext() 之前,把 applicationContext 處理好!

如何優(yōu)先加載 spring 上下文?配置一個(gè) context-param, 再加一個(gè) ContextLoaderListener, 即可:

 <!-- 提前加載spring --> <context-param>  <param-name>contextConfigLocation</param-name>  <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener>  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>

在 ContextLoaderListener 中,會(huì)優(yōu)先加載 contextInitialized(); 從而初始化整個(gè) spring 的生命周期!

  /**   * Initialize the root web application context.   */  @Override  public void contextInitialized(ServletContextEvent event) {    initWebApplicationContext(event.getServletContext());  }

  也就是說(shuō),只要把這個(gè)配置放到新增的 filter 之前,即可實(shí)現(xiàn)正常情況下的加載!

  驗(yàn)證結(jié)果,果然如此!

最后,附上一段 tomcat 加載 context 的魯棒代碼,以供參考:

/**   * Configure the set of instantiated application event listeners   * for this Context.   * @return <code>true</code> if all listeners wre   * initialized successfully, or <code>false</code> otherwise.   */  public boolean listenerStart() {    if (log.isDebugEnabled())      log.debug("Configuring application event listeners");    // Instantiate the required listeners    String listeners[] = findApplicationListeners();    Object results[] = new Object[listeners.length];    boolean ok = true;    for (int i = 0; i < results.length; i++) {      if (getLogger().isDebugEnabled())        getLogger().debug(" Configuring event listener class '" +          listeners[i] + "'");      try {        String listener = listeners[i];        results[i] = getInstanceManager().newInstance(listener);      } catch (Throwable t) {        t = ExceptionUtils.unwrapInvocationTargetException(t);        ExceptionUtils.handleThrowable(t);        getLogger().error(sm.getString(            "standardContext.applicationListener", listeners[i]), t);        ok = false;      }    }    if (!ok) {      getLogger().error(sm.getString("standardContext.applicationSkipped"));      return false;    }    // Sort listeners in two arrays    ArrayList<Object> eventListeners = new ArrayList<>();    ArrayList<Object> 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]);      }    }    // Listener instances may have been added directly to this Context by    // ServletContextInitializers and other code via the pluggability APIs.    // Put them these listeners after the ones defined in web.xml and/or    // annotations then overwrite the list of instances with the new, full    // list.    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());    // Send application start events    if (getLogger().isDebugEnabled())      getLogger().debug("Sending application start events");    // Ensure context is not null    getServletContext();    context.setNewServletContextListenerAllowed(false);    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];      try {        fireContainerEvent("beforeContextInitialized", listener);        // 調(diào)用 listener.contextInitialized() 觸發(fā) listener        if (noPluggabilityListeners.contains(listener)) {          listener.contextInitialized(tldEvent);        } else {          listener.contextInitialized(event);        }        fireContainerEvent("afterContextInitialized", listener);      } catch (Throwable t) {        ExceptionUtils.handleThrowable(t);        fireContainerEvent("afterContextInitialized", listener);        getLogger().error          (sm.getString("standardContext.listenerStart",                 instances[i].getClass().getName()), t);        ok = false;      }    }    return (ok);  }

總結(jié)

以上所述是小編給大家介紹的一個(gè)applicationContext 加載錯(cuò)誤導(dǎo)致的阻塞問(wèn)題及解決方法,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)VeVb武林網(wǎng)網(wǎng)站的支持!


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到JAVA教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 999精品久久久 | 日韩黄色片网站 | 久久成人福利 | 石原莉奈日韩一区二区三区 | 成人毛片免费播放 | 久久精精品 | 国产成人在线免费看 | 在线a毛片| 99riav视频一区二区 | 久久久久久艹 | 成人毛片免费视频 | 小情侣嗯啊哦视频www | 中文字幕11| 国产成人精品免费视频大全最热 | 一级黄色性感片 | av不卡免费在线 | 狠狠干狠狠操 | 免费a观看 | 午夜影院在线免费观看 | 极品xxxx欧美一区二区 | 亚洲天堂成人在线 | 丰满年轻岳中文字幕一区二区 | 91av大片 | 色中射 | 国产免费一区二区三区最新不卡 | 成人毛片100部 | 中文字幕欧美日韩 | www.精品一区 | 精品一区二区在线观看视频 | 久久毛片免费观看 | 免费久久精品 | 成人午夜在线免费观看 | 日韩视频观看 | 午夜精品毛片 | 爱福利视频网 | 久久精品一区二区三区国产主播 | 国产精品爱久久久久久久 | 九一成人 | 日本免费不卡一区二区 | 污片视频网站 | 污片视频网站 |