問題為對接一個sso的驗證模塊,正確的對接姿勢為,接入一個 filter, 然后接入一個 ssolistener 。
然而在接入之后,卻導致了應用無法正常啟動,或者說看起來很奇怪,來看下都遇到什么樣的問題,以及是如何處理的?
還是 web.xml, 原本是這樣的: (很簡潔!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<?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 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
<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> |
另外再加幾個必要的配置文件掃描!對接完成!不費事!
然后,我坑哧坑哧把代碼copy過來,準備 commit 搞定收工!
結果,不出所料,server 起不來了。也不完全是啟不來了,就只是啟起來之后,啥也沒有了。
sso 中也沒啥東西,就是攔截下 header 中的值,判定如果沒有登錄就的話,就直接返回到 sso 的登錄頁去了。
那么,到底是哪里的問題呢?思而不得后,自然就開啟了飛行模式了!
下面,開啟debug模式!
本想直接 debug spring 的,結果,很明顯,失敗了。壓根就沒有進入 spring 的 classpathxmlapplicationcontext 中,得出一個結論,spring 沒有被正確的打開!
好吧,那讓我們退回一步,既然 servlet 啟不來,那么,可能就是 filter 有問題了。
不過,請稍等,filter 不是在有請求進來的時候,才會起作用嗎?沒道理在初始化的時候就把應用給搞死了啊!(不過其實這是有可能的)
那么,到底問題出在了哪里?
簡單掃略下代碼,不多,還有一個 listener 沒有被引起注意,去看看吧。
先了解下,web.xml 中的 listener 作用:
listener 即 監聽器,其實也是 tomcat 的一個加載節點。加載順序與它們在 web.xml 文件中的先后順序無關。即不會因為 filter 寫在 listener 的前面而會先加載 filter。
其加載順序為: listener -> filter -> servlet
接下來,就知道, listener 先加載,既然沒有到 servlet, 也排除了 filter, 那就 debug listener 唄!
果然,debug進入無誤!單步后,發現應用在某此被中斷,線程找不到了,有點懵。(其實只是因為線程中被調用了線程切換而已)
我想著,可能是某處發生了異常,而此處又沒有被 try-catch, 所以也是很傷心。要是能臨時打 try-catch 就好了。
其實 idea 中 是可以對沒有捕獲的異常進行收集的,即開啟當發生異常時就捕獲的功能就可以了。
然而,這大部分情況下捕獲的異常,僅僅正常的 loadclass() 異常,這在類加載模型中,是正常拋出的異常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
// 如: 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; } |
由于這么多無效的異常,導致我反復換了n個姿勢,總算到達正確的位置。
然而當跟蹤到具體的一行時,還是發生了錯誤。
既然用單步調試無法找到錯誤,那么是不是在我沒有單步的地方,出了問題?
對咯,就是 靜態方法塊!這個地方,是在首次調用該類的任意方法時,進行初始化的!也許這是我們的方向。
最后,跟蹤到了一個靜態塊中,發現這里被中斷了!
1
2
3
4
|
static { // 原罪在這里 cas_edis_client_template = casspringcontextutils.getbean( "casredisclienttemplate" , casredisclienttemplate. class ); } |
這一句看起來是向 spring 的 bean工廠請求一個實例,為什么能被卡死呢?
只有再深入一點,才能了解其情況:
1
2
3
|
public static <t> t getbean(string name, class <t> beantype) { return getapplicationcontext().getbean(name, beantype); } |
這句看起來更像是 spring 的bean獲取,不應該有問題啊!不過接下來一句會讓我們明白一切:
1
2
3
4
5
6
7
8
9
10
11
12
|
public static applicationcontext getapplicationcontext() { synchronized (casspringcontextutils. class ) { while (applicationcontext == null ) { try { // 沒錯,就是這里了, 這里設置了死鎖,線程交出,等待1分鐘超時,繼續循環 casspringcontextutils. class .wait( 60000 ); } catch (interruptedexception ex) { } } return applicationcontext; } } |
很明顯,這里已經導致了某種意義上的死鎖。因為 web.xml 在加載到此處時,使用的是一個 main 線程,而加載到此處時,卻被該處判斷阻斷。
那么我們可能想, applicationcontext 是一個 sping 管理的類,那么只要他被加載后,不可以了嗎?就像下面一樣:
沒錯,spring 在加載到此類時,會調用一個 setapplicationcontext, 此時 applicationcontext 就不會null了。然后想像還是太美,原因如上:
1
2
3
4
5
6
7
|
public void setapplicationcontext(applicationcontext applicationcontext) throws beansexception { synchronized (casspringcontextutils. class ) { casspringcontextutils.applicationcontext = applicationcontext; // 夢想總是很美好,當加載完成后,通知 wait() casspringcontextutils. class .notifyall(); } } |
ok, 截止這里,我們已經找到了問題的根源。是一個被引入的jar的優雅方式阻止了你的前進。冬天已現,春天不會遠!
如何解決?
很明顯,你是不可能去改動這段代碼的,那么你要做的,就是想辦法繞過它。
即:在執行 getapplicationcontext() 之前,把 applicationcontext 處理好!
如何優先加載 spring 上下文?配置一個 context-param, 再加一個 contextloaderlistener, 即可:
1
2
3
4
5
6
7
8
|
<!-- 提前加載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 中,會優先加載 contextinitialized(); 從而初始化整個 spring 的生命周期!
1
2
3
4
5
6
7
|
/** * initialize the root web application context. */ @override public void contextinitialized(servletcontextevent event) { initwebapplicationcontext(event.getservletcontext()); } |
也就是說,只要把這個配置放到新增的 filter 之前,即可實現正常情況下的加載!
驗證結果,果然如此!
最后,附上一段 tomcat 加載 context 的魯棒代碼,以供參考:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
/** * 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); // 調用 listener.contextinitialized() 觸發 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); } |
總結
以上所述是小編給大家介紹的一個applicationcontext 加載錯誤導致的阻塞問題及解決方法,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:https://www.cnblogs.com/yougewe/p/9948909.html