AspectJ integration for Dropwizard Metrics with optional Expression Language 3.0 (JSR-341) support.
Metrics AspectJ provides support for the Metrics annotations in Java SE environments using AspectJ to perform AOP instrumentation. It implements the contract specified by these annotations with the following level of functionality:
@ExceptionMetered
, @Metered
and @Timed
,Gauge
metrics for instance and class methods annotated with @Gauge
,Metric
instances in the resolved MetricRegistry
instance,MetricRegistry
instance by looking up into the SharedMetricRegistries
class or optionally by dynamically evaluating EL expressions.Metrics AspectJ is compatible with Metrics version 3.0.0
+ and requires Java 6 or higher.
Add the metrics-aspectj
library as a dependency:
<dependency>
<groupId>io.astefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj</artifactId>
<version>1.2.0</version>
</dependency>
And configure the maven-aspectj-plugin
to compile-time weave (CTW) the metrics-aspectj
aspects into your project:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<configuration>
<aspectLibraries>
<aspectLibrary>
<groupId>io.astefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
More information can be found in the Maven AspectJ plugin documentation.
Use the AjcTask (iajc
) Ant task:
<target name="{target}" >
<iajc sourceroots="${basedir}/src"
classpath="${basedir}/lib/aspectjrt.jar"
outjar="${basedir}/build/${ant.project.name}.jar">
...
<aspectpath>
<pathelement location="${basedir}/lib/metrics-aspectj.jar"/>
</aspectpath>
...
</iajc>
</target>
Other options are detailed in the AspectJ Ant tasks documentation.
A working gradle example is available, but each integration point is described here.
buildscript {
// ensure the gradle-aspectj integration is w/i the build classpath
dependencies {
classpath 'nl.eveoh:gradle-aspectj:1.6'
}
}
// specify the aspectjVersion, used by gradle-aspectj
project.ext {
aspectjVersion = '1.8.10'
}
// specify the Dropwizard Metrics version (metricsVer)
// and the aspect-oriented metrics version (metricsAspectVer, this solution)
ext {
metricsVer = '3.2.2'
metricsAspectVer = '1.2.0'
}
// via the gradle-aspectj integration, run "aspect weaving"
apply plugin: 'aspectj'
// ensure Dropwizard Metrics as well as the aspect-oriented metrics (astefanutti.metrics.aspectj)
// runtime dependencies of your solution are satisfied.
dependencies {
compile "io.astefanutti.metrics.aspectj:metrics-aspectj:${metricsAspectVer}"
// add a path for the gradle-aspectj "aspect weaving" (AspectJ Compiler compile)
aspectpath "io.astefanutti.metrics.aspectj:metrics-aspectj:${metricsAspectVer}"
compile "io.dropwizard.metrics:metrics-core:${metricsVer}"
compile "io.dropwizard.metrics:metrics-annotation:${metricsVer}"
}
The AspectJ compiler can be used directly by executing the following command:
ajc -aspectpath metrics-aspectj.jar [Options] [file...]
More information can be found in the AspectJ compiler / weaver documentation.
Besides depending on Metrics (metrics-core
and metrics-annotation
modules), Metrics AspectJ requires the AspectJ aspectjrt
module:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
These three modules are transitive dependencies of the metrics-aspectj
Maven module.
Alternatively, the metrics-aspectj-deps
artifact that re-packages the metrics-annotation
and the aspectjrt
modules can be used so that the only required dependency is metrics-core
:
<dependency>
<groupId>io.astefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj-deps</artifactId>
</dependency>
In addition to that, Metrics AspectJ optional support of EL 3.0 expression for MetricRegistry
resolution and Metric
name evaluation requires an implementation of Expression Language 3.0 (JSR-341) to be present at runtime. For example, the metrics-aspectj-el
module is using the GlassFish reference implementation as test
dependency for its unit tests execution:
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</dependency>
In order to activate Metrics AspectJ for a particular class, it must be annotated with the @Metrics
annotation:
import com.codahale.metrics.annotation.Timed;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics
class TimedMethod {
@Timed(name = "timerName")
void timedMethod() {} // Timer name => TimedMethod.timerName
}
At weaving time, Metrics AspectJ will detect the @Metrics
annotation, scan all the declared methods of the target class that are annotated with Metrics annotations, then create and register the corresponding Metric
instances and weave its aspects around these methods. At runtime, these Metric
instances will eventually get called according to the Metrics annotations specification.
Note that Metrics annotations won't be inherited if declared on an interface or a parent class method. More details are available in the Limitations section.
Metrics comes with the metrics-annotation
module that contains a set of annotations and provides a standard way to integrate Metrics with frameworks supporting Aspect Oriented Programming (AOP). These annotations are supported by Metrics AspectJ that implements their contract as documented in their Javadoc.
For example, a method can be annotated with the @Timed
annotation so that its execution can be monitored using Metrics:
import com.codahale.metrics.annotation.Timed;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics
class TimedMethod {
@Timed(name = "timerName")
void timedMethod() {} // Timer name => TimedMethod.timerName
}
In that example, Metrics AspectJ will instrument all the constructors of the TimedMethod
class by injecting Java bytecode that will automatically create a Timer
instance with the provided name
(or retrieve an existing Timer
with the same name
already registered in the MetricRegistry
) right after the instantiation of the TimedMethod
class and inline the method invocation around with the needed code to time the method execution using that Timer
instance.
A static
method can also be annotated with the @Timed
annotation so that its execution can be monitored using Metrics:
import com.codahale.metrics.annotation.Timed;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics
class TimedMethod {
@Timed(name = "timerName")
static void timedStaticMethod() {} // Timer name => TimedMethod.timerName
}
In that example, Metrics AspectJ will instrument the TimedMethod
class so that, when it's loaded, a Timer
instance with the provided name
will be created (or an existing Timer
with the same name
already registered in the MetricRegistry
will be retrieved) and inline the method invocation around with the needed code to time the method execution using that Timer
instance.
Optionally, the Metric
name can be resolved with an EL expression that evaluates to a String
:
import com.codahale.metrics.annotation.Timed;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics
class TimedMethod {
private long id;
public long getId() {
return id;
}
@Timed(name = "timerName ${this.id}")
void timedMethod() {} // Timer name => TimedMethod.timerName <id>
}
In that example, Metrics AspectJ will automatically create a Timer
instance (respectively retrieve an existing Timer
instance with the same name
already registered in the MetricRegistry
) right after the instantiation of the TimedMethod
class and evaluate the EL expression based on the value of the id
attribute of that newly created TimedMethod
instance to name the Timer
instance (respectively resolve the Timer
instance registered in the MetricRegistry
). If the value of the id
attribute changes over time, the name
of the Timer
instance won't be re-evaluated.
Note that these annotations won't be inherited if they are placed on interface or parent class methods. Indeed, according to the Java language specification, non-type annotations are not inherited. It is discussed in more details in the Limitations section.
The Metrics.registry
annotation attribute provides the way to declare the MetricRegistry
to register the generated Metric
instances into. Its value can either be a string literal that identifies a MetricRegistry
accessible by name from the SharedMetricRegistries
class or a valid EL expression that evaluates to the registry name or the registry instance. The resultant MetricRegistry
is used to register the Metric
instantiated into each time a Metrics annotation is present on that class methods. It defaults to the string literal metrics-registry
.
The MetricRegistry
can thus be resolved by name relying on the SharedMetricRegistries.getOrCreate(String name)
method:
import com.codahale.metrics.annotation.Metered;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics(registry = "registryName")
class MeteredMethodWithRegistryByName {
@Metered(name = "meterName")
void meteredMethod() {} // Registry => SharedMetricRegistries.getOrCreate("registryName")
}
Or with an EL expression that evaluates to a bean property of type MetricRegistry
:
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.annotation.Metered;
import io.astefanutti.metrics.aspectj.Metrics;
@Metrics(registry = "${this.registry}")
class MeteredMethodWithRegistryFromProperty {
final MetricRegistry registry;
MeteredMethodWithRegistryFromProperty(MetricRegistry registry) {
this.registry = registry;
}
MetricRegistry getRegistry() {
return registry;
}
@Metered(name = "meterName")
void meteredMethod() {} // Registry => this.getRegistry()
}
Or with an EL expression that evaluates to a String
. In that case the registry is resolved by name using the SharedMetricRegistries.getOrCreate(String name)
method.
The Metrics annotations are not inherited whether these are declared on a parent class or an implemented interface method. The root causes of that limitation, according to the Java language specification, are:
@Inherited
meta-annotation,@Inherited
meta-annotation.See the @Inherited
Javadoc and Annotation types from the Java language specification for more details.
AspectJ follows the Java language specification and has documented to what extent it's impacted in Annotation inheritance and Annotation inheritance and pointcut matching. There would have been ways of working around that though:
Spring AOP and AspectJ provides Aspect Oriented Programming (AOP) in two very different ways:
final
classes or methods,Further details can be found in Choosing which AOP declaration style to use from the Spring framework documentation. The Spring AOP vs AspectJ question on Stack Overflow provides some insights as well.
Copyright © 2013-2016, Antonin Stefanutti
Published under Apache Software License 2.0, see LICENSE