diff --git a/src/main/java/com/ryantenney/metrics/annotation/InjectMetric.java b/src/main/java/com/ryantenney/metrics/annotation/InjectMetric.java index 995b0466..fe8eee9b 100755 --- a/src/main/java/com/ryantenney/metrics/annotation/InjectMetric.java +++ b/src/main/java/com/ryantenney/metrics/annotation/InjectMetric.java @@ -21,6 +21,8 @@ import java.lang.annotation.Target; /** + * Deprecated, use @Metric instead. + * * An annotation requesting that a metric be injected *
* Given a field like this: @@ -32,6 +34,7 @@ * A meter for the defining class with the name {@code someTimer} will be created. It will be up to the user * to mark the meter. This annotation can be used on fields of type Meter, Timer, Counter, and Histogram. */ +@Deprecated @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface InjectMetric { diff --git a/src/main/java/com/ryantenney/metrics/annotation/Metric.java b/src/main/java/com/ryantenney/metrics/annotation/Metric.java new file mode 100755 index 00000000..4b90afaf --- /dev/null +++ b/src/main/java/com/ryantenney/metrics/annotation/Metric.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ryantenney.metrics.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * An annotation requesting that a metric be injected + * + * Given a field like this: + *
+ * \@Metric
+ * public Meter someTimer;
+ *
+ *
+ * A meter for the defining class with the name {@code someTimer} will be created. It will be up to the user
+ * to mark the meter. This annotation can be used on fields of type Meter, Timer, Counter, and Histogram.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Metric {
+
+ /**
+ * The name of the metric.
+ */
+ String name() default "";
+
+ /**
+ * If {@code true}, use the given name an as absolute name. If {@code false}, use the given name
+ * relative to the annotated class.
+ */
+ boolean absolute() default false;
+
+}
diff --git a/src/main/java/com/ryantenney/metrics/spring/InjectMetricAnnotationBeanPostProcessor.java b/src/main/java/com/ryantenney/metrics/spring/InjectMetricAnnotationBeanPostProcessor.java
index 5ceb55a9..a5d0fb28 100755
--- a/src/main/java/com/ryantenney/metrics/spring/InjectMetricAnnotationBeanPostProcessor.java
+++ b/src/main/java/com/ryantenney/metrics/spring/InjectMetricAnnotationBeanPostProcessor.java
@@ -33,6 +33,7 @@
import com.codahale.metrics.Timer;
import com.ryantenney.metrics.annotation.InjectMetric;
+@SuppressWarnings("deprecation")
class InjectMetricAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered {
private static final Logger LOG = LoggerFactory.getLogger(InjectMetricAnnotationBeanPostProcessor.class);
diff --git a/src/main/java/com/ryantenney/metrics/spring/MetricAnnotationBeanPostProcessor.java b/src/main/java/com/ryantenney/metrics/spring/MetricAnnotationBeanPostProcessor.java
new file mode 100755
index 00000000..f8477ad9
--- /dev/null
+++ b/src/main/java/com/ryantenney/metrics/spring/MetricAnnotationBeanPostProcessor.java
@@ -0,0 +1,132 @@
+/**
+ * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ryantenney.metrics.spring;
+
+import java.lang.reflect.Field;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.aop.support.AopUtils;
+import org.springframework.beans.factory.config.BeanPostProcessor;
+import org.springframework.core.Ordered;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.ReflectionUtils.FieldCallback;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.ryantenney.metrics.annotation.Metric;
+
+class MetricAnnotationBeanPostProcessor implements BeanPostProcessor, Ordered {
+
+ private static final Logger LOG = LoggerFactory.getLogger(MetricAnnotationBeanPostProcessor.class);
+
+ private static final AnnotationFilter FILTER = new AnnotationFilter(Metric.class);
+
+ private final MetricRegistry metrics;
+
+ public MetricAnnotationBeanPostProcessor(final MetricRegistry metrics) {
+ this.metrics = metrics;
+ }
+
+ @Override
+ public Object postProcessBeforeInitialization(final Object bean, String beanName) {
+ final Class> targetClass = AopUtils.getTargetClass(bean);
+
+ ReflectionUtils.doWithFields(targetClass, new FieldCallback() {
+ @Override
+ public void doWith(Field field) throws IllegalAccessException {
+ final Metric annotation = field.getAnnotation(Metric.class);
+ final String metricName = Util.forMetricField(targetClass, field, annotation);
+
+ final Class> type = field.getType();
+ if (!com.codahale.metrics.Metric.class.isAssignableFrom(type)) {
+ throw new IllegalArgumentException("Field " + targetClass.getCanonicalName() + "."
+ + field.getName() + " must be a subtype of " + com.codahale.metrics.Metric.class.getCanonicalName());
+ }
+
+ ReflectionUtils.makeAccessible(field);
+
+ // Get the value of the field annotated with @Metric
+ com.codahale.metrics.Metric metric = (com.codahale.metrics.Metric) ReflectionUtils.getField(field, bean);
+
+ if (metric == null) {
+ // If null, create a metric of the appropriate type and inject it
+ metric = getMetric(metrics, type, metricName);
+ ReflectionUtils.setField(field, bean, metric);
+ LOG.debug("Injected metric {} for field {}.{}", metricName, targetClass.getCanonicalName(), field.getName());
+ }
+ else {
+ // If non-null, register that instance of the metric
+ try {
+ // Attempt to register that instance of the metric
+ metrics.register(metricName, metric);
+ LOG.debug("Registered metric {} for field {}.{}", metricName, targetClass.getCanonicalName(), field.getName());
+ }
+ catch (IllegalArgumentException ex1) {
+ // A metric is already registered under that name
+ // (Cannot determine the cause without parsing the Exception's message)
+ try {
+ metric = getMetric(metrics, type, metricName);
+ ReflectionUtils.setField(field, bean, metric);
+ LOG.debug("Injected metric {} for field {}.{}", metricName, targetClass.getCanonicalName(), field.getName());
+ }
+ catch (IllegalArgumentException ex2) {
+ // A metric of a different type is already registered under that name
+ throw new IllegalArgumentException("Error injecting metric for field "
+ + targetClass.getCanonicalName() + "." + field.getName(), ex2);
+ }
+ }
+ }
+ }
+ }, FILTER);
+
+ return bean;
+ }
+
+ private com.codahale.metrics.Metric getMetric(MetricRegistry metricRegistry, Class> type, String metricName) {
+ com.codahale.metrics.Metric metric;
+ if (Meter.class == type) {
+ metric = metrics.meter(metricName);
+ }
+ else if (Timer.class == type) {
+ metric = metrics.timer(metricName);
+ }
+ else if (Counter.class == type) {
+ metric = metrics.counter(metricName);
+ }
+ else if (Histogram.class == type) {
+ metric = metrics.histogram(metricName);
+ }
+ else {
+ throw new IllegalArgumentException("Invalid @Metric type " + type.getCanonicalName());
+ }
+ return metric;
+ }
+
+ @Override
+ public Object postProcessAfterInitialization(Object bean, String beanName) {
+ return bean;
+ }
+
+ @Override
+ public int getOrder() {
+ return LOWEST_PRECEDENCE - 2;
+ }
+
+}
diff --git a/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java b/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java
index 8a61064d..55bb13f0 100644
--- a/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java
+++ b/src/main/java/com/ryantenney/metrics/spring/MetricsBeanPostProcessorFactory.java
@@ -53,6 +53,11 @@ public static CachedGaugeAnnotationBeanPostProcessor cachedGauge(final MetricReg
return new CachedGaugeAnnotationBeanPostProcessor(metricRegistry);
}
+ public static MetricAnnotationBeanPostProcessor metric(final MetricRegistry metricRegistry) {
+ return new MetricAnnotationBeanPostProcessor(metricRegistry);
+ }
+
+ @Deprecated
public static InjectMetricAnnotationBeanPostProcessor injectMetric(final MetricRegistry metricRegistry) {
return new InjectMetricAnnotationBeanPostProcessor(metricRegistry);
}
diff --git a/src/main/java/com/ryantenney/metrics/spring/Util.java b/src/main/java/com/ryantenney/metrics/spring/Util.java
index f243612b..630b73ba 100755
--- a/src/main/java/com/ryantenney/metrics/spring/Util.java
+++ b/src/main/java/com/ryantenney/metrics/spring/Util.java
@@ -26,7 +26,9 @@
import com.ryantenney.metrics.annotation.CachedGauge;
import com.ryantenney.metrics.annotation.Counted;
import com.ryantenney.metrics.annotation.InjectMetric;
+import com.ryantenney.metrics.annotation.Metric;
+@SuppressWarnings("deprecation")
class Util {
private Util() {
@@ -60,6 +62,10 @@ static String forInjectMetricField(Class> klass, Member member, InjectMetric a
return chooseName(annotation.name(), annotation.absolute(), klass, member);
}
+ static String forMetricField(Class> klass, Member member, Metric annotation) {
+ return chooseName(annotation.name(), annotation.absolute(), klass, member);
+ }
+
static String chooseName(String explicitName, boolean absolute, Class> klass, Member member, String... suffixes) {
if (explicitName != null && !explicitName.isEmpty()) {
if (absolute) {
diff --git a/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java b/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java
index acd8bea6..dda6c00f 100755
--- a/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java
+++ b/src/main/java/com/ryantenney/metrics/spring/config/AnnotationDrivenBeanDefinitionParser.java
@@ -97,6 +97,11 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
.setFactoryMethod("cachedGauge")
.addConstructorArgReference(metricsBeanName));
+ registerComponent(parserContext,
+ build(MetricsBeanPostProcessorFactory.class, source, ROLE_INFRASTRUCTURE)
+ .setFactoryMethod("metric")
+ .addConstructorArgReference(metricsBeanName));
+
registerComponent(parserContext,
build(MetricsBeanPostProcessorFactory.class, source, ROLE_INFRASTRUCTURE)
.setFactoryMethod("injectMetric")
diff --git a/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java b/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java
index ea7b27ed..13006505 100755
--- a/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java
+++ b/src/main/java/com/ryantenney/metrics/spring/config/annotation/MetricsConfigurationSupport.java
@@ -98,6 +98,13 @@ public BeanPostProcessor cachedGaugeAnnotationBeanPostProcessor() {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ public BeanPostProcessor metricAnnotationBeanPostProcessor() {
+ return MetricsBeanPostProcessorFactory.metric(getMetricRegistry());
+ }
+
+ @Bean
+ @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+ @SuppressWarnings("deprecation")
public BeanPostProcessor injectMetricAnnotationBeanPostProcessor() {
return MetricsBeanPostProcessorFactory.injectMetric(getMetricRegistry());
}
diff --git a/src/test/java/com/ryantenney/metrics/spring/InjectMetricTest.java b/src/test/java/com/ryantenney/metrics/spring/InjectMetricTest.java
index 435afc8b..facf71d1 100755
--- a/src/test/java/com/ryantenney/metrics/spring/InjectMetricTest.java
+++ b/src/test/java/com/ryantenney/metrics/spring/InjectMetricTest.java
@@ -34,6 +34,7 @@
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:injected-metrics.xml")
+@SuppressWarnings("deprecation")
public class InjectMetricTest {
@Autowired
diff --git a/src/test/java/com/ryantenney/metrics/spring/MetricAnnotationTest.java b/src/test/java/com/ryantenney/metrics/spring/MetricAnnotationTest.java
new file mode 100755
index 00000000..8e3dba50
--- /dev/null
+++ b/src/test/java/com/ryantenney/metrics/spring/MetricAnnotationTest.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright (C) 2012 Ryan W Tenney (ryan@10e.us)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.ryantenney.metrics.spring;
+
+import static com.ryantenney.metrics.spring.TestUtil.forMetricField;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import com.codahale.metrics.UniformReservoir;
+import com.ryantenney.metrics.annotation.Metric;
+
+@RunWith(SpringJUnit4ClassRunner.class)
+@ContextConfiguration("classpath:metric-annotation.xml")
+public class MetricAnnotationTest {
+
+ @Autowired
+ @Qualifier("target1")
+ MetricAnnotationTest.Target target;
+
+ @Autowired
+ @Qualifier("target2")
+ MetricAnnotationTest.Target target2;
+
+ @Autowired
+ MetricRegistry metricRegistry;
+
+ @Test
+ public void targetIsNotNull() throws Exception {
+ assertNotNull(target);
+ assertNotNull(target2);
+
+ assertNotNull(target.theNameForTheMeter);
+ assertNotNull(target2.theNameForTheMeter);
+ Meter meter = (Meter) forMetricField(metricRegistry, MetricAnnotationTest.Target.class, "theNameForTheMeter");
+ assertSame(target.theNameForTheMeter, meter);
+ assertSame(target2.theNameForTheMeter, meter);
+
+ assertNotNull(target.timer);
+ assertNotNull(target2.timer);
+ Timer timer = (Timer) forMetricField(metricRegistry, MetricAnnotationTest.Target.class, "timer");
+ assertSame(target.timer, timer);
+ assertSame(target2.timer, timer);
+
+ assertNotNull(target.counter);
+ assertNotNull(target2.counter);
+ Counter ctr = (Counter) forMetricField(metricRegistry, MetricAnnotationTest.Target.class, "counter");
+ assertSame(target.counter, ctr);
+ assertSame(target2.counter, ctr);
+
+ assertNotNull(target.histogram);
+ assertNotNull(target2.histogram);
+ Histogram hist = (Histogram) forMetricField(metricRegistry, MetricAnnotationTest.Target.class, "histogram");
+ assertSame(target.histogram, hist);
+ assertSame(target2.histogram, hist);
+
+ assertNotNull(target.uniformHistogram);
+ assertNotNull(target2.uniformHistogram);
+ Histogram uniHist = (Histogram) forMetricField(metricRegistry, MetricAnnotationTest.Target.class, "uniformHistogram");
+ assertSame(target.uniformHistogram, uniHist);
+ assertSame(target2.uniformHistogram, uniHist);
+ }
+
+ public static class Target {
+
+ @Metric
+ public Meter theNameForTheMeter;
+
+ @Metric(name = "theNameForTheTimer")
+ private Timer timer;
+
+ @Metric(name = "group.type.counter", absolute = true)
+ Counter counter;
+
+ @Metric
+ Histogram histogram;
+
+ @Metric
+ Histogram uniformHistogram = new Histogram(new UniformReservoir());
+
+ }
+
+}
diff --git a/src/test/java/com/ryantenney/metrics/spring/TestSuite.java b/src/test/java/com/ryantenney/metrics/spring/TestSuite.java
index 5bd56259..b77a5777 100644
--- a/src/test/java/com/ryantenney/metrics/spring/TestSuite.java
+++ b/src/test/java/com/ryantenney/metrics/spring/TestSuite.java
@@ -33,6 +33,7 @@
MeteredClassImpementsInterfaceTest.class,
MeteredClassTest.class,
MeteredInterfaceTest.class,
+ MetricAnnotationTest.class,
ProxyTargetClassTest.class,
RegistryTest.class,
ReporterTest.class,
diff --git a/src/test/java/com/ryantenney/metrics/spring/TestUtil.java b/src/test/java/com/ryantenney/metrics/spring/TestUtil.java
index d9ff2f17..756a9c1b 100755
--- a/src/test/java/com/ryantenney/metrics/spring/TestUtil.java
+++ b/src/test/java/com/ryantenney/metrics/spring/TestUtil.java
@@ -29,7 +29,6 @@
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
-import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.annotation.ExceptionMetered;
@@ -37,7 +36,9 @@
import com.codahale.metrics.annotation.Timed;
import com.ryantenney.metrics.annotation.Counted;
import com.ryantenney.metrics.annotation.InjectMetric;
+import com.ryantenney.metrics.annotation.Metric;
+@SuppressWarnings("deprecation")
class TestUtil {
private static final Logger log = LoggerFactory.getLogger(TestUtil.class);
@@ -66,6 +67,10 @@ static String forCountedMethod(Class> klass, Member member, Counted annotation
return Util.forCountedMethod(klass, member, annotation);
}
+ static String forMetricField(Class> klass, Member member, Metric annotation) {
+ return Util.forMetricField(klass, member, annotation);
+ }
+
static String forInjectMetricField(Class> klass, Member member, InjectMetric annotation) {
return Util.forInjectMetricField(klass, member, annotation);
}
@@ -119,11 +124,21 @@ static Counter forCountedMethod(MetricRegistry metricRegistry, Class> clazz, S
return metricRegistry.getCounters().get(metricName);
}
- static Metric forInjectMetricField(MetricRegistry metricRegistry, Class> clazz, String fieldName) {
+ @Deprecated
+ static com.codahale.metrics.Metric forInjectMetricField(MetricRegistry metricRegistry, Class> clazz, String fieldName) {
Field field = findField(clazz, fieldName);
String metricName = forInjectMetricField(clazz, field, field.getAnnotation(InjectMetric.class));
+ return getMetric(metricRegistry, field.getType(), metricName);
+ }
+
+ static com.codahale.metrics.Metric forMetricField(MetricRegistry metricRegistry, Class> clazz, String fieldName) {
+ Field field = findField(clazz, fieldName);
+ String metricName = forMetricField(clazz, field, field.getAnnotation(Metric.class));
+ return getMetric(metricRegistry, field.getType(), metricName);
+ }
+
+ private static com.codahale.metrics.Metric getMetric(MetricRegistry metricRegistry, Class> type, String metricName) {
log.info("Looking up injected metric field named '{}'", metricName);
- Class> type = field.getType();
if (type.isAssignableFrom(Meter.class)) {
return metricRegistry.getMeters().get(metricName);
}
diff --git a/src/test/resources/metric-annotation.xml b/src/test/resources/metric-annotation.xml
new file mode 100755
index 00000000..970d16eb
--- /dev/null
+++ b/src/test/resources/metric-annotation.xml
@@ -0,0 +1,34 @@
+
+
+