tifyty

pure Java, what else ?

Hogyan hozzunk létre példányt a System osztályból?

Ez a kérdés mindenféle fórumokon elég sokszor előfordul. Legutóbb a LinkedIn-en láttam, és gondoltam egy kicsit érdemes körbejárni a témát. Általában kétféle válasz szokott jönni a kérdésre: vizsgáljuk meg mind a kettőt, és aztán nézzük meg, hogy hova vezetnek ezek a gondolatok.

A kérdés maga azért vetődik fel, mert a System osztály egy utility class, amelyiknek csak statikus metódusai és field-jei vannak, és nem arra készült, hogy példányt hozzunk létre belőle. Ennek okán van is egy argumentum nélküli, privát metódusa, ami miatt nem lehet olyan módon példányosítani, hogy

System system = new System();

mert fordítási hibát kapunk.

Válasz#1, használjunk reflection-t!

Constructor<System> constructor = System.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
System system = (System) constructor.newInstance(null);

Ez egy nagyon egyenes válasz, a kérdésre ad választ és elindíthat azon az úton, hogy motiváljon a reflection megtanulására. De ennél többet ez a válasz nem ad, és aki a címben szereplő kérdést teszi fel, könnyen lehet, hogy nem a megfelelő irányba indul el a Java használata során.

Azok a válaszolók, akik inkább akarnak oktatni, és a helyes irányba terelni a következő választ adják:

Válasz#2, Ne tedd!

Miért akarnánk egy példányt a System osztályból? Nincs semmi értelme. A SUN mérnökei mindent megtettek, hogy ne tudjál belőle példányt létrehozni.

Valóban? Mindent megtettek?

Nos, igen. Mindent, amit értelmesen tenni érdemes. De persze a paranoiások számára lehet többet is.

Hogyan védhetjük meg a utility osztályokat?

A System osztály úgy van védve, ahogyan, és hogy miért nem jobban, arra a cikk végén még visszatérünk. De ezen túl vannak még egyéb lehetőségek is. A lehetőségek, amik eszembe jutottak:

  1. Dokumentáljuk, hogy az osztály nem arra van, hogy példányosítsák. Azt gondolom, hogy már ez is sokat segít, de nem teljes megoldás, és nem állhatunk meg itt, mert előfordulhat, hogy valaki nem olvassa el a dokumentációt, és ez bocsánatos bűn.
  2. Készítsünk egy argumentum nélküli privát konstruktor. Ez meggátolja az egyszerű felhasználót, hogy csak úgy létrehozzon egy példányt. Ha volt annyira botor, hogy nem olvasta el a dokumentációt, akkor is megállítja az a tény, hogy a fordító nem fordítja le a new UtilityClass kódot. De persze ettől még használhat reflection-t. Persze kit érdekel? Aki hülye, haljon meg. De most csak a kíváncsiság kedvvért, minket érdekel.
  3. Ne hívjuk meg ezt a privát konstruktor semmilyen statikus metdusból. Elég triviálisnak tűnik, hiszen ezzel éppen magunkat vágnánk pofon.
  4. Legyen az osztály final. Miért segít ez? Ez nem gátolja meg a direkt felhasználót, hogy reflection-nel hozzon létre egy példányt, de megakadályozza, hogy leszármaztassa az osztályt, és utána a leszármazott osztályból hozzon létre egy példány reflection-nel. Persze mondhatjuk, hogy megérdemli, ha ilyen speciális eszközökhöz nyúl, viszont ha az osztály final akkor legalább azt az ártatlan programozót védjük, aki a leszármazott osztályt használná és nem is tudja, hogy olyan osztály példányát hozta létre, amit nem kellett volna. Amúgy meg az utility osztályban csak statikus metódusok vannak, amik nem öröklődnek: minek a leszármazott. Maximum a rossz géneket örökli.
  5. Végül, de nem utolsó sorban (itt a kegyelemdöfés!) dobjunk egy run-time kivételt a privát konstruktorból. Még ha el is kapja ezt a kivételt a reflection-nel operáló kód, a félkész objektumhoz nem fér hozzá, mert nincs rá referenciája. A konstruktor így nem csak nem hívható meg egyszerűen, de ha reflection-nel mégis meghívják: nem tér vissza.

Ezek azok a trükkök, amelyeket egy utility osztály védelmére lehet használni. Lehetnek még más trükkök is, de ezek az eszközök is elegendőek kellenek, hogy legyenek. A SUN mérnökei csak annyit tettek, hogy az osztály final és a konstruktor private. Valószínűleg úgy gondolták, hogy minden egyéb csak túlpörgés. Én meg hajlok arra, hogy egyetértsek velük.

