- 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