Vorwort
Kürzlich habe ich während des Lernprozesses ein Problem entdeckt. Abstrakte Klassen können das Objekt nicht durch Neue konstruieren, ohne alle abstrakten Methoden zu implementieren, aber abstrakte Methoden können ihre eigenen Konstruktionsmethoden haben. Das verwirrte mich. Da es eine Konstruktionsmethode gibt und nicht durch neue erstellt werden kann, können abstrakte Klassen instanziiert werden, wenn sie keine konkreten Klassen geworden sind?
In Java können abstrakte Klassen nicht direkt instanziiert werden. Dieses Merkmal abstrakter Klassen wird jedoch häufig zu einem problematischen Hindernis. Wenn ich beispielsweise einen dynamischen Proxy verwenden möchte, um einer abstrakten Klasse die Möglichkeit zu geben, abstrakte Methoden auszuführen, wird es zwei Schwierigkeiten geben: 1. Der dynamische Proxy kann nur ein Proxy -Objekt erstellen, das die Schnittstelle implementiert, jedoch kein Objekt, das die abstrakte Klasse erbt. Für diesen Standard -JVM gibt es einige Implementierungen, wie z. B. Javassist, mit denen dies mithilfe von Bytecode -Tools (Proxyfactory) durchgeführt werden kann.
Wenn Sie ein abstraktes Klassenobjekt in Android konstruieren möchten, haben Sie möglicherweise nur new ClassName() {} oder vererbt. Beide Methoden können jedoch nicht direkt von ihren Klassenobjekten betrieben werden, was zu einigen Problemen führt, die die von uns benötigten abstrakten Funktionen nicht erreichen können.
Hier ist eine detaillierte Beschreibung der im ersten Absatz erwähnten Szene:
Zunächst gibt es eine Schnittstellendatei, die wie folgt definiert ist (Freunde, die mit Android vertraut sind, kann sehen, dass es sich um eine API -Konfigurationsschnittstelle handelt, die nach Restrofit zur Generierung von Proxy -Objekten zur Verfügung gestellt wird):
public interface realapi {@get ("api1") beobachtbar <string> api1 (); @Get ("api2") Observable <string> api2 (); @Get ("api3") Observable <string> api3 (); //...Other Methods}Schreiben Sie als nächstes eine abstrakte Klasse, um nur eine der Methoden der Schnittstelle zu implementieren (zum Simulieren von Schnittstellendaten):
@Mockapipublic abstrakte Klasse Mockapi implementiert RealAPI {Observable <string> api3 () {return Observable.just ("Mock Data"); }}Dann müssen wir ein Tool wie Mockmanager haben, das unsere vorhandene RealAPI -Objekt- und Mockapi -Klasse kombiniert, um ein gemischtes Objekt zu konstruieren. Bei der Ausführung von Methoden, die bereits in Mockapi definiert wurden, werden sie direkt ausgeführt. Wenn Mockapi die Methode nicht definiert, wird die RealAPI -Methode aufgerufen. Die Anrufmethode ist grob:
Realapi api = MockManager.build (Realapi, Mockapi.Class);
Durch Javassist ist es sehr einfach, die obige Funktion zu erfüllen. Erstellen Sie ein proxyfaktorisches Objekt, stellen Sie seine Superklasse auf Mockapi ein, filtern Sie dann die abstrakte Methode und setzen Sie den Methodenhandler, um das RealAPI -Objekt mit derselben Namens- und Parametemethode aufzurufen. Die Code -Implementierung wird hier nicht angegeben.
Aber auf Android wird die Methode des Javassisten geworfen
Verursacht durch: java.lang.unsupportedoperationException: Diese Art von Klassendatei kann nicht laden. javassist.util.proxy.factoryHelper.toclass2 (factoryHelper.java:182)
Ähnliche Ausnahmen. Der Grund dafür ist wahrscheinlich, dass die Implementierung und die Standards von virtuellen Maschinen auf Android geringfügig unterschiedlich sind. Hier verwandeln wir die Richtung in eine andere Richtung des Annotationsprozessors für dynamische Codegenerierung.
Wenn Sie Annotationsprozessor verwenden, um sie zu implementieren, ist die Idee viel einfacher, aber der Prozess ist immer noch etwas gewunden:
Definieren Sie zuerst eine Annotation, um die abstrakte Klasse zu markieren, die konstruiert werden muss
@Target (elementtype.type)@documented@retention (retentionPolicy.source) public @Interface Mockapi {} Der Prozessor erhält das Elementobjekt der Klasse basierend auf der Annotation, die ein klassenähnliches Objekt ist. Da die Klasse in der Vorkompilierungsphase noch nicht existiert, ist es unmöglich, das von der Laufzeit geforderte Klassenobjekt mit Class.forName zu erhalten. Element bietet jedoch Methoden, die wie die klassenreflexionsbezogene Methoden ähneln, und es gibt auch Unterschiede wie Typelement und exekutive. Was sind die abstrakten Methoden der abstrakten Klasse, die mit dem Element-Objekt kommentiert werden, um die kommentierte abstrakte Klasse zu analysieren, eine Implementierungsklasse (nicht abstrakt) zu generieren, die die Klasse erbt und alle abstrakten Methoden in der Klasse implementieren. Da diese abstrakten Methoden nicht tatsächlich verwendet werden, müssen sie nur kompilieren und passieren können. Die Art und Weise, wie ich ausgewählt habe, ist, dass jeder Methodenkörper eine Ausnahme ausgelegt hat, was dazu veranlasst, dass die Methode eine abstrakte Methode ist und nicht direkt aufgerufen werden kann. Die Methode zum Generieren von Code kann einige Tools verwenden, um die Arbeit zu vereinfachen, z. B. Autoprocessor und Javapoet, die den Projektcode am Ende des Referenztextes spezifisch implementiert. Der generierte Code ist ungefähr so:
// Der generierte Klassenname wird mit dem Suffix des ursprünglichen Klassennamens + "$ impl" benannt, um Konflikte mit anderen Klassennamen zu vermeiden. Diese Einschränkung wird auch verwendet, um die Klasse öffentlicher endgültiger Klasse Mockapi $ impl zu widerspiegeln. } @Override public Observable <string> api2 () {neue illegaleStateException werfen ("api2 () ist eine abstrakte Methode!"); }}Reflektieren Sie die Implementierungsklasse basierend auf dem Klassennamen der abstrakten Klasse und erstellen Sie dann ein Implementierungsobjekt basierend auf der Reflexion, indem Sie seine Konstruktormethode aufrufen.
// Erhalten Sie das Objekt, das das Code Construct private statische <T> t GetImpObject (Klasse <T> cls) {try {return (t) class.forname (cls.getName () + "$ impl"). NewInstance (); } catch (Ausnahme e) {return null; }}Konstruieren Sie einen dynamischen Proxy, geben Sie ihn in das reale Objekt von RealAPI und das Implementierungsobjekt der im vorherigen Schritt konstruierten abstrakten Klasse weiter und bestimmen Sie, welches Objekt sein Methodverhalten basierend auf der Definition in der abstrakten Klasse verfolgt: Wenn in der abstrakten Klasse eine Definition vorliegt, ist die Methode keine abstrakte Methode, wobei die Implementierungsobjekte der abstrakten Klasse ausgeführt wird. Andernfalls wird es vom realen Objekt der Schnittstelle ausgeführt.
public static <Origin, Mock erweitert Origin> Ursprung Build (endgültige Herkunftsurkunde, endgültige Klasse <Mock> Mockclass) {// Wenn die Mock -Klasse als geschlossen markiert ist, gibt es direkt das reale Schnittstellenobjekt zurück, wenn (! iSenable (Mockclass)) {return origin; } Final Mock MockObject = getImpObject (MockClass); Klasse <?> OriginClass = Origin.getClass (). GetInterFaces () [0]; return (Origin) proxy.newproxyinstance (OriginClass.getClassloader (), New Class [] {OriginClass}, New InvocationHandler () {@Override öffentliches Objekt Invoke (Objekt O, Methode Methode, Objekt [] Objekte) wirft Throwable {// die Methode desselben Namens ab. MockClass.getDeclaredMethod (method.getName (), method.getParameterTypes ()); MockMethod.Invoke (MockObject, Objekte);}}});}});}Nach Abschluss der oben genannten Arbeit können Sie die Build -Methode verwenden, um ein Proxy -Objekt zu erstellen, das reale Schnittstellen und abstrakte Klassenmethoden mischt, wie zu Beginn erwähnt. Obwohl die Anrufklasse im Wesentlichen hart codiert ist, wird sie automatisch durch Annotationsprozessor ohne manuelle Wartung generiert. In Bezug auf die Verwendung ist es im Grunde genommen mit der Verwendung der Javassist -Implementierung.
Ich habe die Methode verwendet, zu der ich in diesem Artikel angehörte, um ein Tool zu implementieren, das Nachrüstanforderungen simuliert (es gibt einen Link am Ende des Artikels), aber im Wesentlichen kann es verwendet werden, um viele Anforderungen zu implementieren, die die Konstruktion abstrakter Klassen erfordern, und mehr Nutzungsszenarien müssen noch untersucht werden.
Die im Artikel erwähnte Quellcode-Implementierung finden Sie im Projekt Retrofit-Mock-Result oder lokaler Download von Project.
Zusammenfassen
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, dass der Inhalt dieses Artikels einen gewissen Referenzwert für das Studium oder die Arbeit eines jeden hat. Wenn Sie Fragen haben, können Sie eine Nachricht zur Kommunikation überlassen. Vielen Dank für Ihre Unterstützung bei Wulin.com.