Java >> Programma Java >  >> Java

Registrazione del metodo Java con AOP e annotazioni

A volte, voglio registrare (tramite slf4j e log4j) ogni esecuzione di un metodo, vedendo quali argomenti riceve, cosa restituisce e quanto tempo impiega ogni esecuzione. Ecco come lo sto facendo, con l'aiuto di AspectJ, jcabi-aspects e Java 6 annotations:







public class Foo {
  @Loggable
  public int power(int x, int p) {
    return Math.pow(x, p);
  }
}

Questo è ciò che vedo nell'output di log4j:

[INFO] com.example.Foo #power(2, 10): 1024 in 12μs
[INFO] com.example.Foo #power(3, 3): 27 in 4μs

Bello, vero? Ora vediamo come funziona.

Annotazione con conservazione del runtime

Le annotazioni è una tecnica introdotta in Java 6. È uno strumento di meta-programmazione che non cambia il modo in cui funziona il codice, ma dà segni su determinati elementi (metodi, classi o variabili). In altre parole, le annotazioni sono solo marcatori attaccati al codice che possono essere visti e letti. Alcune annotazioni sono progettate per essere visualizzate solo in fase di compilazione:non esistono in .class file dopo la compilazione. Altri rimangono visibili dopo la compilazione e sono accessibili in runtime.

Ad esempio, @Override è del primo tipo (il suo tipo di conservazione è SOURCE ), mentre @Test da JUnit è del secondo tipo (il tipo di conservazione è RUNTIME ). @Loggable - quella che sto usando nello script sopra - è un'annotazione del secondo tipo, da jcabi-aspects. Rimane con il bytecode nel .class file dopo la compilazione.

Ancora una volta, è importante capire che nonostante il metodo power() è annotato e compilato, finora non invia nulla a slf4j. Contiene solo un indicatore che dice "per favore, registra la mia esecuzione".

Programmazione orientata agli aspetti (AOP)

AOP è una tecnica utile che consente di aggiungere blocchi eseguibili al codice sorgente senza modificarlo esplicitamente. Nel nostro esempio, non vogliamo registrare l'esecuzione del metodo all'interno della classe. Invece, vogliamo che un'altra classe intercetta ogni chiamata al metodo power() , misura il suo tempo di esecuzione e invia queste informazioni a slf4j.

Vogliamo che quell'intercettore capisca il nostro @Loggable annotazione e registra ogni chiamata a quel metodo specifico power() . E, naturalmente, lo stesso intercettore dovrebbe essere utilizzato per altri metodi in cui inseriremo la stessa annotazione in futuro.

Questo caso si adatta perfettamente all'intento originale di AOP:evitare la re-implementazione di alcuni comportamenti comuni in più classi.

La registrazione è una funzionalità supplementare alla nostra funzionalità principale e non vogliamo inquinare il nostro codice con più istruzioni di registrazione. Invece, vogliamo che la registrazione avvenga dietro le quinte.

In termini di AOP, la nostra soluzione può essere spiegata come la creazione di un aspetto che trasversale il codice in determinati punti di unione e applica un consiglio in giro che implementa la funzionalità desiderata.

AspettoJ

Vediamo cosa significano queste parole magiche. Ma prima, vediamo come jcabi-aspects li implementa usando AspectJ (è un esempio semplificato, il codice completo lo trovi in ​​MethodLogger.java ):

@Aspect
public class MethodLogger {
  @Around("execution(* *(..)) && @annotation(Loggable)")
  public Object around(ProceedingJoinPoint point) {
    long start = System.currentTimeMillis();
    Object result = point.proceed();
    Logger.info(
      "#%s(%s): %s in %[msec]s",
      MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
      point.getArgs(),
      result,
      System.currentTimeMillis() - start
    );
    return result;
  }
}

Questo è un aspetto con un unico consiglio in giro around() dentro. L'aspetto è annotato con @Aspect e il consiglio è annotato con @Around . Come discusso in precedenza, queste annotazioni sono solo indicatori in .class File. Non fanno altro che fornire alcune meta-informazioni a coloro che sono interessati al runtime.

Annotazione @Around ha un parametro che, in questo caso, dice che il consiglio dovrebbe essere applicato a un metodo se:

  1. il suo modificatore di visibilità è * (public , protected o private );
  2. il suo nome è nome * (qualsiasi nome);
  3. i suoi argomenti sono .. (qualsiasi argomento); e
  4. è annotato con @Loggable

