好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

一个applicationContext 加载错误导致的阻塞问题及解决方法

问题为对接一个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

查看更多关于一个applicationContext 加载错误导致的阻塞问题及解决方法的详细内容...

  阅读:15次