- serverovou částí, která běží v Tomcatu
- klientskou částí, která se u uživatelů spouští přes Java Web Start, ale při vývoji na lokálním počítači ji spouštíme klasicky z IDE (při testování ji samozřejmě z testovacích prostředí spouštíme taky přes Java Web Start)
- společnou částí – api.jar, který je sdílen serverem i klientem, obsahuje interfacy servisních metod a DTO objekty
java.io.InvalidClassException: Foo$2; class invalid for deserialization
Příčina
V api.jar existovala třída Foo, která v sobě měla dvě různé instance anonymních tříd (osekávám na minimalistický případ, ve skutečnosti jich bylo víc). Buildování aplikace provádíme Mavenem, který (přesněji maven-compiler-plugin) pro kompilaci tříd používá javac.Vypublikování do Tomcatu však WTP dělalo z adresáře, kam byl api.jar zbuildován pomocí Eclipse JDT.Pozorování ukázalo, že v souboru
Foo$1
je – podle kompilátoru, který jej vytvořil – pokaždé jiná třída. Totéž v souboru Foo$2
. Server a klient tedy měly každý svůj api.jar, v něm stejně pojmenované třídy, ale s jiným obsahem. Server klientovi posílal serializovanou instanci třídy Foo$2
, ale ten pod stejným názvem znal jinou anonymní třídu.Ve třídě
Foo
navíc byly anonymní třídy použity při inicializaci fieldu (to je pro rekonstrukci chyby důležité – viz příklad níže), což není časté a zdůvodňuje to, proč se na problém nepřišlo dřív.Nikde jsem se nedogooglil závazné specifikace, že anonymní třídy se mají číslovat
$1
, $2
..., natož způsob určení pořadí dle jazykových konstruktů ve zdrojáku. (Nejpodobnější byla pouze tato stará chyba v JDT.) Pozorováním se zdá, že javac čísluje anonymní třídy podle jejich výskytu ve zdrojáku, zatímco Eclipse JDT projde nejdřív fieldy (možná tvoří implicitní konstruktor) a pak teprve metody:Příklad
package numbering_anonymous_class; public class Test { public void method() {new Error() {};} public final Object field = new Exception() {}; }
Pozn.: třídy
Error
a Exception
jsou vybrány jen pro stručnost, aby nebylo nutné importovat ani překrývat nic v těle a byl poznat původ v Java decompileru (proto ne new Object() {}
).Po uložení v Eclipse a zkompilování dostává třída ve fieldu číslo
$1
, třída v metodě číslo $2
:c:\java\workspace\work\bin\numbering_anonymous_class>javap -c Test Compiled from "Test.java" public class numbering_anonymous_class.Test extends java.lang.Object{ public final java.lang.Object field; public numbering_anonymous_class.Test(); Code: 0: aload_0 1: invokespecial #10; //Method java/lang/Object."<init>":()V 4: aload_0 5: new #12; //class numbering_anonymous_class/Test$1 8: dup 9: aload_0 10: invokespecial #14; //Method numbering_anonymous_class/Test$1."<init>":(Lnumbering_anonymous_class/Test;)V 13: putfield #17; //Field field:Ljava/lang/Object; 16: return public void method(); Code: 0: new #24; //class numbering_anonymous_class/Test$2 3: aload_0 4: invokespecial #26; //Method numbering_anonymous_class/Test$2."<init>":(Lnumbering_anonymous_class/Test;)V 7: return }
Po zkompilování v javac
c:\java\workspace\work\bin\numbering_anonymous_class>javac -d .. ..\..\src\numbering_anonymous_class\Test.java
dostává třída ve fieldu číslo
$2
, třída v metodě číslo $1
:c:\java\workspace\work\bin\numbering_anonymous_class>javap -c Test Compiled from "Test.java" public class numbering_anonymous_class.Test extends java.lang.Object{ public final java.lang.Object field; public numbering_anonymous_class.Test(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."<init>":()V 4: aload_0 5: new #2; //class numbering_anonymous_class/Test$2 8: dup 9: aload_0 10: invokespecial #3; //Method numbering_anonymous_class/Test$2."<init>":(Lnumbering_anonymous_class/Test;)V 13: putfield #4; //Field field:Ljava/lang/Object; 16: return public void method(); Code: 0: new #5; //class numbering_anonymous_class/Test$1 3: dup 4: aload_0 5: invokespecial #6; //Method numbering_anonymous_class/Test$1."<init>":(Lnumbering_anonymous_class/Test;)V 8: pop 9: return }
Poučení
- Na pořadí anonymních tříd nelze spoléhat.
- Všechno dělat jedním compilerem. (Nebo aspoň mít přehled, který compiler co dělá.)
Pokud s tím máte jiné nebo další zkušenosti, rád do článku doplním update.
Anonymni tridy lze normalizovat (precislovat) pri nacitani class loaderem. Inspiraci muzes najit v projektu HotspawAgent - plugin AnonymousClassPatchPlugin.
OdpovědětVymazatDíky za připomínku. Vždy sice raději preferuji řešení, kdy se počet vrstev nutných pro vyřešení problému minimalizuje, takže myslím, že opravit buildování bylo v dané situaci přímočařejší než do toho zatahovat proprietární classloader. Ale připomínky si vážím, protože se na věc dívá z jiného pohledu, což se může hodit. A zvýšila můj zájem o projekt HotswapAgent.
Vymazat