- 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