Skip to content

Commit 39e8942

Browse files
authored
Fix java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerMapping in Spring Boot Servlet mode without WebMVC (#3336)
* Fix missing HandlerMapping class * changelog * rename config * provide default transaction name provider for servlet mode that returns URL
1 parent adebce6 commit 39e8942

File tree

9 files changed

+123
-16
lines changed

9 files changed

+123
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
### Fixes
1111

1212
- Add rate limit to Metrics ([#3334](https://github.com/getsentry/sentry-java/pull/3334))
13+
- Fix java.lang.ClassNotFoundException: org.springframework.web.servlet.HandlerMapping in Spring Boot Servlet mode without WebMVC ([#3336](https://github.com/getsentry/sentry-java/pull/3336))
1314

1415
## 7.7.0
1516

sentry-spring-boot-jakarta/src/main/java/io/sentry/spring/boot/jakarta/SentryAutoConfiguration.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.sentry.spring.jakarta.tracing.SentryTracingFilter;
3434
import io.sentry.spring.jakarta.tracing.SentryTransactionPointcutConfiguration;
3535
import io.sentry.spring.jakarta.tracing.SpringMvcTransactionNameProvider;
36+
import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider;
3637
import io.sentry.spring.jakarta.tracing.TransactionNameProvider;
3738
import io.sentry.transport.ITransportGate;
3839
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
@@ -49,6 +50,7 @@
4950
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
5051
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
5152
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
53+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
5254
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
5355
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
5456
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
@@ -267,12 +269,6 @@ static class SentrySecurityConfiguration {
267269
return new SentryRequestResolver(hub);
268270
}
269271

270-
@Bean
271-
@ConditionalOnMissingBean(TransactionNameProvider.class)
272-
public @NotNull TransactionNameProvider transactionNameProvider() {
273-
return new SpringMvcTransactionNameProvider();
274-
}
275-
276272
@Bean
277273
@ConditionalOnMissingBean(name = "sentrySpringFilter")
278274
public @NotNull FilterRegistrationBean<SentrySpringFilter> sentrySpringFilter(
@@ -296,11 +292,10 @@ public FilterRegistrationBean<SentryTracingFilter> sentryTracingFilter(
296292
return filter;
297293
}
298294

299-
/** Wraps exception resolver @Bean because the return type is loaded too early otherwise */
300295
@Configuration(proxyBeanMethods = false)
301296
@ConditionalOnClass(HandlerExceptionResolver.class)
302297
@Open
303-
static class SentryExceptionResolverConfigurationWrapper {
298+
static class SentryMvcModeConfig {
304299

305300
@Bean
306301
@ConditionalOnMissingBean
@@ -311,6 +306,24 @@ static class SentryExceptionResolverConfigurationWrapper {
311306
return new SentryExceptionResolver(
312307
sentryHub, transactionNameProvider, options.getExceptionResolverOrder());
313308
}
309+
310+
@Bean
311+
@ConditionalOnMissingBean(TransactionNameProvider.class)
312+
public @NotNull TransactionNameProvider transactionNameProvider() {
313+
return new SpringMvcTransactionNameProvider();
314+
}
315+
}
316+
317+
@Configuration(proxyBeanMethods = false)
318+
@ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver")
319+
@Open
320+
static class SentryServletModeConfig {
321+
322+
@Bean
323+
@ConditionalOnMissingBean(TransactionNameProvider.class)
324+
public @NotNull TransactionNameProvider transactionNameProvider() {
325+
return new SpringServletTransactionNameProvider();
326+
}
314327
}
315328
}
316329

sentry-spring-boot-jakarta/src/test/kotlin/io/sentry/spring/boot/jakarta/SentryAutoConfigurationTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import io.sentry.spring.jakarta.SentryUserFilter
2626
import io.sentry.spring.jakarta.SentryUserProvider
2727
import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider
2828
import io.sentry.spring.jakarta.tracing.SentryTracingFilter
29+
import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider
30+
import io.sentry.spring.jakarta.tracing.TransactionNameProvider
2931
import io.sentry.transport.ITransport
3032
import io.sentry.transport.ITransportGate
3133
import io.sentry.transport.apache.ApacheHttpClientTransportFactory
@@ -445,6 +447,15 @@ class SentryAutoConfigurationTest {
445447
}
446448
}
447449

450+
@Test
451+
fun `when Spring MVC is not on the classpath, fallback TransactionNameProvider is configured`() {
452+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true")
453+
.withClassLoader(FilteredClassLoader(HandlerExceptionResolver::class.java))
454+
.run {
455+
assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf(SpringServletTransactionNameProvider::class.java)
456+
}
457+
}
458+
448459
@Test
449460
fun `when tracing is enabled, creates tracing filter`() {
450461
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.traces-sample-rate=1.0")

sentry-spring-boot/src/main/java/io/sentry/spring/boot/SentryAutoConfiguration.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.sentry.spring.tracing.SentryTracingFilter;
3434
import io.sentry.spring.tracing.SentryTransactionPointcutConfiguration;
3535
import io.sentry.spring.tracing.SpringMvcTransactionNameProvider;
36+
import io.sentry.spring.tracing.SpringServletTransactionNameProvider;
3637
import io.sentry.spring.tracing.TransactionNameProvider;
3738
import io.sentry.transport.ITransportGate;
3839
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
@@ -49,6 +50,7 @@
4950
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
5051
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
5152
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
53+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
5254
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
5355
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
5456
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
@@ -265,12 +267,6 @@ static class SentrySecurityConfiguration {
265267
return new SentryRequestResolver(hub);
266268
}
267269

268-
@Bean
269-
@ConditionalOnMissingBean(TransactionNameProvider.class)
270-
public @NotNull TransactionNameProvider transactionNameProvider() {
271-
return new SpringMvcTransactionNameProvider();
272-
}
273-
274270
@Bean
275271
@ConditionalOnMissingBean(name = "sentrySpringFilter")
276272
public @NotNull FilterRegistrationBean<SentrySpringFilter> sentrySpringFilter(
@@ -294,11 +290,10 @@ public FilterRegistrationBean<SentryTracingFilter> sentryTracingFilter(
294290
return filter;
295291
}
296292

297-
/** Wraps exception resolver @Bean because the return type is loaded too early otherwise */
298293
@Configuration(proxyBeanMethods = false)
299294
@ConditionalOnClass(HandlerExceptionResolver.class)
300295
@Open
301-
static class SentryExceptionResolverConfigurationWrapper {
296+
static class SentryMvcModeConfig {
302297

303298
@Bean
304299
@ConditionalOnMissingBean
@@ -309,6 +304,24 @@ static class SentryExceptionResolverConfigurationWrapper {
309304
return new SentryExceptionResolver(
310305
sentryHub, transactionNameProvider, options.getExceptionResolverOrder());
311306
}
307+
308+
@Bean
309+
@ConditionalOnMissingBean(TransactionNameProvider.class)
310+
public @NotNull TransactionNameProvider transactionNameProvider() {
311+
return new SpringMvcTransactionNameProvider();
312+
}
313+
}
314+
315+
@Configuration(proxyBeanMethods = false)
316+
@ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver")
317+
@Open
318+
static class SentryServletModeConfig {
319+
320+
@Bean
321+
@ConditionalOnMissingBean(TransactionNameProvider.class)
322+
public @NotNull TransactionNameProvider transactionNameProvider() {
323+
return new SpringServletTransactionNameProvider();
324+
}
312325
}
313326
}
314327

sentry-spring-boot/src/test/kotlin/io/sentry/spring/boot/SentryAutoConfigurationTest.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import io.sentry.spring.SentryUserFilter
2626
import io.sentry.spring.SentryUserProvider
2727
import io.sentry.spring.SpringSecuritySentryUserProvider
2828
import io.sentry.spring.tracing.SentryTracingFilter
29+
import io.sentry.spring.tracing.SpringServletTransactionNameProvider
30+
import io.sentry.spring.tracing.TransactionNameProvider
2931
import io.sentry.transport.ITransport
3032
import io.sentry.transport.ITransportGate
3133
import io.sentry.transport.apache.ApacheHttpClientTransportFactory
@@ -444,6 +446,17 @@ class SentryAutoConfigurationTest {
444446
}
445447
}
446448

449+
@Test
450+
fun `when Spring MVC is not on the classpath, fallback TransactionNameProvider is configured`() {
451+
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true")
452+
.withClassLoader(FilteredClassLoader(HandlerExceptionResolver::class.java))
453+
.run {
454+
assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf(
455+
SpringServletTransactionNameProvider::class.java
456+
)
457+
}
458+
}
459+
447460
@Test
448461
fun `when tracing is enabled, creates tracing filter`() {
449462
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.traces-sample-rate=1.0")

sentry-spring-jakarta/api/sentry-spring-jakarta.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,12 @@ public final class io/sentry/spring/jakarta/tracing/SpringMvcTransactionNameProv
261261
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
262262
}
263263

264+
public final class io/sentry/spring/jakarta/tracing/SpringServletTransactionNameProvider : io/sentry/spring/jakarta/tracing/TransactionNameProvider {
265+
public fun <init> ()V
266+
public fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
267+
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
268+
}
269+
264270
public abstract interface class io/sentry/spring/jakarta/tracing/TransactionNameProvider {
265271
public abstract fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
266272
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.sentry.spring.jakarta.tracing;
2+
3+
import io.sentry.protocol.TransactionNameSource;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
import org.jetbrains.annotations.ApiStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
/** Fallback TransactionNameProvider when Spring is used in servlet mode (without MVC). */
10+
@ApiStatus.Internal
11+
public final class SpringServletTransactionNameProvider implements TransactionNameProvider {
12+
@Override
13+
public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) {
14+
return request.getMethod() + " " + request.getRequestURI();
15+
}
16+
17+
@Override
18+
@ApiStatus.Internal
19+
public @NotNull TransactionNameSource provideTransactionSource() {
20+
return TransactionNameSource.URL;
21+
}
22+
}

sentry-spring/api/sentry-spring.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,12 @@ public final class io/sentry/spring/tracing/SpringMvcTransactionNameProvider : i
260260
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
261261
}
262262

263+
public final class io/sentry/spring/tracing/SpringServletTransactionNameProvider : io/sentry/spring/tracing/TransactionNameProvider {
264+
public fun <init> ()V
265+
public fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
266+
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
267+
}
268+
263269
public abstract interface class io/sentry/spring/tracing/TransactionNameProvider {
264270
public abstract fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
265271
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.sentry.spring.tracing;
2+
3+
import io.sentry.protocol.TransactionNameSource;
4+
import javax.servlet.http.HttpServletRequest;
5+
import org.jetbrains.annotations.ApiStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
/** Fallback TransactionNameProvider when Spring is used in servlet mode (without MVC). */
10+
@ApiStatus.Internal
11+
public final class SpringServletTransactionNameProvider implements TransactionNameProvider {
12+
@Override
13+
public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) {
14+
return request.getMethod() + " " + request.getRequestURI();
15+
}
16+
17+
@Override
18+
@ApiStatus.Internal
19+
public @NotNull TransactionNameSource provideTransactionSource() {
20+
return TransactionNameSource.URL;
21+
}
22+
}

0 commit comments

Comments
 (0)