AspectJ integration for Dropwizard Metrics with optional Expression Language 3.0 (JSR-341) support.
Add the metrics-aspectj
library as a dependency:
<dependency>
<groupId>org.stefanutti.metrics.aspectj</groupId>
<artifactId>metrics-aspectj</artifactId>
<version>1.0.0-rc.4</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>org.stefanutti.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.
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>org.stefanutti.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 org.stefanutti.metrics.aspectj.Metrics;
@Metrics
public class TimedMethod {
@Timed(name = "timerName")
public void timedMethod() {}
}
At weaving time, Metrics Aspects will detect the @Metrics
annotation, scan all the declared methods of the target class that are annotated with some Metrics annotations, then create and register the corresponding Metric
instances and finally weave its aspects around these methods, so that at runtime, these Metric
instances get called according to the Metrics annotations specification.
Note that this annotation won't be inherited if it's placed on an interface or a parent class. More details are available in the Limitations section.
Metrics comes with the metrics-annotation
module that contains a set of annotations (@ExceptionMetered
, @Gauge
, @Metered
and @Timed
) 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 org.stefanutti.metrics.aspectj.Metrics;
@Metrics
public class TimedMethod {
@Timed(name = "timerName")
public void timedMethod() {}
}
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 org.stefanutti.metrics.aspectj.Metrics;
@Metrics
public class TimedMethod {
@Timed(name = "timerName")
public static void timedStaticMethod() {}
}
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 org.stefanutti.metrics.aspectj.Metrics;
@Metrics
public class TimedMethod {
private long id;
public long getId() {
return id;
}
@Timed(name = "timerName ${this.id}")
public void timedMethod() {}
}
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 interfaces or parent classes. Indeed, according to the Java language specification, non-type annotations are not inherited. It's 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.Timed;
import org.stefanutti.metrics.aspectj.Metrics;
@Metrics(registry = "registryName")
public class TimedMethodWithRegistryByName {
@Timed(name = "timerName")
public void timedMethod() {}
}
or with an EL expression that evaluates to a bean property of type MetricRegistry
:
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.annotation.Timed;
import org.stefanutti.metrics.aspectj.Metrics;
@Metrics(registry = "${this.registry}")
public class TimedMethodWithRegistryFromProperty {
private final MetricRegistry registry;
public TimedMethodWithRegistryFromProperty(MetricRegistry registry) {
this.registry = registry;
}
public MetricRegistry getRegistry() {
return registry;
}
@Timed(name = "timerName")
public void timedMethod() {}
}
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 on an implemented interface. The root causes of that limitation, according to the Java language specification, are:
- Non-type annotations are not inherited,
- Annotations on types are only inherited if they have the
@Inherited
meta-annotation, - Annotations on interfaces are not inherited irrespective to having the
@Inherited
meta-annotation.
See the @Inherited
Javadoc and Annotation types from the Java language specification for more details.
AspectJ is following 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:
- That would have been working around the Java language specification in the first place,
- Plus that would have required to rely on a combination of Expression-based pointcuts, Runtime type matching and Reflective access to define conditional pointcut expressions which:
- Would have widen the scope of matching joint points thus introducing side-effects in addition to being inefficient,
- Would have been evaluated at runtime for each candidate join point relying on the Java Reflection API thus impacting the application performance and incidentally voiding the non-intrusive benefit of AOP in a larger sense.
Spring AOP and AspectJ provides Aspect Oriented Programming (AOP) in two very different ways:
- AspectJ provides a full-fledged aspect definition and support both Compile Time Weaving (CTW) and Load Time Weaving (LTW) (with a Java agent) and implements AOP with class instrumentation (byte code manipulation),
- Spring AOP does not support the whole AspectJ aspect definition and does not support Compile Time Weaving,
- Spring AOP implements AOP either using (see Spring proxying mechanisms):
- JDK dynamic proxies, which add little runtime overhead, clutter stack traces and can be incompatible with other Spring functionality like Spring JMX (for dynamic MBean export for example),
- Or CGLIB (byte code manipulation), that has to be added as a runtime dependency:
- It dynamically extends classes thus it is incompatible with
final
classes or methods, - CGLIB development isn't active, Hibernate has been deprecating it in favor of Javassist (see Deprecated CGLIB support),
- It dynamically extends classes thus it is incompatible with
- AJDT (AspectJ Development Tools) provides deep integration between AspectJ and the Eclipse platform which is not possible with Spring AOP due to the runtime / dynamic nature of its AOP implementation.
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-2014, Antonin Stefanutti
Published under Apache Software License 2.0, see LICENSE