java classloader的一个bug

最近遇到一个很诡异的问题,SAE的javaruntime有一部分用户反馈session总是丢失,导致相当一部分用户的代码部署后登录功能无法正常使用。

先说下sae session的托管方式:

这里先说下SAE的session托管方式,采用分布式session,最终由mc实现session的综合存储,从而达到多jvm实例的情况下,session同步。分布式session的实现代码最终调用spymmecached的客户的对session的后端mc进行增删改。


通过分析用户运行时产生的日志分析发现一个如下的错误

java.lang.ClassNotFoundException: com.xxx.xxx.xxxx   //这里一般都是mvc里的model代码,一些基本的用户信息处理类,序列化后存session是一般登录的处理方式
     at java.net.URLClassLoader$1.run(URLClassLoader.java:217) 
     at java.security.AccessController.doPrivileged(Native Method) 
     at java.net.URLClassLoader.findClass(URLClassLoader.java:205) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:388) 
     at java.lang.ClassLoader.loadClass(ClassLoader.java:333) 
     at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:419) 
     at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:372) 
     at java.lang.Class.forName0(Native Method) 
     at java.lang.Class.forName(Class.java:279) 
     at inner.spy.memcached.transcoders.SerializingTranscoder$DeserializableObject.resolveClass(SerializingTranscoder.java:323)    //调用栈这里是调用到了spymemecached客户端改写的反序列化的代码
     at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1592) 
     at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1513) 
     at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1749) 
     at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1346) 
     at java.io.ObjectInputStream.readObject(ObjectInputStream.java:368) 
     at inner.spy.memcached.transcoders.SerializingTranscoder.deserialize(SerializingTranscoder.java:294) 
     at inner.spy.memcached.transcoders.SerializingTranscoder.decode(SerializingTranscoder.java:204) 
     at inner.spy.memcached.transcoders.TranscodeService$1.call(TranscodeService.java:63) 
     at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334) 
     at java.util.concurrent.FutureTask.run(FutureTask.java:166) 
     at inner.spy.memcached.transcoders.TranscodeService$Task.run(TranscodeService.java:110) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603) 
     at java.lang.Thread.run(Thread.java:714)

从以上的错误可以看出就是类找不到,照一般的想法应该是看看打包后的war包中是否包含这个类,不幸的是这个类一般都是在的,那么就是其他的问题来看看具体错误处的代码

public Object deserialize(byte[] in) {  //这个方法是最终拿到session mc的数据后判断为object的时候,调用的反序列化
		Object rv = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream is = null;
		try {
			if (in != null) {
				ClassLoader loader = Thread.currentThread().getContextClassLoader(); //这里是是拿到当前上下文的classloader
				DeserializableObject ois = new DeserializableObject( new ByteArrayInputStream(in),loader);
				return ois.readObject();
			}
		} catch (IOException e) {
			getLogger().warn("Caught IOException decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} catch (ClassNotFoundException e) {
			getLogger().warn("Caught CNFE decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} finally {
			CloseUtil.close(is);
			CloseUtil.close(bis);
		}
		return rv;
	}
	
	
	/**
	 * 反序列化字节流,使用一个指定的classloader序列化class流。
	 * @author Administrator
	 *
	 */
	private static class DeserializableObject extends ObjectInputStream { //具体处理逻辑
		private ClassLoader loader;
		DeserializableObject(InputStream in, ClassLoader loader) throws IOException {
			super(in);
			this.loader = loader;  //解析一些用户类的时候这里的classloader变成了StartJarClassloader
		}
		@Override
		protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException {
			return Class.forName( desc.getName(),true,loader );
		}
	}

通过过debug发现,在解析一些用户的类的时候,当前上下文的classloader不知道怎么变成了StarJarClassloader对象实例,StartJarClassloader的对象实例加载的类对象是在jetty/lib下的相关jar包,当然无法找到用户自己写的类对象,查了半天也没有确认为什么会导致这样的问题,那么只能换个思路解决问题了,能不能在jetty启动的时候将加载用户类的WebAppClassloader的实例保存起来,然后在反序列化的时候直接拿这个classloader来反射具体的类,于是考虑半天具体这样实现

/**
	 * 自定义的反序列化
	 */
	public Object deserialize(byte[] in) {
		Object rv = null;
		ByteArrayInputStream bis = null;
		ObjectInputStream is = null;
		try {
			if (in != null) {
//				ClassLoader loader = Thread.currentThread().getContextClassLoader();
				ClassLoader loader = ServiceStatus.loader !=null?ServiceStatus.loader : Thread.currentThread().getContextClassLoader();  //用一个全局的类存下加载用户代码的classloader
				DeserializableObject ois = new DeserializableObject( new ByteArrayInputStream(in),loader);
				return ois.readObject();
			}
		} catch (IOException e) {
			getLogger().warn("Caught IOException decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} catch (ClassNotFoundException e) {
			getLogger().warn("Caught CNFE decoding %d bytes of data",
					in == null ? 0 : in.length, e);
		} finally {
			CloseUtil.close(is);
			CloseUtil.close(bis);
		}
		return rv;
	}
	
	
	/**
	 * 反序列化字节流,使用一个指定的classloader序列化class流。
	 * @author Administrator
	 *
	 */
	private static class DeserializableObject extends ObjectInputStream {
		private ClassLoader loader;
		DeserializableObject(InputStream in, ClassLoader loader) throws IOException {
			super(in);
			this.loader = loader;
		}
		@Override
		protected Class<?> resolveClass(ObjectStreamClass desc)throws IOException, ClassNotFoundException {
			return Class.forName( desc.getName(),true,loader );
		}

按照以上的处理方式处理后发现,果然解决了这个问题,真是曲线救国啊~~~

这里可能要问了怎么获取到到加载用户代码的WebAppClassloader呢 还是贴代码

public class SaeWebAppContext extends WebAppContext {  //这里的父类是构造用户代码的上下文,在这里会解析用户的war包,而后创建属于用户代码的WebAppClassloader 
    	@Override
	public void setClassLoader(ClassLoader classLoader) {  
		// TODO Auto-generated method stub
		super.setClassLoader(classLoader); //在父类创建完后 存下这个对象的引用
		ServiceStatus.loader = getClassLoader();
	}
}

自此 问题解决 目前运行良好,接下来要查查是什么原因导致classloader变化,等查到了在继续分析原因

本文完

你可能感兴趣的:(java classloader的一个bug)