jetty_classloader
29461 ワード
1.現象
jbossからjettyに移行すると、次のような例外が表示されます.
コードを見てみると、このような操作が行われています.
2.原因
コンテナを交換してエラーを報告して、第一に考えられるのはjarパッケージの問題で、このクラスが異なるバージョンのClassをロードしたかどうか、以前に伝わったvalueがStringで、今はそうではありません.
この問題を検証するために、クラスの呼び出しでは次の方法が使用されます.
次に観察すると、jbossの順序は次のようになっています.
Jettyになると順番が逆になりますjson-lib-2.2.3.jar!/net/sf/json/JSONObject.classが前に並んでいるので、この問題が発生します.
eclipse mavenプラグインまたはmvn dependency:treeを使用すると、上記の2つのjarパケットは、jettyのjarパケットのロード順序を2つの二乗ライブラリで導入された依存(shy 2およびaranda)で考察することができます.
Jettyは起動中にWebAppContextのconfigureを使用してlibのパスをロードします.具体的な方法は以下の通りです.
呼び出したclassloaderの具体的なaddJarsメソッドは次のとおりです.
addClassPath最終呼び出し親クラスURLClassLoaderのaddURLメソッド
classloaderのパスに追加します.クラスがロードされると、この順序でpathを一つ一つ探して、対応するjarパッケージが見つかるかどうかを見ます.
したがって、addClassPathの順序は、そのjarパッケージのクラスがロードされる問題を決定します.
上のプログラムから分かるように、fileがlistされる順序はString[]files=libである.list();決定したのでlibを表示します.list
実際に使用するのはjavaです.io.fileのlistメソッド:
最終的に呼び出されるのはFileSystemの抽象的な方法です
public abstract String[] list(File f);
次はプラットフォーム関連のコードです.
jdkソースコードの検索が未熟なので、簡単なコードを書いて、外科的な方法で考察しました.
straceでシステム呼び出しをチェックすると、次のような有用な情報が得られます.
最終的に呼び出されたのはgetdents(最終的にreaddirを呼び出すようです)で、このシステム関数listのファイルがどのような順序なのか、今のところ分かりませんが、
inode番号によると、試してみてもそうではないようですが、いつも、順番はオペレーティングシステムに関連していて保証できません.
3.ソリューション:
1.jettyのwebAppClassloaderを複写し、リストから出てきたファイルを並べ替え、いくつかのパッケージを指定する順序を前に設定することもできます.
2.mavenによってexcludeの依存を構成するが、互換性を保証するには、互換性がない場合は、両方のライブラリ担当者に連絡して解決する必要がある.
3.パクリ方法、梱包時にjarパッケージの名前を変更するのは、あまり頼りにならない.
jbossからjettyに移行すると、次のような例外が表示されます.
1
2
3
4
5
6
net.sf.json.JSONException: java.lang.ClassCastException: com.ali.martini.biz.marketing.time.Parser$PeriodType cannot be cast to java.lang.String
at com.ali.martini.web.marketing.SendTimeDtoUtil$1.setProperty(SendTimeDtoUtil.java:210)
at net.sf.json.JSONObject.setProperty(JSONObject.java:1497)
at net.sf.json.JSONObject.toBean(JSONObject.java:387)
at com.ali.martini.common.JsonUtil.JSONStringToBean(JsonUtil.java:44)
at com.ali.martini.web.marketing.SendTimeDtoUtil.JSONStringToBean(SendTimeDtoUtil.java:216)
コードを見てみると、このような操作が行われています.
1
2
3
4
5
6
7
JsonConfig config = new JsonConfig();
config.setPropertySetStrategy(new PropertySetStrategy() {
public void setProperty(Object bean, String key, Object value) throws JSONException {
if("periodType".equals(key)){
Object val = PeriodType.valueOf((String)value);
}
}
2.原因
コンテナを交換してエラーを報告して、第一に考えられるのはjarパッケージの問題で、このクラスが異なるバージョンのClassをロードしたかどうか、以前に伝わったvalueがStringで、今はそうではありません.
この問題を検証するために、クラスの呼び出しでは次の方法が使用されます.
1
2
3
4
5
6
7
8
9
10
try {
Enumeration<URL> urls = this.getClass().getClassLoader().getResources("net/sf/json/JSONObject.class");
while(urls.hasMoreElements()) {
URL url = urls.nextElement();
System.out.println("url!!="+url);
logger.info("url!!="+url);
}
} catch (IOException e) {
e.printStackTrace();
}
次に観察すると、jbossの順序は次のようになっています.
1
2
jar:file:/D:/alibaba/jboss-4.2.2.GA/server/default/deploy/eve.war/WEB-INF/lib/ajax.json__json-lib-2.2-jdk15.jar-2.2.jar!/net/sf/json/JSONObject.class
jar:file:/D:/alibaba/jboss-4.2.2.GA/server/default/deploy/eve.war/WEB-INF/lib/sourceforge.json-lib-2.2.3.jar!/net/sf/json/JSONObject.class
Jettyになると順番が逆になりますjson-lib-2.2.3.jar!/net/sf/json/JSONObject.classが前に並んでいるので、この問題が発生します.
eclipse mavenプラグインまたはmvn dependency:treeを使用すると、上記の2つのjarパケットは、jettyのjarパケットのロード順序を2つの二乗ライブラリで導入された依存(shy 2およびaranda)で考察することができます.
Jettyは起動中にWebAppContextのconfigureを使用してlibのパスをロードします.具体的な方法は以下の通りです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public void configure(WebAppContext context) throws Exception
{
//cannot configure if the context is already started
if (context.isStarted())
{
if (Log.isDebugEnabled()){Log.debug("Cannot configure webapp "+context+" after it is started");}
return;
}
Resource web_inf = context.getWebInf();
// Add WEB-INF classes and lib classpaths
if (web_inf != null && web_inf.isDirectory() && context.getClassLoader() instanceof WebAppClassLoader)
{
// Look for classes directory
Resource classes= web_inf.addPath("classes/");
if (classes.exists())
((WebAppClassLoader)context.getClassLoader()).addClassPath(classes);
// Look for jars
Resource lib= web_inf.addPath("lib/");
if (lib.exists() || lib.isDirectory())
((WebAppClassLoader)context.getClassLoader()).addJars(lib);
}
...
}
呼び出したclassloaderの具体的なaddJarsメソッドは次のとおりです.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* ------------------------------------------------------------ */
/** Add elements to the class path for the context from the jar and zip files found
* in the specified resource.
* @param lib the resource that contains the jar and/or zip files.
*/
public void addJars(Resource lib)
{
if (lib.exists() && lib.isDirectory())
{
String[] files=lib.list();
for (int f=0;files!=null && f<files.length;f++)
{
try
{
Resource fn=lib.addPath(files[f]);
String fnlc=fn.getName().toLowerCase();
if (!fn.isDirectory() && isFileSupported(fnlc))
{
String jar=fn.toString();
jar=StringUtil.replace(jar, ",", "%2C");
jar=StringUtil.replace(jar, ";", "%3B");
addClassPath(jar);
}
}
catch (Exception ex)
{
Log.warn(Log.EXCEPTION,ex);
}
}
}
}
addClassPath最終呼び出し親クラスURLClassLoaderのaddURLメソッド
1
2
3
protected void addURL(URL url) {
ucp.addURL(url);
}
classloaderのパスに追加します.クラスがロードされると、この順序でpathを一つ一つ探して、対応するjarパッケージが見つかるかどうかを見ます.
したがって、addClassPathの順序は、そのjarパッケージのクラスがロードされる問題を決定します.
上のプログラムから分かるように、fileがlistされる順序はString[]files=libである.list();決定したのでlibを表示します.list
1
2
3
4
5
6
7
8
9
10
11
12
13
public String[] list()
{
String[] list =_file.list();
if (list==null)
return null;
for (int i=list.length;i-->0;)
{
if (new File(_file,list[i]).isDirectory() &&
!list[i].endsWith("/"))
list[i]+="/";
}
return list;
}
実際に使用するのはjavaです.io.fileのlistメソッド:
1
2
3
4
5
6
7
public String[] list() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
return fs.list(this);
}
最終的に呼び出されるのはFileSystemの抽象的な方法です
public abstract String[] list(File f);
次はプラットフォーム関連のコードです.
jdkソースコードの検索が未熟なので、簡単なコードを書いて、外科的な方法で考察しました.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ListFileTest {
public static void main(String[] args) {
if (args.length == 0 || args[0] == null) {
System.out.println("no args");
System.exit(0);
}
File file = new File(args[0]);
String [] files = file.list();
for (String f :files) {
System.out.println(f);
}
}
}
straceでシステム呼び出しをチェックすると、次のような有用な情報が得られます.
15381 open(".", O_RDONLY|O_NONBLOCK|O_DIRECTORY) = 9
15381 fstat(9, {st_mode=S_IFDIR|0777, st_size=20480, ...}) = 0
15381 fcntl(9, F_SETFD, FD_CLOEXEC) = 0
15381 getdents(9, /* 75 entries */, 4096) = 4072
15381 getdents(9, /* 74 entries */, 4096) = 4080
15381 getdents(9, /* 76 entries */, 4096) = 4080
15381 getdents(9, /* 72 entries */, 4096) = 4064
最終的に呼び出されたのはgetdents(最終的にreaddirを呼び出すようです)で、このシステム関数listのファイルがどのような順序なのか、今のところ分かりませんが、
inode番号によると、試してみてもそうではないようですが、いつも、順番はオペレーティングシステムに関連していて保証できません.
3.ソリューション:
1.jettyのwebAppClassloaderを複写し、リストから出てきたファイルを並べ替え、いくつかのパッケージを指定する順序を前に設定することもできます.
2.mavenによってexcludeの依存を構成するが、互換性を保証するには、互換性がない場合は、両方のライブラリ担当者に連絡して解決する必要がある.
3.パクリ方法、梱包時にjarパッケージの名前を変更するのは、あまり頼りにならない.