問題為對(duì)接一個(gè)sso的驗(yàn)證模塊,正確的對(duì)接姿勢(shì)為,接入一個(gè) filter, 然后接入一個(gè) SsoListener 。
十載的長(zhǎng)樂網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。全網(wǎng)營(yíng)銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整長(zhǎng)樂建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)公司從事“長(zhǎng)樂網(wǎng)站設(shè)計(jì)”,“長(zhǎng)樂網(wǎng)站推廣”以來,每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
然而在接入之后,卻導(dǎo)致了應(yīng)用無法正常啟動(dòng),或者說看起來很奇怪,來看下都遇到什么樣的問題,以及是如何處理的?
還是 web.xml, 原本是這樣的: (很簡(jiǎn)潔?。?/p>
<?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過來,準(zhǔn)備 commit 搞定收工!
結(jié)果,不出所料,server 起不來了。也不完全是啟不來了,就只是啟起來之后,啥也沒有了。
sso 中也沒啥東西,就是攔截下 header 中的值,判定如果沒有登錄就的話,就直接返回到 sso 的登錄頁(yè)去了。
那么,到底是哪里的問題呢?思而不得后,自然就開啟了飛行模式了!
下面,開啟debug模式!
本想直接 debug spring 的,結(jié)果,很明顯,失敗了。壓根就沒有進(jìn)入 spring 的 ClassPathXmlApplicationContext 中,得出一個(gè)結(jié)論,spring 沒有被正確的打開!
好吧,那讓我們退回一步,既然 servlet 啟不來,那么,可能就是 filter 有問題了。
不過,請(qǐng)稍等,filter 不是在有請(qǐng)求進(jìn)來的時(shí)候,才會(huì)起作用嗎?沒道理在初始化的時(shí)候就把應(yīng)用給搞死了?。。ú贿^其實(shí)這是有可能的)
那么,到底問題出在了哪里?
簡(jiǎn)單掃略下代碼,不多,還有一個(gè) listener 沒有被引起注意,去看看吧。
先了解下,web.xml 中的 listener 作用:
listener 即 監(jiān)聽器,其實(shí)也是 tomcat 的一個(gè)加載節(jié)點(diǎn)。加載順序與它們?cè)?web.xml 文件中的先后順序無關(guān)。即不會(huì)因?yàn)?filter 寫在 listener 的前面而會(huì)先加載 filter。
其加載順序?yàn)? listener -> filter -> servlet
接下來,就知道, listener 先加載,既然沒有到 servlet, 也排除了 filter, 那就 debug listener 唄!
果然,debug進(jìn)入無誤!單步后,發(fā)現(xiàn)應(yīng)用在某此被中斷,線程找不到了,有點(diǎn)懵。(其實(shí)只是因?yàn)榫€程中被調(diào)用了線程切換而已)
我想著,可能是某處發(fā)生了異常,而此處又沒有被 try-catch, 所以也是很傷心。要是能臨時(shí)打 try-catch 就好了。
其實(shí) idea 中 是可以對(duì)沒有捕獲的異常進(jìn)行收集的,即開啟當(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; }
由于這么多無效的異常,導(dǎo)致我反復(fù)換了n個(gè)姿勢(shì),總算到達(dá)正確的位置。
然而當(dāng)跟蹤到具體的一行時(shí),還是發(fā)生了錯(cuò)誤。
既然用單步調(diào)試無法找到錯(cuò)誤,那么是不是在我沒有單步的地方,出了問題?
對(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); }
這一句看起來是向 spring 的 bean工廠請(qǐng)求一個(gè)實(shí)例,為什么能被卡死呢?
只有再深入一點(diǎn),才能了解其情況:
public static <T> T getBean(String name, Class<T> beanType) { return getApplicationContext().getBean(name, beanType); }
這句看起來更像是 spring 的bean獲取,不應(yīng)該有問題?。〔贿^接下來一句會(huì)讓我們明白一切:
public static ApplicationContext getApplicationContext() { synchronized (CasSpringContextUtils.class) { while (applicationContext == null) { try { // 沒錯(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 管理的類,那么只要他被加載后,不可以了嗎?就像下面一樣:
沒錯(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)找到了問題的根源。是一個(gè)被引入的jar的優(yōu)雅方式阻止了你的前進(jìn)。冬天已現(xiàn),春天不會(huì)遠(yuǎn)!
如何解決?
很明顯,你是不可能去改動(dòng)這段代碼的,那么你要做的,就是想辦法繞過它。
即:在執(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()); }
也就是說,只要把這個(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)致的阻塞問題及解決方法,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)創(chuàng)新互聯(lián)網(wǎng)站的支持!
本文名稱:一個(gè)applicationContext加載錯(cuò)誤導(dǎo)致的阻塞問題及解決方法
本文網(wǎng)址:http://chinadenli.net/article24/ihjsje.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供移動(dòng)網(wǎng)站建設(shè)、網(wǎng)站排名、、App開發(fā)、定制開發(fā)、營(yíng)銷型網(wǎng)站建設(shè)
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)