Quando una chiamata a un metodo annotato deve essere intercettata, metodo around() viene eseguito prima di eseguire il metodo effettivo. Quando una chiamata al metodo power() deve essere intercettato, metodo around() riceve un'istanza della classe ProceedingJoinPoint e deve restituire un oggetto, che verrà utilizzato come risultato del metodo power() .

Per chiamare il metodo originale, power() , il consiglio deve chiamare proceed() del punto di unione oggetto.

Compiliamo questo aspetto e lo rendiamo disponibile in classpath insieme al nostro file principale Foo.class . Fin qui tutto bene, ma dobbiamo fare un ultimo passo per mettere in atto il nostro aspetto:dovremmo applicare il nostro consiglio.

Tessitura di aspetti binari

La tessitura degli aspetti è il nome del processo di applicazione dei consigli. Aspect weaver modifica il codice originale iniettando chiamate agli aspetti. AspectJ fa esattamente questo. Gli diamo due classi Java binarie Foo.class e MethodLogger.class; ne restituisce tre — Foo.class modificato , Foo$AjcClosure1.class e MethodLogger.class non modificato .

Per capire quali consigli dovrebbero essere applicati a quali metodi, AspectJ weaver sta usando le annotazioni da .class File. Inoltre, usa la riflessione per sfogliare tutte le classi nel percorso di classe. Analizza quali metodi soddisfano le condizioni del @Around annotazione. Ovviamente trova il nostro metodo power() .

Quindi, ci sono due passaggi. Per prima cosa, compiliamo il nostro .java file utilizzando javac e ottieni due file. Quindi, AspectJ li intreccia/modifica e crea la propria classe extra. Il nostro Foo la classe assomiglia a questa dopo la tessitura:

public class Foo {
  private final MethodLogger logger;
  @Loggable
  public int power(int x, int p) {
    return this.logger.around(point);
  }
  private int power_aroundBody(int x, int p) {
    return Math.pow(x, p);
  }
}

AspectJ weaver sposta la nostra funzionalità originale in un nuovo metodo, power_aroundBody() e reindirizza tutti i power() chiamate alla classe aspetto MethodLogger .

Invece di un metodo power() nella classe Foo ora abbiamo quattro classi che lavorano insieme. D'ora in poi, questo è ciò che accade dietro le quinte di ogni chiamata a power() :

Funzionalità originale del metodo power() è indicato dalla piccola linea di vita verde sul diagramma.

Come puoi vedere, il processo di tessitura degli aspetti collega tra loro classi e aspetti, trasferendo le chiamate tra di loro tramite punti di unione. Senza tessitura, sia le classi che gli aspetti sono solo binari Java compilati con annotazioni allegate.

jcabi-aspetti

jcabi-aspects è una libreria JAR che contiene Loggable annotazione e MethodLogger aspetto (a proposito, ci sono molti più aspetti e annotazioni). Non è necessario scrivere il proprio aspetto per la registrazione del metodo. Basta aggiungere alcune dipendenze al percorso di classe e configurare jcabi-maven-plugin per la tessitura degli aspetti (ottenere le ultime versioni in Maven Central):

<project>
  <depenencies>
    <dependency>
      <dependency>
        <groupId>com.jcabi</groupId>
        <artifactId>jcabi-aspects</artifactId>
      </dependency>
      <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
      </dependency>
    </dependency>
  </depenencies>
  <build>
    <plugins>
      <plugin>
        <groupId>com.jcabi</groupId>
        <artifactId>jcabi-maven-plugin</artifactId>
        <executions>
          <execution>
            <goals>
              <goal>ajc</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
</project>

Poiché questa procedura di tessitura richiede molto sforzo di configurazione, ho creato un comodo plug-in Maven con un ajc obiettivo, che fa l'intero lavoro di tessitura dell'aspetto. Puoi usare direttamente AspectJ, ma ti consiglio di usare jcabi-maven-plugin.

Questo è tutto. Ora puoi usare @com.jcabi.aspects.Loggable annotazione e i tuoi metodi verranno registrati tramite slf4j.

Se qualcosa non funziona come spiegato, non esitare a inviare un problema con Github.

Post correlati

Potresti anche trovare interessanti questi post:

  • Come riprovare la chiamata del metodo Java in caso di eccezione
  • Memorizza nella cache i risultati del metodo Java
  • Sbarazzarsi di Java Static Logger
  • Limita il tempo di esecuzione del metodo Java
  • Semplice client SSH Java

Etichetta Java