Bónusz kérdés oroszlánszívűeknek:

Hogyan hozol létre egy példányt egy olyan osztályból, amelyik a fent felsorolt ÖSSZES védelemmel rendelkezik ez ellen?

13 responses to “Hogyan hozzunk létre példányt a System osztályból?

  1. Kofa augusztus 28, 2013 11:25 de.

    Deszerializáció? (Persze a bináris kódot kézzel kellene összerakni.)

    • Peter Verhas augusztus 28, 2013 12:56 du.

      Igen, ez az egyik lehetőség. Én nem ezt választanám, mert van ennél járhatóbb (egyszerűbb) is, bár nem várható el még akár egy senior-tól sem,hogy ismerje a SUN belső csomagokat. És persze nincs minden JVM-ben nem hivatalos, kompatibilis runtime.

  2. A. augusztus 28, 2013 12:40 du.

    -saját javaagent
    -http://docs.oracle.com/javase/6/docs/api/java/lang/instrument/ClassFileTransformer.html
    de ez most csak egy gyors tipp..

  3. tvk augusztus 28, 2013 1:50 du.

    Valami röptében történő bájtkódmanipulációval gondolom meg lehet oldani, valószínűleg javaagent is szerepelne a történetben. Egyébként ilyen példányosíthatatlan, final meg mindenféle osztályok példányosítása a való életben is előfordul mint probléma. Főleg tesztkódban, unit teszteknél találkoztam vele. (Más kérdés, hogy ha ilyen probléma előfordul, akkor a kód nem lett valami jól tesztelhetőre megírva.) A PowerMock framework-öt lehet használni ilyen célra.

  4. Peter Verhas augusztus 28, 2013 3:57 du.

    A java agent valószínűleg működő megoldás, de nagyon komoly előfeltételeket támaszt. Ezzel az erővel azt is mondhatom, hogy saját class loadert írok, amelyik módosítja az osztály bájtkódját úgy, hogy legyen példányosítható. És persze az kell, hogy ez a class loader töltse be az osztályt.

  5. tornaia augusztus 28, 2013 4:24 du.

    Akkor javaagent nélküli megoldás:

    Őt:

    public final class MyUtilityClass {
    	private MyUtilityClass() {
    		throw new RuntimeException("HAHA");
    	}
    	public void doSomething() {
    		System.out.println(":-)");
    	}
    }
    

    Így:

    public class MyMain {
    
    	public static void main(String[] args) throws Exception {
    		MyUtilityClass create = create(MyUtilityClass.class, Object.class);
    		create.doSomething();
    	}
    
    	public static  T create(Class clazz, Class parent) throws Exception {
    		ReflectionFactory rf = ReflectionFactory.getReflectionFactory();
    		Constructor objDef = parent.getDeclaredConstructor();
    		Constructor intConstr = rf.newConstructorForSerialization(clazz, objDef);
    		return clazz.cast(intConstr.newInstance());
    	}
    }
    
  6. Peter Verhas augusztus 28, 2013 5:10 du.

    Hozzátéve, hogy magam sem ismerek olyan megoldást, amelyik ne használna valamilyen nem hivatalos API-t, mielőtt valaki elkezdené használni a sun.reflect csomagbeli osztályokat olvassa el a következő figyelmeztetést:

    http://www.oracle.com/technetwork/java/faq-sun-packages-142232.html

    Amúgy, hogy nem csak üres fenyegetés a dolog:

    http://www.infoq.com/news/2013/07/Oracle-Removes-getCallerClass

    az ORACLE ki akarja vonni az egyik metódust, és mi tartaná vissza. Talán az, hogy ezzel agyonvágja a Groovy 1.8-at?

    http://www.infoq.com/news/2013/08/Oracle-Resurrects-getCallerClass

    Szóval ez egy érdekes téma.

  7. tornaia augusztus 28, 2013 5:12 du.

    3. se nem agent, se nem sun.reflect, de… 🙂

    import sun.misc.Unsafe;
    
    public class MyMain2 {
    
    	public static void main(String[] args) throws Exception {
    		MyUtilityClass utilityClass = (MyUtilityClass) getUnsafe().allocateInstance(MyUtilityClass.class);
    		utilityClass.doSomething();
    	}
    	
    	private static Unsafe getUnsafe() throws Exception {
    		Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);
    	}
    }
    
  8. hron84 február 13, 2014 12:42 du.


    #!/usr/bin/env groovy

    system = new System()

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s

%d blogger ezt kedveli: