问题现象
项目在本地Tomcat下一切正常,但是部署到websphere(was)时启动报出如下错误:
Caused by: org.neo4j.ogm.exception.core.MappingException: Unable to load class with FQN: com.ktanx.model.UserNode
at org.neo4j.ogm.metadata.reflect.EntityFactory.instantiateObjectFromTaxa(EntityFactory.java:109)
at org.neo4j.ogm.metadata.reflect.EntityFactory.newObject(EntityFactory.java:58)
at org.neo4j.ogm.context.GraphEntityMapper.mapNodes(GraphEntityMapper.java:217)
at org.neo4j.ogm.context.GraphEntityMapper.mapEntities(GraphEntityMapper.java:203)
at org.neo4j.ogm.context.GraphEntityMapper.map(GraphEntityMapper.java:135)
at org.neo4j.ogm.context.GraphEntityMapper.map(GraphEntityMapper.java:89)
at org.neo4j.ogm.session.delegates.LoadOneDelegate.load(LoadOneDelegate.java:70)
at org.neo4j.ogm.session.delegates.LoadOneDelegate.load(LoadOneDelegate.java:46)
at org.neo4j.ogm.session.Neo4jSession.load(Neo4jSession.java:155)
at controllers.UsersController.getUserMetrics(UsersController.java:372)
Caused by: java.lang.ClassNotFoundException: com.ktanx.model.UserNode
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at org.neo4j.ogm.metadata.reflect.EntityFactory.instantiateObjectFromTaxa(EntityFactory.java:106)
at org.neo4j.ogm.metadata.reflect.EntityFactory.newObject(EntityFactory.java:58)
at org.neo4j.ogm.context.GraphEntityMapper.mapNodes(GraphEntityMapper.java:217)
at org.neo4j.ogm.context.GraphEntityMapper.mapEntities(GraphEntityMapper.java:203)
at org.neo4j.ogm.context.GraphEntityMapper.map(GraphEntityMapper.java:135)
主要信息是ClassNotFoundException
,找不到class,但是查看war包里面都是存在的,本地Tomcat下部署war包也都一切正常,那么肯定是环境问题导致的了。
分析
通过查看异常信息和远程调试,最终定位到具体出错代码位置是在org.neo4j.ogm.classloader.MetaDataClassLoader
的loadClass方法,
private static final ClassLoader classLoader = ClassLoaderResolver.resolve();
public static Class loadClass(final String name) throws ClassNotFoundException {
return Class.forName(name, false, classLoader);
}
该方法在was下获取到的ClassLoader是AppClassLoader,而加载的时候确实加载不到上面的类,应该是was的隔离机制和Tomcat等不同导致错误的发生。
将ClassLoader改成Thread.currentThread().getContextClassLoader()
后则加载成功,看来问题的根源就在这里了。
解决
因为这是个静态方法,修改的话就要直接动jar包,显然这种修改方式并不好。
查看在spring中声明使用的各个bean(项目和spring整合),发现org.springframework.data.neo4j.mapping.Neo4jMappingContext
类中有如下代码:
public Neo4jMappingContext(MetaData metaData) {
this.metaData = metaData;
for (ClassInfo classInfo : metaData.persistentEntities()) {
try {
addPersistentEntity( MetaDataClassLoader.loadClass( classInfo.name() ));
} catch (ClassNotFoundException e) {
logger.error("Failed to load class: " + classInfo.name() + " named in ClassInfo due to exception", e);
}
}
logger.info("Neo4jMappingContext initialisation completed");
}
将addPersistentEntity( MetaDataClassLoader.loadClass( classInfo.name() ));
这行改为
addPersistentEntity( Thread.currentThread().getContextClassLoader().loadClass( classInfo.name() ));
上面的错误信息是没了,但是又报出其它的错误信息,调试后发现neo4j的包中好多地方都用到了MetaDataClassLoader.loadClass( classInfo.name() )
这样的代码,例如org.neo4j.ogm.annotations.EntityFactory
中:
private <T> T instantiateObjectFromTaxa(String... taxa) {
if (taxa == null || taxa.length == 0) {
throw new BaseClassNotFoundException("<null>");
}
String fqn = resolve(taxa);
try {
@SuppressWarnings("unchecked")
Class<T> loadedClass = (Class<T>) MetaDataClassLoader.loadClass(fqn); //Class.forName(fqn);
return instantiate(loadedClass);
} catch (ClassNotFoundException e) {
throw new MappingException("Unable to load class with FQN: " + fqn, e);
}
}
都是直接调用的静态方法且该类都是neo4j的jar包内部使用根本无法扩展替换,所以只能修改MetaDataClassLoader
并替换原jar包中的class了。
替换后运行使用都一切正常!
最后
was下的坑总是特别多,部署项目经常就是一个填坑的过程,唉!
另外感觉neo4j相关包封装的扩展性有待提高,像上面的问题完全无法通过自定义class来解决只能hack jar包,Spring目前为止就没碰到过该问题。