1. Algunos conceptos básicos
Antes de comenzar, necesitamos declarar algo importante: no estamos discutiendo las anotaciones que se procesan a través de mecanismos de reflexión en tiempo de ejecución, sino discutiendo las anotaciones que se procesan en el momento de la compilación.
¿Cuál es la diferencia entre anotaciones en tiempo de compilación y anotaciones en tiempo de ejecución? De hecho, no es grande, principalmente debido a problemas de rendimiento. Las anotaciones de tiempo de ejecución dependen principalmente de la reflexión, y la eficiencia de la reflexión es más lenta que la de las nativas, por lo que habrá un retraso en las máquinas con menos memoria y una CPU pobre. Sin embargo, la anotación no tendrá este problema en absoluto, porque en nuestro proceso de compilación (clase Java->), algunas marcas de anotación se utilizan para generar dinámicamente algunas clases o archivos, por lo que no tiene nada que ver con nuestra operación APK, por lo que naturalmente no hay ningún problema de rendimiento. Por lo tanto, si un proyecto de código abierto más famoso utiliza la función de anotación, generalmente usa anotación en tiempo de compilación.
El procesador de anotaciones es una herramienta que viene con Javac, que se utiliza para escanear y procesar la información de anotación durante el período de compilación. Puede registrar su propio procesador de anotación para ciertas anotaciones. Aquí, supongo que ya entiendes qué son las anotaciones y cómo personalizarlas. Si aún no ha entendido las anotaciones, puede consultar la documentación oficial. Los procesadores de anotación ya existían en Java 5, pero no había API disponible hasta Java 6 (lanzado en diciembre de 2006). Pasó un tiempo antes de que los usuarios de Java se dieron cuenta del poder del procesador de anotaciones. Por lo tanto, solo se ha vuelto popular en los últimos años.
Un procesador con una anotación específica toma el código fuente de Java (o compilado ByTecode) como entrada y luego genera algunos archivos (generalmente .java archivos) como salida. ¿Qué significa eso? ¡Puedes generar código Java! Estos códigos Java están en el archivo .java generado. Por lo tanto, no puede cambiar la clase Java existente, como agregar un método. Estos archivos Java generados serán compilados por Javac como otro código fuente Java escrito manualmente.
El procesamiento de anotación se ejecuta en la etapa de compilación. Su principio es leer el código fuente de Java, analizar las anotaciones y luego generar un nuevo código Java. El código Java recién generado finalmente se compila en Java Bytecode. El procesador de anotaciones no puede cambiar la clase de lectura Java, como agregar o eliminar los métodos Java.
2. Abstract -Processor
Echemos un vistazo a la API del procesador. Todos los procesadores heredan AbstractProcessor, como se muestra a continuación:
paquete com.example; import java.util.linkedhashset; import java.util.set; import javax.annotation.processing.abstractprocessor; import javax.annotation.processing.processingenvironment; import javax.annotation.processing.roundedment; importación; javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.supportedSourCeVersion; import javax.lang.model.sourceVersion; import javax.lang.model.Element.TyTypeelement; MyProcessor de clase pública extiende abstracsor { @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @@overré TIPSELEMENT> anuncios, redonvironment env) {return false; } @Override Public set <String> getSupportedAnnotationTypes () {set <string> annotataions = new LinkedHashset <String> (); annotataions.add ("com.example.myannotation"); Annotataiones de retorno; } @Override public SourceVersion GetSupportedSourCeVersion () {return SourceVersion.LatestSupported (); } @Override public sincronized void init (procesamiento de procesamiento de procesamiento de mentalidad) {super.init (processenv); }}INIT (ProcessingEnvironment Processenv): todas las clases de procesador de anotación deben tener un constructor sin parámetros. Sin embargo, existe un método especial init (), que la herramienta de procesamiento de anotaciones llama, tomando el entorno de procesamiento como un parámetro. ProcessingEnvironment proporciona algunos elementos, tipos y archivos de clases de herramientas prácticas. Los usaremos más tarde.
process(Set<? extends TypeElement> annoations, RoundEnvironment env) : esto es similar al método Main () de cada procesador. Puede codificar e implementar el escaneo, las anotaciones de procesamiento y la generación de archivos Java en este método. Usando el parámetro Roundenvironment, puede consultar elementos anotados con una cierta anotación (texto original: puede consultar para elementos anotados con una cierta anotación). Veremos los detalles más adelante.
getSupportedAnnotationTypes(): en este método debe especificar qué anotaciones deben registrar el procesador de anotaciones. Tenga en cuenta que su valor de retorno es una colección de cadenas que contiene el nombre completo del tipo de anotación que su procesador de anotación quiere procesar. En otras palabras, define aquí qué anotaciones manejará su procesador de anotación.
getSupportedSourceVersion() : se usa para especificar la versión Java que usa. Por lo general, debe devolver SourceVersion.latestSupported() . Sin embargo, si tiene suficientes razones para quedarse con Java 6, también puede devolver SourceVersion.RELEASE_6 . Recomiendo usar SourceVersion.latestSupported() . En Java 7, también puede usar anotaciones para reemplazar la reescritura getSupportedAnnotationTypes() y getSupportedSourceVersion() , como se muestra a continuación:
@SupportedSourCeVersion (value = SourceVersion.Release_7) @SupportedAnnotationTypes ({// conjunto de nombres de tipos de anotación Qulified completos "com.example.myAnnotation", "com.example.anotherannotation"}) clase pública myProcessor extiende abstractorscessor {@Override boolean Process (set <? Roundenvironment env) {return false; } @Override public sincronized void init (procesamiento de procesamiento de procesamiento de mentalidad) {super.init (processenv); }} Debido a problemas de compatibilidad, especialmente para Android, recomiendo reescribir getSupportedAnnotationTypes() y getSupportedSourceVersion() en lugar de usar @SupportedAnnotationTypes y @SupportedSourceVersion .
Lo siguiente que debe saber es: el procesador de anotación se ejecuta en su propio JVM. Sí, lo leíste bien. Javac comienza una máquina virtual Java completa para ejecutar el procesador de anotación. ¿qué significa eso? Puede usar cualquier cosa que use en un programa Java normal. ¡Usando guayaba! Puede usar herramientas de inyección de dependencia como daga o cualquier otra biblioteca de clases que desee usar. Pero no olvide que incluso si es solo un pequeño procesador, debe prestar atención al uso de algoritmos eficientes y patrones de diseño, tal como lo hace en el desarrollo de otros programas de Java.
3. Registre su procesador
Puede preguntar "cómo registrar mi procesador de anotación a Javac?". Debe proporcionar un archivo .jar. Al igual que otros archivos .JAR, empaqueta su procesador de anotación ya compilado en este archivo. Y, en su archivo .jar, debe empaquetar un archivo especial javax.annotation.processing.processor en el directorio de meta-INF/Servicios. Entonces su estructura de directorio de archivos .JAR se ve así:
Myprocess.jar -com -example -myprocess.class -meta -inf -services -javax.annotation.processing.processor
El contenido del archivo Javax.annotation.processing.processor es una lista, y cada línea es el nombre completo de un procesador de anotaciones. Por ejemplo:
com.example.myprocess
com.example.another process
4. Ejemplo: modelo de fábrica
El problema que queremos resolver es: queremos implementar una tienda de pizza, que brinda a los clientes dos pizza (Margherita y Calzone), así como el postre Tiramisu (tiramisu).
Comida de interfaz pública {public float getPrice ();} clase pública Margheritapizza implementa la comida {@Override public float getPrice () {return 6.0f; }} Clase pública Calzonepizza implementa la comida {@Override public float getPrice () {return 8.5f; }} clase pública tiramisu implementa harina {@Override public float getPrice () {return 4.5f; }} public class Pizzastore {Order Public Meal (String MealName) {if (null == MealName) {Throw New IlegalArGumentException ("¡El nombre de la comida es nulo!"); } if ("Margherita" .equals (MealName)) {return new Margheritapizza (); } if ("Calzone" .equals (MealName)) {return new CalzonePizza (); } if ("tiramisu" .equals (MealName)) {return new Tiramisu (); } tirar nueva ilegalArgumentException ("Comida desconocida" + Mealname + "'"); } cadena estática privada ReadConsole () {Scanner Scanner = New Scanner (System.in); String Meal = Scanner.NextLine (); Scanner.close (); Comida de regreso; } public static void main (string [] args) {System.out.println ("Bienvenido a la tienda Pizza"); Pizzastore pizzastore = new Pizzastore (); Comida comida = pizzastore.order (readConsole ()); System.out.println ("Bill: $" + Meal.getPrice ()); }}Como puede ver, en el método Order (), tenemos muchas declaraciones de juicio de condición. Y, si agregamos una nueva pizza, tenemos que agregar un juicio nuevo y condicional. Pero espere, utilizando el procesador de anotaciones y el modo de fábrica, podemos tener un procesador de anotación generar estas declaraciones IF. De esta manera, el código que queremos se ve así:
Public Class Pizzastore {Private MealFactory Factory = new MealFactory (); Orden de comidas públicas (String MealName) {return factory.create (MealName); } cadena estática privada ReadConsole () {Scanner Scanner = New Scanner (System.in); String Meal = Scanner.NextLine (); Scanner.close (); Comida de regreso; } public static void main (string [] args) {System.out.println ("Bienvenido a la tienda Pizza"); Pizzastore pizzastore = new Pizzastore (); Comida comida = pizzastore.order (readConsole ()); System.out.println ("Bill: $" + Meal.getPrice ()); }} public class MealFactory {Public Meal Create (String ID) {if (id == NULL) {Throw New IlegalArGumentException ("ID es nulo!"); } if ("calzone" .equals (id)) {return new CalzonePizza (); } if ("tiramisu" .equals (id)) {return new Tiramisu (); } if ("margherita" .equals (id)) {return new Margheritapizza (); } tirar nueva ilegalArgumentException ("desconocido id =" + id); }}5. @Factory anotación
¿Puedes adivinar que planeamos usar el procesador de anotación para generar la clase MealFactory? En términos más generales, queremos proporcionar una anotación y un procesador para generar clases de fábrica.
Echemos un vistazo a la anotación @Factory:
@Target (ElementType.Type) @Retention (retentionPolicy.class) public @Interface Factory { / ** * El nombre de la fábrica * / class <?> Type (); / ** * El identificador para determinar qué elemento debe ser instanciado */ String id ();}La idea es la siguiente: anotamos esas clases de alimentos, usamos tipo () para indicar a qué fábrica pertenece esta clase y usamos id () para indicar el tipo específico de esta clase. Aplicemos la anotación @Factory a estas clases:
@Factory (type = margheritapizza.class, id = "margherita") clase pública margheritapizza implementa harina {@Override public float getPrice () {return 6.0f; }} @Factory (type = calzonepizza.class, id = "calzone") clase pública calzonepizza implementa harina {@Override public float getPrice () {return 8.5f; }} @Fábrica (type = tiramisu.class, id = "tiramisu") public class tiramisu implementa harina {@Override public float getPrice () {return 4.5f; }}Puede preguntar, ¿podemos aplicar la anotación @Factory a la interfaz de la comida? La respuesta es no, porque la anotación no puede ser heredada. Es decir, si hay anotaciones en la clase X, la clase Y extiende x, entonces la clase Y no heredará las anotaciones en la clase X. Antes de escribir un procesador, necesitamos aclarar algunas reglas:
Procesador de anotación:
Public Class FactoryProcessor extiende AbstractProcessor {Tipos privados TykeUtils; Elementos privados ElementUtils; Filer de archivo privado; Messager privado Messager; Mapa privado <String, FactoryGroupedClasses> FactoryClasses = new LinkedHashmap <String, FactoryGroupedClasses> (); @Override public sincronized void init (procesamiento de procesamiento de mentalidad) {super.init (procesamiento -V); thingutils = processenv.gettypeutils (); elementUtils = processenv.getElementUtils (); filer = processenv.getFiler (); Messager = Processingenv.getMessager (); } @Override Public Boolean Process (set <? Extiende TypeElement> Arg0, Roundenvironment arg1) {... return false; } @Override Public set <String> getSupportedAnnotationTypes () {set <string> annotataions = new LinkedHashset <String> (); annotataions.add (factory.class.getCanonicalName ()); Annotataiones de retorno; } @Override public SourceVersion GetSupportedSourCeVersion () {return SourceVersion.LatestSupported (); }} En el método getSupportedAnnotationTypes() , especificamos que la anotación @Factory será procesada por este procesador.
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.