⼀个applicationContext加载错误导致的阻塞问题及解决
⽅法
问题为对接⼀个sso的验证模块,正确的对接姿势为,接⼊⼀个 filter, 然后接⼊⼀个 SsoListener 。
然⽽在接⼊之后,却导致了应⽤⽆法正常启动,或者说看起来很奇怪,来看下都遇到什么样的问题,以及是如何处理的?还是 l, 原本是这样的: (很简洁!)
<?xml version="1.0" encoding="UTF-8" ?>
<web-app xmlns="java.sun/xml/ns/javaee" xmlns:xsi="/2001/XMLSchema-instance"
xsi:schemaLocation="java.sun/xml/ns/javaee java.sun/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:l</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&session.RedisSessionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SessionFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class&session.SSOHttpSessionListener</listener-class>
</listener>
<filter>
<filter-name>SSOFilter</filter-name>
<filter-class&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 没有被引起注意,去看看吧。
先了解下,l 中的 listener 作⽤:
listener 即,其实也是 tomcat 的⼀个加载节点。加载顺序与它们在 l ⽂件中的先后顺序⽆关。即不会因为filter 写在 listener 的前⾯⽽会先加载 filter。
其加载顺序为: listener -> filter -> servlet
接下来,就知道, listener 先加载,既然没有到 servlet, 也排除了 filter, 那就 debug listener 呗!
果然,debug进⼊⽆误!单步后,发现应⽤在某此被中断,线程不到了,有点懵。(其实只是因为线程中被调⽤了线程切换⽽已)
我想着,可能是某处发⽣了异常,⽽此处⼜没有被 try-catch, 所以也是很伤⼼。要是能临时打 try-catch 就好了。
其实 idea 中是可以对没有捕获的异常进⾏收集的,即开启当发⽣异常时就捕获的功能就可以了。
然⽽,这⼤部分情况下捕获的异常,仅仅正常的 loadClass() 异常,这在类加载模型中,是正常抛出的异常。
// 如: java.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 = place('.', '/').concat(".class");
Resource res = Resource(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) Exception();
}
if (result == null) {
// 此处抛出的异常可以被 idea 捕获
throw new ClassNotFoundException(name);
}
return result;
}
由于这么多⽆效的异常,导致我反复换了n个姿势,总算到达正确的位置。
然⽽当跟踪到具体的⼀⾏时,还是发⽣了错误。
既然⽤单步调试⽆法到错误,那么是不是在我没有单步的地⽅,出了问题?
对咯,就是静态⽅法块!这个地⽅,是在⾸次调⽤该类的任意⽅法时,进⾏初始化的!也许这是我们的⽅向。
最后,跟踪到了⼀个静态块中,发现这⾥被中断了!
static {
// 原罪在这⾥
CAS_EDIS_CLIENT_TEMPLATE = Bean("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;
}
}
很明显,这⾥已经导致了某种意义上的死锁。因为 l 在加载到此处时,使⽤的是⼀个 main 线程,⽽加载到此处时,却被该处判断阻断。
那么我们可能想, applicationContext 是⼀个 sping 管理的类,那么只要他被加载后,不可以了吗?就像下⾯⼀样:
没错,spring 在加载到此类时,会调⽤⼀个 setApplicationContext, 此时 applicationContext 就不会null了。然后想像还是太美,原因如上:
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
synchronized (CasSpringContextUtils.class) {
CasSpringContextUtils.applicationContext = applicationContext;
// 梦想总是很美好,当加载完成后,通知 wait()spring是什么意思1
ifyAll();
}
}
ok, 截⽌这⾥,我们已经到了问题的根源。是⼀个被引⼊的jar的优雅⽅式阻⽌了你的前进。冬天已现,春天不会远!
如何解决?
很明显,你是不可能去改动这段代码的,那么你要做的,就是想办法绕过它。
即:在执⾏ getApplicationContext() 之前,把 applicationContext 处理好!
如何优先加载 spring 上下⽂?配置⼀个 context-param, 再加⼀个 ContextLoaderListener, 即可:
<!-- 提前加载spring -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:l</param-value>
</context-param>
<listener>
<listener-class>org.t.ContextLoaderListener</listener-class>
</listener>
在 ContextLoaderListener 中,会优先加载 contextInitialized(); 从⽽初始化整个 spring 的⽣命周期!
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
ServletContext());
}
也就是说,只要把这个配置放到新增的 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().String(
"standardContext.applicationListener", listeners[i]), t);
ok = false;
}
}
if (!ok) {
getLogger().String("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 l and/or
// annotations then overwrite the list of instances with the new, full
// list.
for (Object eventListener: getApplicationEventListeners()) {
eventListeners.add(eventListener);
}
Array());
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
lifecycleListeners.add(lifecycleListener);
if (lifecycleListener instanceof ServletContextListener) {
noPluggabilityListeners.add(lifecycleListener);
}
Array());
// 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);
// 调⽤ tInitialized() 触发 listener
if (ains(listener)) {
} else {
}
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 加载错误导致的阻塞问题及解决⽅法,希望对⼤家有所帮助,如果⼤家有任何疑问请给我留⾔,⼩编会及时回复⼤家的。在此也⾮常感谢⼤家对⽹站的⽀持!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论