You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by re...@apache.org on 2020/11/01 15:44:15 UTC

[cxf] 01/02: CXF-8252: Add Micrometer metric support for JAX-RS (Spring Boot) (#716)

This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch 3.3.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf.git

commit e225ae38db06bc5778d22f2de063780073f67875
Author: Andriy Redko <dr...@gmail.com>
AuthorDate: Sun Nov 1 09:12:31 2020 -0500

    CXF-8252: Add Micrometer metric support for JAX-RS (Spring Boot) (#716)
    
    (cherry picked from commit 595cc425ed3fbd729635d59d8e7fccf18334542d)
---
 .../main/release/samples/jax_rs/spring_boot/README |   8 +-
 .../release/samples/jax_rs/spring_boot/pom.xml     |  13 ++
 .../sample/rs/service/SampleRestApplication.java   |  11 +-
 .../src/main/resources/application.properties      |   3 +
 integration/spring-boot/autoconfigure/pom.xml      |   6 +
 .../spring/boot/autoconfigure/CxfProperties.java   |  15 +-
 .../MicrometerMetricsAutoConfiguration.java        |  85 ++++++----
 .../SpringBasedTimedAnnotationProvider.java        |  38 ++++-
 .../MicrometerMetricsAutoConfigurationTest.java    |  91 +++++++++++
 .../micrometer/MicrometerMetricsProperties.java    |  10 +-
 .../micrometer/MicrometerMetricsProvider.java      |  19 ++-
 .../provider/DefaultTimedAnnotationProvider.java   |  32 ++--
 .../metrics/micrometer/provider/StandardTags.java  |   2 +-
 .../jaxrs/JaxrsOperationTagsCustomizer.java        |  43 +++++
 .../micrometer/provider/jaxrs/JaxrsTags.java       |  44 +++++
 .../micrometer/MicrometerMetricsProviderTest.java  |  30 +++-
 .../DefaultExceptionClassProviderTest.java         |   6 +-
 .../DefaultTimedAnnotationProviderTest.java        |   9 +-
 .../provider/StandardTagsProviderTest.java         |   4 +-
 .../micrometer/provider/StandardTagsTest.java      |   7 +-
 .../jaxrs/JaxrsOperationTagsCustomizerTest.java    |  73 +++++++++
 .../micrometer/provider/jaxrs/JaxrsTagsTest.java   |  65 ++++++++
 .../cxf/systest/jaxrs/resources/Library.java       |   6 +
 .../systest/jaxrs/spring/boot/SpringJaxrsTest.java | 181 +++++++++++++++++++++
 .../src/test/resources/application-jaxrs.yml       |   7 +
 .../src/test/resources/application-jaxws.yml       |   5 +-
 26 files changed, 734 insertions(+), 79 deletions(-)

diff --git a/distribution/src/main/release/samples/jax_rs/spring_boot/README b/distribution/src/main/release/samples/jax_rs/spring_boot/README
index 164955b..b9f3d73 100644
--- a/distribution/src/main/release/samples/jax_rs/spring_boot/README
+++ b/distribution/src/main/release/samples/jax_rs/spring_boot/README
@@ -1,7 +1,8 @@
 == Spring Boot - Samples - CXF Rest Web Services 
 
 This sample project demonstrates how to use CXF JAX-RS services
-with Spring Boot. This demo has two JAX-RS class resources being deployed in a single JAX-RS endpoint.  
+with Spring Boot and Spring Actuator. This demo has two JAX-RS class resources being deployed 
+in a single JAX-RS endpoint.  
 
 = Starting the server =
 
@@ -60,6 +61,11 @@ or access it from the CXF Services page:
   http://localhost:8080/services/helloservice/services
   and follow a Swagger link.
 
+To view the exposed metrics:
+  http://localhost:8080/actuator/metrics
+
+The Apache CXF specific metrics are available under:
+  http://localhost:8080/actuator/metrics/cxf.server.requests
 
 ---- From the command line ----
 
diff --git a/distribution/src/main/release/samples/jax_rs/spring_boot/pom.xml b/distribution/src/main/release/samples/jax_rs/spring_boot/pom.xml
index 5895749..03fa264 100644
--- a/distribution/src/main/release/samples/jax_rs/spring_boot/pom.xml
+++ b/distribution/src/main/release/samples/jax_rs/spring_boot/pom.xml
@@ -46,6 +46,19 @@
             <version>${project.version}</version>
         </dependency>
         <dependency>
+            <groupId>org.apache.cxf</groupId>
+            <artifactId>cxf-rt-features-metrics</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-actuator</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
diff --git a/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java b/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java
index 6f1cef3..7ae97b0 100644
--- a/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java
+++ b/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/java/sample/rs/service/SampleRestApplication.java
@@ -25,6 +25,8 @@ import org.apache.cxf.ext.logging.LoggingFeature;
 import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxrs.openapi.OpenApiFeature;
 import org.apache.cxf.jaxrs.swagger.ui.SwaggerUiConfig;
+import org.apache.cxf.metrics.MetricsFeature;
+import org.apache.cxf.metrics.MetricsProvider;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -37,6 +39,8 @@ import sample.rs.service.hello2.HelloServiceImpl2;
 public class SampleRestApplication {
     @Autowired
     private Bus bus;
+    @Autowired
+    private MetricsProvider metricsProvider;
 
     public static void main(String[] args) {
         SpringApplication.run(SampleRestApplication.class, args);
@@ -48,7 +52,7 @@ public class SampleRestApplication {
         endpoint.setBus(bus);
         endpoint.setServiceBeans(Arrays.<Object>asList(new HelloServiceImpl1(), new HelloServiceImpl2()));
         endpoint.setAddress("/");
-        endpoint.setFeatures(Arrays.asList(createOpenApiFeature(), new LoggingFeature()));
+        endpoint.setFeatures(Arrays.asList(createOpenApiFeature(), metricsFeature(), new LoggingFeature()));
         return endpoint.create();
     }
 
@@ -67,4 +71,9 @@ public class SampleRestApplication {
                 .url("/services/helloservice/openapi.json"));
         return openApiFeature;
     }
+    
+    @Bean
+    public MetricsFeature metricsFeature() {
+        return new MetricsFeature(metricsProvider);
+    }
 }
diff --git a/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/resources/application.properties b/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/resources/application.properties
index d9993d1..ff5a5a2 100644
--- a/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/resources/application.properties
+++ b/distribution/src/main/release/samples/jax_rs/spring_boot/src/main/resources/application.properties
@@ -2,3 +2,6 @@ cxf.path=/services/helloservice
 cxf.jaxrs.client.address=http://localhost:8080/services/helloservice
 cxf.jaxrs.client.headers.accept=text/plain
 cxf.jaxrs.client.classes-scan-packages=sample.rs.service.api
+
+management.endpoint.metrics.enabled=true
+management.endpoints.web.exposure.include=*
diff --git a/integration/spring-boot/autoconfigure/pom.xml b/integration/spring-boot/autoconfigure/pom.xml
index 4d6d9d6..2b45f0c 100644
--- a/integration/spring-boot/autoconfigure/pom.xml
+++ b/integration/spring-boot/autoconfigure/pom.xml
@@ -170,5 +170,11 @@
             <version>${cxf.hibernate.validator.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>3.18.0</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java
index 0f53bd8..016851d 100644
--- a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java
+++ b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/CxfProperties.java
@@ -94,8 +94,12 @@ public class CxfProperties {
     }
 
     public static class Metrics {
-
         private final Server server = new Server();
+        
+        /**
+         * Enables or disables metrics instrumentation
+         */
+        private boolean enabled = true;
 
         public Server getServer() {
             return this.server;
@@ -145,6 +149,15 @@ public class CxfProperties {
                 this.maxUriTags = maxUriTags;
             }
         }
+        
+        public boolean isEnabled() {
+            return enabled;
+        }
+
+        public void setEnabled(boolean enabled) {
+            this.enabled = enabled;
+        }
+
     }
 
 }
diff --git a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfiguration.java b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfiguration.java
index 86788a3..89aee36 100644
--- a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfiguration.java
+++ b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfiguration.java
@@ -21,6 +21,7 @@ package org.apache.cxf.spring.boot.autoconfigure.micrometer;
 
 import java.util.List;
 
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
 import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
 import org.apache.cxf.metrics.MetricsProvider;
 import org.apache.cxf.metrics.micrometer.MicrometerMetricsProperties;
@@ -32,6 +33,8 @@ import org.apache.cxf.metrics.micrometer.provider.StandardTagsProvider;
 import org.apache.cxf.metrics.micrometer.provider.TagsCustomizer;
 import org.apache.cxf.metrics.micrometer.provider.TagsProvider;
 import org.apache.cxf.metrics.micrometer.provider.TimedAnnotationProvider;
+import org.apache.cxf.metrics.micrometer.provider.jaxrs.JaxrsOperationTagsCustomizer;
+import org.apache.cxf.metrics.micrometer.provider.jaxrs.JaxrsTags;
 import org.apache.cxf.metrics.micrometer.provider.jaxws.JaxwsFaultCodeProvider;
 import org.apache.cxf.metrics.micrometer.provider.jaxws.JaxwsFaultCodeTagsCustomizer;
 import org.apache.cxf.metrics.micrometer.provider.jaxws.JaxwsOperationTagsCustomizer;
@@ -46,8 +49,8 @@ import org.springframework.boot.autoconfigure.AutoConfigureAfter;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.core.annotation.Order;
@@ -58,11 +61,10 @@ import io.micrometer.core.instrument.config.MeterFilter;
 @Configuration
 @AutoConfigureAfter({MetricsAutoConfiguration.class, SimpleMetricsExportAutoConfiguration.class})
 @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
-@ConditionalOnClass({JaxWsServerFactoryBean.class, MetricsProvider.class})
-@ConditionalOnBean({MeterRegistry.class})
-@EnableConfigurationProperties(CxfProperties.class)
+@ConditionalOnClass(MetricsProvider.class)
+@ConditionalOnProperty(name = "cxf.metrics.enabled", matchIfMissing = true)
+@ConditionalOnBean(MeterRegistry.class)
 public class MicrometerMetricsAutoConfiguration {
-
     private final CxfProperties properties;
 
     public MicrometerMetricsAutoConfiguration(CxfProperties properties) {
@@ -75,36 +77,12 @@ public class MicrometerMetricsAutoConfiguration {
     }
 
     @Bean
-    public JaxwsTags jaxwsTags() {
-        return new JaxwsTags();
-    }
-
-    @Bean
     @ConditionalOnMissingBean(ExceptionClassProvider.class)
     public ExceptionClassProvider exceptionClassProvider() {
         return new DefaultExceptionClassProvider();
     }
 
     @Bean
-    @ConditionalOnMissingBean(JaxwsFaultCodeProvider.class)
-    public JaxwsFaultCodeProvider jaxwsFaultCodeProvider() {
-        return new JaxwsFaultCodeProvider();
-    }
-
-    @Bean
-    @ConditionalOnMissingBean(JaxwsFaultCodeTagsCustomizer.class)
-    public JaxwsFaultCodeTagsCustomizer jaxwsFaultCodeTagsCustomizer(JaxwsTags jaxwsTags,
-                                                                     JaxwsFaultCodeProvider jaxwsFaultCodeProvider) {
-        return new JaxwsFaultCodeTagsCustomizer(jaxwsTags, jaxwsFaultCodeProvider);
-    }
-
-    @Bean
-    @ConditionalOnMissingBean(JaxwsOperationTagsCustomizer.class)
-    public JaxwsOperationTagsCustomizer jaxwsOperationTagsCustomizer(JaxwsTags jaxwsTags) {
-        return new JaxwsOperationTagsCustomizer(jaxwsTags);
-    }
-
-    @Bean
     @ConditionalOnMissingBean(StandardTags.class)
     public StandardTags standardTags() {
         return new StandardTags();
@@ -126,7 +104,7 @@ public class MicrometerMetricsAutoConfiguration {
 
         Server server = this.properties.getMetrics().getServer();
         micrometerMetricsProperties.setAutoTimeRequests(server.isAutoTimeRequests());
-        micrometerMetricsProperties.setRequestsMetricName(server.getRequestsMetricName());
+        micrometerMetricsProperties.setServerRequestsMetricName(server.getRequestsMetricName());
 
         return new MicrometerMetricsProvider(registry, tagsProvider, tagsCustomizers, timedAnnotationProvider,
                 micrometerMetricsProperties);
@@ -141,4 +119,51 @@ public class MicrometerMetricsAutoConfiguration {
         return MeterFilter.maximumAllowableTags(
                 metricName, "uri", this.properties.getMetrics().getServer().getMaxUriTags(), filter);
     }
+    
+    @Configuration
+    @ConditionalOnClass(JaxWsServerFactoryBean.class)
+    @ConditionalOnProperty(name = "cxf.metrics.jaxws.enabled", matchIfMissing = true)
+    protected static class JaxWsMetricsConfiguration {
+        @Bean
+        @ConditionalOnMissingBean(JaxwsFaultCodeProvider.class)
+        public JaxwsFaultCodeProvider jaxwsFaultCodeProvider() {
+            return new JaxwsFaultCodeProvider();
+        }
+
+        @Bean
+        @ConditionalOnMissingBean(JaxwsFaultCodeTagsCustomizer.class)
+        public JaxwsFaultCodeTagsCustomizer jaxwsFaultCodeTagsCustomizer(JaxwsTags jaxwsTags,
+                JaxwsFaultCodeProvider jaxwsFaultCodeProvider) {
+            return new JaxwsFaultCodeTagsCustomizer(jaxwsTags, jaxwsFaultCodeProvider);
+        }
+
+        @Bean
+        @ConditionalOnMissingBean(JaxwsOperationTagsCustomizer.class)
+        public JaxwsOperationTagsCustomizer jaxwsOperationTagsCustomizer(JaxwsTags jaxwsTags) {
+            return new JaxwsOperationTagsCustomizer(jaxwsTags);
+        }
+        
+        @Bean
+        @ConditionalOnMissingBean(JaxwsTags.class)
+        public JaxwsTags jaxwsTags() {
+            return new JaxwsTags();
+        }
+    }
+    
+    @Configuration
+    @ConditionalOnClass(JAXRSServerFactoryBean.class)
+    @ConditionalOnProperty(name = "cxf.metrics.jaxrs.enabled", matchIfMissing = true)
+    protected static class JaxRsMetricsConfiguration {
+        @Bean
+        @ConditionalOnMissingBean(JaxrsTags.class)
+        public JaxrsTags jaxrsTags() {
+            return new JaxrsTags();
+        }
+        
+        @Bean
+        @ConditionalOnMissingBean(JaxrsOperationTagsCustomizer.class)
+        public JaxrsOperationTagsCustomizer jaxrsOperationTagsCustomizer(JaxrsTags jaxrsTags) {
+            return new JaxrsOperationTagsCustomizer(jaxrsTags);
+        }
+    }
 }
diff --git a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java
index eada8d8..557a78c 100644
--- a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java
+++ b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java
@@ -25,6 +25,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.cxf.jaxrs.model.OperationResourceInfo;
 import org.apache.cxf.message.Exchange;
 import org.apache.cxf.metrics.micrometer.provider.TimedAnnotationProvider;
 import org.apache.cxf.service.Service;
@@ -42,7 +43,10 @@ public class SpringBasedTimedAnnotationProvider implements TimedAnnotationProvid
 
     @Override
     public Set<Timed> getTimedAnnotations(Exchange ex) {
-        HandlerMethod handlerMethod = new HandlerMethod(ex);
+        HandlerMethod handlerMethod = HandlerMethod.create(ex);
+        if (handlerMethod == null) {
+            return emptySet();
+        }
 
         final Set<Timed> exists = timedAnnotationCache.get(handlerMethod);
         if (exists != null) {
@@ -66,19 +70,35 @@ public class SpringBasedTimedAnnotationProvider implements TimedAnnotationProvid
     }
 
     private static final class HandlerMethod {
-        private final Class beanType;
+        private final Class<?> beanType;
         private final Method method;
 
-        private HandlerMethod(Exchange exchange) {
-            Service service = exchange.getService();
-            BindingOperationInfo bop = exchange.getBindingOperationInfo();
-            MethodDispatcher md = (MethodDispatcher) service.get(MethodDispatcher.class.getName());
+        private HandlerMethod(Class<?> beanType, Method method) {
+            this.beanType = beanType;
+            this.method = method;
+        }
 
-            this.method = md.getMethod(bop);
-            this.beanType = method.getDeclaringClass();
+        private static HandlerMethod create(Exchange exchange) {
+            final Service service = exchange.getService();
+            if (service != null) {
+                final BindingOperationInfo bop = exchange.getBindingOperationInfo();
+                if (bop != null) { /* JAX-WS call */
+                    final MethodDispatcher md = (MethodDispatcher) service.get(MethodDispatcher.class.getName());
+                    final Method method = md.getMethod(bop);
+                    return new HandlerMethod(method.getDeclaringClass(), method);
+                } else { /* JAX-RS call */
+                    final OperationResourceInfo ori = exchange.get(OperationResourceInfo.class);
+                    if (ori != null) {
+                        return new HandlerMethod(ori.getClassResourceInfo().getResourceClass(), 
+                            ori.getAnnotatedMethod());
+                    }
+                }
+            }
+            
+            return null;
         }
 
-        private Class getBeanType() {
+        private Class<?> getBeanType() {
             return beanType;
         }
 
diff --git a/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfigurationTest.java b/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfigurationTest.java
new file mode 100644
index 0000000..f43ff9d
--- /dev/null
+++ b/integration/spring-boot/autoconfigure/src/test/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/MicrometerMetricsAutoConfigurationTest.java
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.spring.boot.autoconfigure.micrometer;
+
+import org.apache.cxf.metrics.MetricsProvider;
+import org.apache.cxf.metrics.micrometer.MicrometerMetricsProvider;
+import org.apache.cxf.metrics.micrometer.provider.jaxrs.JaxrsTags;
+import org.apache.cxf.metrics.micrometer.provider.jaxws.JaxwsTags;
+import org.apache.cxf.spring.boot.autoconfigure.CxfProperties;
+import org.springframework.beans.factory.NoSuchBeanDefinitionException;
+import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
+import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertThrows;
+
+public class MicrometerMetricsAutoConfigurationTest {
+    private final WebApplicationContextRunner runner = new WebApplicationContextRunner()
+            .withBean(CxfProperties.class)
+            .withConfiguration(AutoConfigurations.of(
+                MetricsAutoConfiguration.class,
+                SimpleMetricsExportAutoConfiguration.class,
+                MicrometerMetricsAutoConfiguration.class
+            ));
+    
+    @Test
+    public void metricsProviderShouldBeAvailable() {
+        runner
+            .run(ctx -> {
+                assertThat(ctx.getBean(MetricsProvider.class), instanceOf(MicrometerMetricsProvider.class));
+                assertThat(ctx.getBean(JaxrsTags.class), not(nullValue()));
+                assertThat(ctx.getBean(JaxwsTags.class), not(nullValue()));
+            });
+    }
+    
+    @Test
+    public void metricsProviderShouldNotBeAvailable() {
+        runner
+            .withPropertyValues("cxf.metrics.enabled=false")
+            .run(ctx -> {
+                assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(MetricsProvider.class));
+                assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(JaxrsTags.class));
+                assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(JaxwsTags.class));
+            });
+    }
+    
+    @Test
+    public void jaxrsMetricsShouldNotBeAvailable() {
+        runner
+            .withPropertyValues("cxf.metrics.jaxrs.enabled=false")
+            .run(ctx -> {
+                assertThat(ctx.getBean(MetricsProvider.class), instanceOf(MicrometerMetricsProvider.class));
+                assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(JaxrsTags.class));
+                assertThat(ctx.getBean(JaxwsTags.class), not(nullValue()));
+            });
+    }
+
+    @Test
+    public void jaxwsMetricsShouldNotBeAvailable() {
+        runner
+            .withPropertyValues("cxf.metrics.jaxws.enabled=false")
+            .run(ctx -> {
+                assertThat(ctx.getBean(MetricsProvider.class), instanceOf(MicrometerMetricsProvider.class));
+                assertThat(ctx.getBean(JaxrsTags.class), not(nullValue()));
+                assertThrows(NoSuchBeanDefinitionException.class, () -> ctx.getBean(JaxwsTags.class));
+            });
+    }
+}
diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProperties.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProperties.java
index 8144067..6573392 100644
--- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProperties.java
+++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProperties.java
@@ -31,7 +31,7 @@ public class MicrometerMetricsProperties {
     /**
      * Name of the metric for received requests.
      */
-    private String requestsMetricName = "cxf.server.requests";
+    private String serverRequestsMetricName = "cxf.server.requests";
 
     public boolean isAutoTimeRequests() {
         return autoTimeRequests;
@@ -41,11 +41,11 @@ public class MicrometerMetricsProperties {
         this.autoTimeRequests = autoTimeRequests;
     }
 
-    public String getRequestsMetricName() {
-        return requestsMetricName;
+    public String getServerRequestsMetricName() {
+        return serverRequestsMetricName;
     }
 
-    public void setRequestsMetricName(String requestsMetricName) {
-        this.requestsMetricName = requestsMetricName;
+    public void setServerRequestsMetricName(String requestsMetricName) {
+        this.serverRequestsMetricName = requestsMetricName;
     }
 }
diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProvider.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProvider.java
index 3cd4dd4..d4c0601 100644
--- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProvider.java
+++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProvider.java
@@ -60,7 +60,7 @@ public class MicrometerMetricsProvider implements MetricsProvider {
      * {@inheritDoc}
      */
     @Override
-    public MetricsContext createEndpointContext(Endpoint endpoint, boolean isClient, String clientId) {
+    public MetricsContext createEndpointContext(Endpoint endpoint, boolean asClient, String clientId) {
         return null;
     }
 
@@ -70,8 +70,14 @@ public class MicrometerMetricsProvider implements MetricsProvider {
     @Override
     public MetricsContext createOperationContext(Endpoint endpoint, BindingOperationInfo boi, boolean asClient,
                                                  String clientId) {
+        // Client metrics are not yet supported
+        if (asClient) {
+            return null;
+        }
+        
         return new MicrometerMetricsContext(registry, tagsProvider, timedAnnotationProvider, tagsCustomizers,
-                micrometerMetricsProperties.getRequestsMetricName(), micrometerMetricsProperties.isAutoTimeRequests());
+            micrometerMetricsProperties.getServerRequestsMetricName(), 
+            micrometerMetricsProperties.isAutoTimeRequests());
     }
 
 
@@ -81,6 +87,13 @@ public class MicrometerMetricsProvider implements MetricsProvider {
     @Override
     public MetricsContext createResourceContext(Endpoint endpoint, String resourceName, boolean asClient,
                                                 String clientId) {
-        return null;
+        // Client metrics are not yet supported
+        if (asClient) {
+            return null;
+        }
+        
+        return new MicrometerMetricsContext(registry, tagsProvider, timedAnnotationProvider, tagsCustomizers,
+            micrometerMetricsProperties.getServerRequestsMetricName(), 
+            micrometerMetricsProperties.isAutoTimeRequests());
     }
 }
diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java
index d9be969..5b018d0 100644
--- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java
+++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java
@@ -31,9 +31,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import org.apache.cxf.message.Exchange;
-import org.apache.cxf.service.Service;
-import org.apache.cxf.service.invoker.MethodDispatcher;
-import org.apache.cxf.service.model.BindingOperationInfo;
+import org.apache.cxf.message.MessageUtils;
 
 import io.micrometer.core.annotation.Timed;
 import io.micrometer.core.annotation.TimedSet;
@@ -46,8 +44,11 @@ public class DefaultTimedAnnotationProvider implements TimedAnnotationProvider {
 
     @Override
     public Set<Timed> getTimedAnnotations(Exchange ex) {
-        HandlerMethod handlerMethod = new HandlerMethod(ex);
-
+        final HandlerMethod handlerMethod = HandlerMethod.create(ex);
+        if (handlerMethod == null) {
+            return emptySet();
+        }
+        
         final Set<Timed> exists = timedAnnotationCache.get(handlerMethod);
         if (exists != null) {
             return exists;
@@ -94,19 +95,22 @@ public class DefaultTimedAnnotationProvider implements TimedAnnotationProvider {
     }
 
     private static final class HandlerMethod {
-        private final Class beanType;
+        private final Class<?> beanType;
         private final Method method;
 
-        private HandlerMethod(Exchange exchange) {
-            Service service = exchange.getService();
-            BindingOperationInfo bop = exchange.getBindingOperationInfo();
-            MethodDispatcher md = (MethodDispatcher) service.get(MethodDispatcher.class.getName());
-
-            this.method = md.getMethod(bop);
-            this.beanType = method.getDeclaringClass();
+        private HandlerMethod(Class<?> beanType, Method method) {
+            this.method = method;
+            this.beanType = beanType;
+        }
+        
+        private static HandlerMethod create(Exchange exchange) {
+            return MessageUtils
+                .getTargetMethod(exchange.getInMessage())
+                .map(method -> new HandlerMethod(method.getDeclaringClass(), method))
+                .orElse(null);
         }
 
-        private Class getBeanType() {
+        private Class<?> getBeanType() {
             return beanType;
         }
 
diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/StandardTags.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/StandardTags.java
index 86897f0..9b63ecf 100644
--- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/StandardTags.java
+++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/StandardTags.java
@@ -75,7 +75,7 @@ public class StandardTags {
 
     public Tag uri(Message request) {
         return ofNullable(request)
-                .map(e -> e.get(Message.BASE_PATH))
+                .map(e -> e.get(Message.REQUEST_URI))
                 .filter(e -> e instanceof String)
                 .map(e -> (String) e)
                 .map(e -> Tag.of("uri", e))
diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsOperationTagsCustomizer.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsOperationTagsCustomizer.java
new file mode 100644
index 0000000..0a90b8a
--- /dev/null
+++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsOperationTagsCustomizer.java
@@ -0,0 +1,43 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.metrics.micrometer.provider.jaxrs;
+
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.metrics.micrometer.provider.TagsCustomizer;
+
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+
+import static java.util.Optional.ofNullable;
+
+public class JaxrsOperationTagsCustomizer implements TagsCustomizer {
+    private final JaxrsTags jaxrsTags;
+
+    public JaxrsOperationTagsCustomizer(JaxrsTags jaxrsTags) {
+        this.jaxrsTags = jaxrsTags;
+    }
+
+    @Override
+    public Iterable<Tag> getAdditionalTags(Exchange ex) {
+        Message request = ofNullable(ex.getInMessage()).orElseGet(ex::getInFaultMessage);
+        return Tags.of(jaxrsTags.operation(request));
+    }
+}
diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsTags.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsTags.java
new file mode 100644
index 0000000..d966445
--- /dev/null
+++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsTags.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.metrics.micrometer.provider.jaxrs;
+
+import java.lang.reflect.Method;
+
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+
+import io.micrometer.core.instrument.Tag;
+
+import static java.util.Optional.ofNullable;
+
+public class JaxrsTags {
+
+    private static final String UNKNOWN = "UNKNOWN";
+
+    private static final Tag OPERATION_UNKNOWN = Tag.of("operation", UNKNOWN);
+    
+    public Tag operation(Message request) {
+        return ofNullable(request)
+            .flatMap(MessageUtils::getTargetMethod)
+            .map(Method::getName)
+            .map(operation -> Tag.of("operation", operation))
+            .orElse(OPERATION_UNKNOWN);
+    }
+}
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProviderTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProviderTest.java
index 7d2c41a..a79232a 100644
--- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProviderTest.java
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsProviderTest.java
@@ -65,7 +65,7 @@ public class MicrometerMetricsProviderTest {
         initMocks(this);
 
         micrometerMetricsProperties = new MicrometerMetricsProperties();
-        micrometerMetricsProperties.setRequestsMetricName("http.server.requests");
+        micrometerMetricsProperties.setServerRequestsMetricName("http.server.requests");
         micrometerMetricsProperties.setAutoTimeRequests(true);
 
         underTest =
@@ -86,11 +86,35 @@ public class MicrometerMetricsProviderTest {
     }
 
     @Test
-    public void testCreateOperationContext() throws NoSuchFieldException, IllegalAccessException {
+    public void testCreateServerOperationContext() throws NoSuchFieldException, IllegalAccessException {
+        // when
+        MetricsContext actual = underTest.createOperationContext(endpoint, boi, false, "clientId");
+
+        // then
+        assertThat(actual, instanceOf(MicrometerMetricsContext.class));
+        assertThat(getFieldValue(actual, "registry"), is(registry));
+        assertThat(getFieldValue(actual, "tagsProvider"), is(tagsProvider));
+        assertThat(getFieldValue(actual, "timedAnnotationProvider"), is(timedAnnotationProvider));
+        assertThat(getFieldValue(actual, "metricName"), is("http.server.requests"));
+        assertThat(getFieldValue(actual, "autoTimeRequests"), is(true));
+        assertThat(getFieldValue(actual, "tagsCustomizers"), is(Collections.singletonList(tagsCustomizer)));
+    }
+    
+    @Test
+    public void testCreateClientOperationContext() throws NoSuchFieldException, IllegalAccessException {
         // when
         MetricsContext actual = underTest.createOperationContext(endpoint, boi, true, "clientId");
 
         // then
+        assertThat(actual, is(nullValue()));
+    }
+    
+    @Test
+    public void testCreateServerResourceContext() throws NoSuchFieldException, IllegalAccessException {
+        // when
+        MetricsContext actual = underTest.createResourceContext(endpoint, "resourceName", false, "clientId");
+
+        // then
         assertThat(actual, instanceOf(MicrometerMetricsContext.class));
         assertThat(getFieldValue(actual, "registry"), is(registry));
         assertThat(getFieldValue(actual, "tagsProvider"), is(tagsProvider));
@@ -101,7 +125,7 @@ public class MicrometerMetricsProviderTest {
     }
 
     @Test
-    public void testCreateResourceContext() {
+    public void testCreateClientResourceContext() {
         // when
         MetricsContext actual = underTest.createResourceContext(endpoint, "resourceName", true, "clientId");
 
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultExceptionClassProviderTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultExceptionClassProviderTest.java
index f8feb7d..54b7543 100644
--- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultExceptionClassProviderTest.java
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultExceptionClassProviderTest.java
@@ -32,7 +32,7 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.MockitoAnnotations.initMocks;
+import static org.mockito.MockitoAnnotations.openMocks;
 
 public class DefaultExceptionClassProviderTest {
 
@@ -50,7 +50,7 @@ public class DefaultExceptionClassProviderTest {
 
     @Before
     public void setUp() {
-        initMocks(this);
+        openMocks(this);
 
         underTest = new DefaultExceptionClassProvider();
     }
@@ -58,7 +58,7 @@ public class DefaultExceptionClassProviderTest {
     @Test
     public void testGetExceptionClassReturnCauseExceptionFromExchange() {
         // given
-        Class expected = CauseException.class;
+        Class<?> expected = CauseException.class;
 
         doReturn(FAULT_EXCEPTION).when(exchange).get(Exception.class);
 
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProviderTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProviderTest.java
index b29e281..0229112 100644
--- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProviderTest.java
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProviderTest.java
@@ -24,6 +24,7 @@ import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.Message;
 import org.apache.cxf.service.Service;
 import org.apache.cxf.service.invoker.MethodDispatcher;
 import org.apache.cxf.service.model.BindingOperationInfo;
@@ -39,7 +40,7 @@ import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.empty;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.MockitoAnnotations.initMocks;
+import static org.mockito.MockitoAnnotations.openMocks;
 
 @SuppressWarnings({"unused"})
 public class DefaultTimedAnnotationProviderTest {
@@ -54,15 +55,19 @@ public class DefaultTimedAnnotationProviderTest {
     private BindingOperationInfo bindingOperationInfo;
     @Mock
     private MethodDispatcher methodDispatcher;
+    @Mock
+    private Message message;
 
     @Before
     public void setUp() {
-        initMocks(this);
+        openMocks(this);
         underTest = new DefaultTimedAnnotationProvider();
 
         doReturn(service).when(exchange).getService();
         doReturn(bindingOperationInfo).when(exchange).getBindingOperationInfo();
         doReturn(methodDispatcher).when(service).get(MethodDispatcher.class.getName());
+        doReturn(message).when(exchange).getInMessage();
+        doReturn(exchange).when(message).getExchange();
     }
 
     @Test
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsProviderTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsProviderTest.java
index 2ed8dce..a6e5529 100644
--- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsProviderTest.java
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsProviderTest.java
@@ -33,7 +33,7 @@ import org.mockito.Mock;
 import static org.hamcrest.CoreMatchers.equalTo;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.MockitoAnnotations.initMocks;
+import static org.mockito.MockitoAnnotations.openMocks;
 
 public class StandardTagsProviderTest {
 
@@ -53,7 +53,7 @@ public class StandardTagsProviderTest {
 
     @Before
     public void setUp() {
-        initMocks(this);
+        openMocks(this);
 
         underTest = new StandardTagsProvider(exceptionClassProvider, standardTags);
 
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsTest.java
index ae8a4c2..92a13b3 100644
--- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsTest.java
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/StandardTagsTest.java
@@ -34,7 +34,7 @@ import org.mockito.Mock;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.MockitoAnnotations.initMocks;
+import static org.mockito.MockitoAnnotations.openMocks;
 
 
 public class StandardTagsTest {
@@ -65,7 +65,7 @@ public class StandardTagsTest {
 
     @Before
     public void setUp() {
-        initMocks(this);
+        openMocks(this);
 
         doReturn(exchange).when(request).getExchange();
         doReturn(bindingOperationInfo).when(exchange).getBindingOperationInfo();
@@ -263,7 +263,7 @@ public class StandardTagsTest {
     @Test
     public void testUriReturnWithCorrectValue() {
         // given
-        doReturn(DUMMY_URI).when(request).get(Message.BASE_PATH);
+        doReturn(DUMMY_URI).when(request).get(Message.REQUEST_URI);
 
         // when
         Tag actual = underTest.uri(request);
@@ -298,6 +298,7 @@ public class StandardTagsTest {
     public void testExceptionReturnWithNameWhenExceptionIsAnonymous() {
         // given
         Exception exception = new Exception() {
+            private static final long serialVersionUID = 1L;
         };
 
         // when
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsOperationTagsCustomizerTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsOperationTagsCustomizerTest.java
new file mode 100644
index 0000000..ff4fcef
--- /dev/null
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsOperationTagsCustomizerTest.java
@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.metrics.micrometer.provider.jaxrs;
+
+import org.apache.cxf.message.Exchange;
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.apache.cxf.metrics.micrometer.provider.TagsCustomizer;
+
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.Tags;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class JaxrsOperationTagsCustomizerTest {
+    private static final String OPERATION_METRIC_NAME = "operation";
+    private static final String DUMMY_OPERATOR = "getOperator";
+
+    private Message message;
+    private TagsCustomizer tagsCustomizer;
+    private Exchange exchange;
+
+    @Before
+    public void setUp() {
+        exchange = new ExchangeImpl();
+        
+        message = new MessageImpl();
+        message.setExchange(exchange);
+        
+        exchange.setInMessage(message);
+        tagsCustomizer = new JaxrsOperationTagsCustomizer(new JaxrsTags());
+    }
+    
+    @Test
+    public void testOperationReturnWithUnknownWhenRequestIsNull() {
+        final Iterable<Tag> actual = tagsCustomizer.getAdditionalTags(exchange);
+        assertThat(actual, equalTo(Tags.of(Tag.of(OPERATION_METRIC_NAME, "UNKNOWN"))));
+    }
+
+    @Test
+    public void testOperationReturnWithCorrectValue() throws NoSuchMethodException, SecurityException {
+        message.put("org.apache.cxf.resource.method", getClass().getDeclaredMethod("getOperator"));
+        final Iterable<Tag> actual = tagsCustomizer.getAdditionalTags(exchange);
+        assertThat(actual, equalTo(Tags.of(Tag.of(OPERATION_METRIC_NAME, DUMMY_OPERATOR))));
+    }
+    
+    @SuppressWarnings("unused")
+    private void getOperator() {
+        /* operation method */
+    }
+}
\ No newline at end of file
diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsTagsTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsTagsTest.java
new file mode 100644
index 0000000..4706966
--- /dev/null
+++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/provider/jaxrs/JaxrsTagsTest.java
@@ -0,0 +1,65 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.metrics.micrometer.provider.jaxrs;
+
+import org.apache.cxf.message.ExchangeImpl;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+
+import io.micrometer.core.instrument.Tag;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class JaxrsTagsTest {
+    private static final String OPERATION_METRIC_NAME = "operation";
+    private static final String DUMMY_OPERATOR = "getOperator";
+
+    private JaxrsTags tags;
+    private Message message;
+
+    @Before
+    public void setUp() throws NoSuchMethodException, SecurityException {
+        message = new MessageImpl();
+        message.setExchange(new ExchangeImpl());
+        message.put("org.apache.cxf.resource.method", getClass().getDeclaredMethod("getOperator"));
+        tags = new JaxrsTags();
+    }
+    
+    @Test
+    public void testOperationReturnWithUnknownWhenRequestIsNull() {
+        final Tag actual = tags.operation(null);
+        assertThat(actual, is(Tag.of(OPERATION_METRIC_NAME, "UNKNOWN")));
+    }
+
+    @Test
+    public void testOperationReturnWithCorrectValue() {
+        final Tag actual = tags.operation(message);
+        assertThat(actual, is(Tag.of(OPERATION_METRIC_NAME, DUMMY_OPERATOR)));
+    }
+    
+    @SuppressWarnings("unused")
+    private void getOperator() {
+        /* operation method */
+    }
+}
diff --git a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/resources/Library.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/resources/Library.java
index 43fa6da..c540eb3 100644
--- a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/resources/Library.java
+++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/resources/Library.java
@@ -23,6 +23,7 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.TreeMap;
 
+import javax.ws.rs.DELETE;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -62,4 +63,9 @@ public class Library {
             ? Response.ok().entity(books.get(id)).build() 
                 : Response.status(Status.NOT_FOUND).build();
     }
+    
+    @DELETE
+    public void deleteBooks() {
+        throw new UnsupportedOperationException("Operation is not supported by the server");
+    }
 }
diff --git a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java
new file mode 100644
index 0000000..7771653
--- /dev/null
+++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java
@@ -0,0 +1,181 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.cxf.systest.jaxrs.spring.boot;
+
+import java.util.Map;
+
+import javax.ws.rs.InternalServerErrorException;
+import javax.ws.rs.NotFoundException;
+import javax.ws.rs.client.ClientBuilder;
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider;
+
+import org.apache.cxf.bus.spring.SpringBus;
+import org.apache.cxf.feature.Feature;
+import org.apache.cxf.metrics.MetricsFeature;
+import org.apache.cxf.metrics.MetricsProvider;
+import org.apache.cxf.systest.jaxrs.resources.Book;
+import org.apache.cxf.systest.jaxrs.resources.Library;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
+import org.springframework.boot.test.system.OutputCaptureRule;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tag;
+import io.micrometer.core.instrument.search.RequiredSearch;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static java.util.stream.Collectors.toMap;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, classes = SpringJaxrsTest.TestConfig.class)
+@ActiveProfiles("jaxrs")
+public class SpringJaxrsTest {
+    @Rule
+    public OutputCaptureRule output = new OutputCaptureRule();
+
+    @Autowired
+    private MeterRegistry registry;
+    
+    @LocalServerPort
+    private int port;
+
+    @Configuration
+    @EnableAutoConfiguration
+    @ComponentScan(basePackageClasses = Library.class)
+    static class TestConfig {
+        @Bean
+        public SpringBus cxf() {
+            final SpringBus bus = new SpringBus();
+            // Bye default, the exception are propagated and out fault interceptors are not called 
+            bus.setProperty("org.apache.cxf.propagate.exception", Boolean.FALSE);
+            return bus;
+        }
+        
+        @Bean
+        public Feature metricsFeature(MetricsProvider metricsProvider) {
+            return new MetricsFeature(metricsProvider);
+        }
+        
+        @Bean
+        public JacksonJsonProvider jacksonJsonProvider() {
+            return new JacksonJsonProvider();
+        }
+    }
+    
+    @Before
+    public void setUp() {
+        this.registry.getMeters().forEach(meter -> registry.remove(meter));
+    }
+
+    @Test
+    public void testJaxrsSuccessMetric() {
+        final WebTarget target = createWebTarget();
+        
+        try (Response r = target.request().get()) {
+            assertThat(r.getStatus()).isEqualTo(200);
+        }
+        
+        RequiredSearch requestMetrics = registry.get("cxf.server.requests");
+
+        Map<Object, Object> tags = requestMetrics.timer().getId().getTags().stream()
+                .collect(toMap(Tag::getKey, Tag::getValue));
+
+        assertThat(tags)
+            .containsOnly(
+                    entry("exception", "None"),
+                    entry("method", "GET"),
+                    entry("operation", "getBooks"),
+                    entry("uri", "/api/library"),
+                    entry("outcome", "SUCCESS"),
+                    entry("status", "200"));
+    }
+    
+    @Test
+    public void testJaxrsFailedMetric() {
+        final WebTarget target = createWebTarget();
+        
+        assertThatThrownBy(() -> target.path("100").request().get(Book.class))
+            .isInstanceOf(NotFoundException.class)
+            .hasMessageContaining("Not Found");
+
+        RequiredSearch requestMetrics = registry.get("cxf.server.requests");
+
+        Map<Object, Object> tags = requestMetrics.timer().getId().getTags().stream()
+                .collect(toMap(Tag::getKey, Tag::getValue));
+
+        assertThat(tags)
+                .containsOnly(
+                        entry("exception", "None"),
+                        entry("method", "GET"),
+                        entry("operation", "getBook"),
+                        entry("uri", "/api/library/100"),
+                        entry("outcome", "CLIENT_ERROR"),
+                        entry("status", "404"));
+    }
+    
+    @Test
+    public void testJaxrsExceptionMetric() {
+        final WebTarget target = createWebTarget();
+        
+        assertThatThrownBy(() -> target.request().delete(String.class))
+            .isInstanceOf(InternalServerErrorException.class)
+            .hasMessageContaining("Internal Server Error");
+
+        RequiredSearch requestMetrics = registry.get("cxf.server.requests");
+
+        Map<Object, Object> tags = requestMetrics.timer().getId().getTags().stream()
+                .collect(toMap(Tag::getKey, Tag::getValue));
+
+        assertThat(tags)
+                .containsOnly(
+                        entry("exception", "UnsupportedOperationException"),
+                        entry("method", "DELETE"),
+                        entry("operation", "deleteBooks"),
+                        entry("uri", "/api/library"),
+                        entry("outcome", "SERVER_ERROR"),
+                        entry("status", "500"));
+    }
+    
+    private WebTarget createWebTarget() {
+        return ClientBuilder
+            .newClient()
+            .register(JacksonJsonProvider.class)
+            .target("http://localhost:" + port + "/api/library");
+    }
+
+}
diff --git a/systests/spring-boot/src/test/resources/application-jaxrs.yml b/systests/spring-boot/src/test/resources/application-jaxrs.yml
new file mode 100644
index 0000000..4049aaf
--- /dev/null
+++ b/systests/spring-boot/src/test/resources/application-jaxrs.yml
@@ -0,0 +1,7 @@
+cxf:
+  path: /api
+  jaxrs:
+    component-scan: true
+  metrics:
+    jaxws:
+      enabled: false
\ No newline at end of file
diff --git a/systests/spring-boot/src/test/resources/application-jaxws.yml b/systests/spring-boot/src/test/resources/application-jaxws.yml
index 4fec840..49c747e 100644
--- a/systests/spring-boot/src/test/resources/application-jaxws.yml
+++ b/systests/spring-boot/src/test/resources/application-jaxws.yml
@@ -1,2 +1,5 @@
 cxf:
-  path: /Service
\ No newline at end of file
+  path: /Service
+  metrics:
+    jaxrs:
+      enabled: false
\ No newline at end of file