评论

收藏

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

编程语言 编程语言 发布于:2021-10-07 14:20 | 阅读数:455 | 评论:0

这篇文章主要介绍了一个applicationContext 加载错误导致的阻塞问题及解决方法,需要的朋友可以参考下
问题为对接一个sso的验证模块,正确的对接姿势为,接入一个 filter, 然后接入一个 ssolistener 。
然而在接入之后,却导致了应用无法正常启动,或者说看起来很奇怪,来看下都遇到什么样的问题,以及是如何处理的?
还是 web.xml, 原本是这样的: (很简洁!)
<?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>
另外再加几个必要的配置文件扫描!对接完成!不费事!
然后,我坑哧坑哧把代码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() 异常,这在类加载模型中,是正常抛出的异常。
// 如: 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个姿势,总算到达正确的位置。
然而当跟踪到具体的一行时,还是发生了错误。
既然用单步调试无法找到错误,那么是不是在我没有单步的地方,出了问题?
对咯,就是 静态方法块!这个地方,是在首次调用该类的任意方法时,进行初始化的!也许这是我们的方向。
最后,跟踪到了一个静态块中,发现这里被中断了!
static {
  // 原罪在这里
  cas_edis_client_template = casspringcontextutils.getbean("casredisclienttemplate", casredisclienttemplate.class);
}
这一句看起来是向 spring 的 bean工厂请求一个实例,为什么能被卡死呢?
只有再深入一点,才能了解其情况:
public static <t> t getbean(string name, class<t> beantype) {
  return getapplicationcontext().getbean(name, beantype);
}
这句看起来更像是 spring 的bean获取,不应该有问题啊!不过接下来一句会让我们明白一切:
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了。然后想像还是太美,原因如上:
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, 即可:
<!-- 提前加载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 的生命周期!
/**
 * initialize the root web application context.
 */
@override
public void contextinitialized(servletcontextevent event) {
  initwebapplicationcontext(event.getservletcontext());
}
也就是说,只要把这个配置放到新增的 filter 之前,即可实现正常情况下的加载!
验证结果,果然如此!
最后,附上一段 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);
    // 调用 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 加载错误导致的阻塞问题及解决方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对CodeAE代码之家网站的支持!
原文链接:https://www.cnblogs.com/yougewe/p/9948909.html

关注下面的标签,发现更多相似文章