Préface
Récemment, pendant le processus d'apprentissage, j'ai découvert un problème. Les classes abstraites ne peuvent pas construire l'objet via Nouveau sans mettre en œuvre toutes les méthodes abstraites, mais les méthodes abstraites peuvent avoir leurs propres méthodes de construction. Cela m'a confondu. Puisqu'il existe une méthode de construction et qu'il ne peut pas être créé via de nouvelles, les classes abstraites peuvent-elles être instanciées lorsqu'ils ne sont pas devenus des classes concrètes?
En Java, les classes abstraites ne peuvent pas être instanciées directement. Cependant, cette caractéristique des classes abstraites devient souvent un obstacle gênant. Par exemple, si je souhaite utiliser un proxy dynamique pour donner à une classe abstraite la possibilité d'exécuter des méthodes abstraites, il y aura deux difficultés: 1. Le proxy dynamique ne peut créer qu'un objet proxy qui implémente l'interface, mais pas un objet qui hérite de la classe abstraite. Pour ce JVM standard, il existe certaines implémentations, telles que Javassist, qui peuvent être utilisées pour y parvenir en utilisant des outils ByteCode (ProxyFactory).
Si vous souhaitez construire un objet de classe abstrait dans Android, vous pouvez avoir uniquement new ClassName() {} ou hérité. Cependant, les deux méthodes ne peuvent pas être directement exploitées par leurs objets de classe, ce qui entraîne certains problèmes qui ne peuvent pas atteindre les capacités abstraites dont nous avons besoin.
Voici une description détaillée de la scène mentionnée dans le premier paragraphe:
Tout d'abord, il existe un fichier d'interface défini comme suit (les amis familiers avec Android peuvent voir qu'il s'agit d'une interface de configuration d'API fournie pour rétrofiler pour générer des objets proxy):
interface publique realAPI {@get ("api1") observable <string> api1 (); @Get ("api2") observable <string> api2 (); @Get ("api3") observable <string> api3 (); //... Autres méthodes}Ensuite, écrivez une classe abstraite pour implémenter une seule des méthodes de l'interface (utilisée pour simuler les données d'interface):
@Mockapipublic Résumé Classe MockAPI implémente RealAPI {observable <string> api3 () {return observable.Just ("Mock Data"); }}Ensuite, nous devons avoir un outil, comme MockManager, qui combine notre objet RealAPI et notre classe MockAPI existants pour construire un objet mixte. Lors de l'exécution de méthodes déjà définies dans MockAPI, elle les exécutera directement. Lorsque MockAPI ne définit pas la méthode, elle appellera la méthode RealAPI. La méthode d'appel est à peu près:
Realapi api = mockManager.build (realapi, mockapi.class);
Grâce à Javassist, il est très simple de remplir la fonction ci-dessus. Créez un objet proxyfactoire, définissez sa superclasse sur MockAPI, puis filtrez la méthode abstraite et définissez le gestionnaire de méthode pour appeler l'objet RealAPI avec le même nom et la même méthode de paramètre. L'implémentation du code ne sera pas donnée ici.
Mais sur Android, la méthode de Javassiste sera jetée
Causé par: java.lang.unsupportedOperationException: Impossible de charger ce type de fichier de classe sur java.lang.classloader.defineclass (classloader.java:520) à java.lang.reflect.method.invoke (méthode native) à Java.lang.reflect.method.invoke (méthode native) à Java.lang.reflect.Method.invoke (méthode native) à Java.lang.Reflect.Method.invoke (méthode native) à Java.lang.Reflect.Method.invoke (Méthode native) à Java.Lang.Reflect.Method.inVoke (Méthode native) à Java.Lang.Reflect.Method.invoke (Méthode native) AT AT javassist.util.proxy.factoryhelper.toclass2 (factoryhelper.java:182)
Exceptions similaires. La raison en est probablement que l'implémentation et les normes des machines virtuelles sur Android sont légèrement différentes, alors nous tournons ici la direction vers une autre direction du processeur d'annotation de génération de code dynamique.
Si vous utilisez un processeur Annotation pour l'implémenter, l'idée est beaucoup plus simple, mais le processus est toujours un peu tortueux:
Définissez d'abord une annotation pour marquer la classe abstraite qui doit être construite
@Target (elementType.Type) @ documenté @ rétention (retentionPolicy.source) public @interface mockapi {} Le processeur obtient l'objet élément de la classe en fonction de l'annotation, qui est un objet de type classe. Parce que la classe n'existe pas encore dans l'étape de précompilation, il est impossible d'obtenir l'objet de classe requis par l'exécution à l'aide Class.forName . Cependant, l'élément fournit des méthodes similaires à la réflexion de classe liées, et il existe également des différences telles que TypeElement et ExecutableElement. Quelles sont les méthodes abstraites de la classe abstraite annotée à l'aide de l'objet Element pour analyser la classe abstraite annotée, générer une classe d'implémentation (non abstracte) qui hérite de la classe et implémenter toutes les méthodes abstraites de la classe. Étant donné que ces méthodes abstraites ne seront pas réellement utilisées, elles doivent seulement pouvoir compiler et passer. La façon dont j'ai choisi est que chaque corps de méthode lance une exception, ce qui incite que la méthode est une méthode abstraite et ne peut pas être appelée directement. La méthode de génération de code peut utiliser certains outils pour simplifier le travail, tel que l'autoprocesseur et Javapoet, qui implémente spécifiquement le code de projet à la fin du texte de référence. Le code généré est à peu près comme ceci:
// Le nom de classe généré est nommé avec le suffixe du nom de classe d'origine + "$ impl" pour éviter les conflits avec d'autres noms de classe. Cette contrainte est également utilisée pour refléter la classe publique finale de la classe Mockapi $ implante étend mockapi {@Override public observable <string> api1 () {throw new illégalstateException ("api1 () est une méthode abstraite!"); } @Override public observable <string> api2 () {throw new illégalstateException ("api2 () est une méthode abstraite!"); }}Reflétez la classe d'implémentation en fonction du nom de classe de la classe abstraite, puis construisez un objet d'implémentation basé sur la réflexion en appelant sa méthode de constructeur.
// Obtenez l'objet qui génère le code Construct Static Private STATIC <T> T getIMPloBject (class <T> CLS) {try {return (t) class.forname (cls.getName () + "$ impl"). NewInstance (); } catch (exception e) {return null; }}Construisez un proxy dynamique, passez-le dans l'objet réel de RealAPI et de l'objet d'implémentation de la classe abstrait construit à l'étape précédente, et déterminez quel objet indique son comportement de méthode basé sur la définition de la classe abstraite: s'il existe une définition dans la classe abstraite, c'est-à-dire que la méthode n'est pas une méthode abstraite, l'objet d'implémentation de la classe abstrait sera exécuté; Sinon, il sera exécuté par l'objet réel de l'interface.
Public Static <Origin, Mock étend Origin> Build Origin (Final Origin Origin, Final Class <from> MockClass) {// Si Mock Class est marqué comme fermé, il renverra directement l'objet d'interface réel si (! Isenable (MockClass)) {Retour Origin; } Final Mock MockObject = GetImpLobject (MockClass); Classe <?> Originclass = origine.getClass (). GetInterfaces () [0]; return (Origin) proxy.newproxyinstance (originclass.getClassloadher (), new class [] {originclass}, new invocationhandler () {@Override public objet invoke (objet o, méthode, objet [] objets) lance le throws throws {// Obtenez la méthode du même nom dans la classe défini et déterminent la méthode = MockClass.getDeclaredMethod (Method.GetName (), méthode.getParAmEterTypes ());} Catch (NosuchMethoDException ignorée) {} if (mockMethod == Null || RETOURNE.Invater (MockMethod); MockMethod.Invoke (MockObject, objets);}}});}});}Après avoir terminé le travail ci-dessus, vous pouvez utiliser la méthode de construction pour construire un objet proxy qui mélange des interfaces réelles et des méthodes de classe abstraites comme mentionné au début. Bien que la classe d'appels soit essentiellement codée par dure, elle est automatiquement générée par le processeur d'annotation sans maintenance manuelle. En termes d'utilisation, il est essentiellement la même que l'utilisation de l'implémentation Javassiste.
J'ai utilisé la méthode à laquelle j'appartiens dans cet article pour implémenter un outil qui simule les demandes de rétrofice (il y a un lien à la fin de l'article), mais en substance, il peut être utilisé pour mettre en œuvre de nombreux besoins qui nécessitent la construction de classes abstraites, et d'autres scénarios d'utilisation doivent encore être explorés.
La mise en œuvre du code source mentionné dans l'article se trouve dans le projet Retrofit-Mock-Result ou téléchargement local;
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.