一个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 加载错误导致的阻塞问题及解决方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 详解spring applicationContext.xml 配置文件

    applicationContext.xml 文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http

  • Spring获取ApplicationContext对象工具类的实现方法

     Spring获取ApplicationContext对象工具类的实现方法 (1)实现的工具类: package com.util; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; final public class ApplicationContextUtil { private s

  • JSP Spring ApplicationContext的国际化支持

    JSP Spring  ApplicationContext的国际化支持 1.ApplicationContext接口继承了MessageResource接口,因此使用ApplicationContext作为spring容器可以使用国际化资源文件. 2.在MessageResource接口中定义了两个主要用于国际化的方法: String getMessage(String param,Object[] args,Locale loc) ; String getMessage(String par

  • Spring MVC之WebApplicationContext_动力节点Java学院整理

    如果你使用了listener监听器来加载配置,一般在Struts+Spring+Hibernate的项目中都是使用listener监听器的.如下 Java代码 <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> Spring会创建一个WebApplicationContext上下文,称为父上下

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

    问题为对接一个sso的验证模块,正确的对接姿势为,接入一个 filter, 然后接入一个 SsoListener . 然而在接入之后,却导致了应用无法正常启动,或者说看起来很奇怪,来看下都遇到什么样的问题,以及是如何处理的? 还是 web.xml, 原本是这样的: (很简洁!) <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns="http://java.sun.com/xml/n

  • Bootstrap Table表格一直加载(load)不了数据的快速解决方法

    bootstrap-table是一个基于Bootstrap风格的强大的表格插件神器,官网:http://bootstrap-table.wenzhixin.net.cn/zh-cn/ 这里列出遇到的一个小问题:Bootstrap Table表格一直加载不了数据. $("#button").click(function(){ var name=$("input[name='name']").val(); $('#table').bootstrapTable('load

  • layui: layer.open加载窗体时出现遮罩层的解决方法

    如下所示: 把窗体方法独立出来放在layer.use([],function(){});外面,需要的时候从layer.use方法里面调用,就不会出现遮罩层 layer.use([],function(){ $("#添加按钮id").click(function(){ editData("",form,"添加") ; }) ; }); function editData(data,from,title){ var win = layer.open(

  • 判断jQuery是否加载完成,没完成继续判断的解决方法

    一个比较头疼的事情,有些插件绑定dom对象的,然后又用jq的语法,比如你在vue+node的时候,基本就要百度方法 我提供一个另类解决方案  比如你下了个轮播图插件   a.js 你打开他的a.js然后  用  function lbt(){} 把整个JS包起来在头部 再加上我这段,基本  是可以用了. isjQueryLoadend(); function isjQueryLoadend(){//判断JQ是否加载完成没有的话 继续判断 if (typeof $ != 'undefined' &

  • Windows下PHP安装路径配置错误导致Apache无法启动的解决方法

    LoadModule php5_module "C(/D):/Program Files/php5/php5apache2_2.dll"  PHP安装路径引起的apache无法启动错误 今天给一同事的PC机安装部署web服务的时候,按同事要求把所有程序文件放到安装目录的program files下,于是我把apache安装到了c:\program files下面,php也安装在c:\program files下.装完MySql,配置好apache和php的配置文件后,重起发现问题了 A

  • Spring的同一个服务会加载多次的问题分析及解决方法

    目录 问题现象 问题分析 解决方案 问题现象 最近在本地调试公司的一个Web项目时,无意中发现日志中出现了两次同一个服务的init记录,项目都是基于Spring来搭建的,按理说服务都是单例的,应该只有一次服务加载日志才对,本着对工作认真负责(闲来无事)的态度,必然要一探究竟. 问题分析 为什么同一个 Bean 会被容器初始化两次? 首先,我们先来梳理一下 Web 容器中如何加载 Bean: 在 Web 容器中,ContextLoaderListener 和 DispatchServlet 都会在

  • Visual Studio 2017无法加载Visual Studio 2015创建的SharePoint解决方法

    前几天安装了最新的Visual Studio 2017企业版,发现无法打开之前使用Visual Studio 2015创建的SharePoint 2016解决方案,提示"需要更新". 解决方法如下: 右键-编辑.csproj,把MinimumOfficeToolsVersion的值从14.5修改为14.0,重新加载项目就可以了. 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们.

  • php ci框架中加载css和js文件失败的解决方法

    在将html页面整合到ci框架里面的时候,加载css和js失败,弄了半天发现ci框架是入口的框架,对框架中文件的所有请求都需要经过index.php处理完成,当加载外部的css和js文件的时候要使用base_url()函数处理外部的链接. 比如: 在config配置文件中的base_url为:" localhost:8080/项目名称/ " 在控制器中访问application/resource/aaa.js文件 相对路径<script src= "resource/a

  • javascript实现一个网页加载进度loading

    loading随处可见,比如一个app经常会有下拉刷新,上拉加载的功能,在刷新和加载的过程中为了让用户感知到 load 的过程,我们会使用一些过渡动画来表达.最常见的比如"转圈圈","省略号"等等. 网页loading有很多用处,比如页面的加载进度,数据的加载过程等等,数据的加载loading很好做,只需要在加载数据之前(before ajax)显示loading效果,在数据返回之后(ajax completed)结束loading效果,就可以了. 但是页面的加载进

  • 解决Android ListView数据为空及加载错误的方法

    在项目中,都会用到ListView或GridView等列表控件.一般会用来展示从网络请求的数据 .如果请求的数据为空或者在请求的时候正好无没有网络了,我们的界面应该如何展示呢?数据为空的时候,ListView可以使用setEmptyView (View emptyView) 方法来我们需要的统一界面.数据加载失败呢?我们也可以统一进行处理. //下面这个类是简单地封装用于无数据及加载错误的一个页面. public class CommonShowView { private Context mC

随机推荐