Skip to content

Commit

Permalink
Fix java.lang.ClassNotFoundException: org.springframework.web.servlet…
Browse files Browse the repository at this point in the history
….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
  • Loading branch information
adinauer authored Apr 9, 2024
1 parent adebce6 commit 39e8942
Show file tree
Hide file tree
Showing 9 changed files with 123 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
### Fixes

- Add rate limit to Metrics ([#3334](https://github.com/getsentry/sentry-java/pull/3334))
- 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))

## 7.7.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.sentry.spring.jakarta.tracing.SentryTracingFilter;
import io.sentry.spring.jakarta.tracing.SentryTransactionPointcutConfiguration;
import io.sentry.spring.jakarta.tracing.SpringMvcTransactionNameProvider;
import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider;
import io.sentry.spring.jakarta.tracing.TransactionNameProvider;
import io.sentry.transport.ITransportGate;
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
Expand All @@ -49,6 +50,7 @@
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.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.client.RestClientAutoConfiguration;
Expand Down Expand Up @@ -267,12 +269,6 @@ static class SentrySecurityConfiguration {
return new SentryRequestResolver(hub);
}

@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringMvcTransactionNameProvider();
}

@Bean
@ConditionalOnMissingBean(name = "sentrySpringFilter")
public @NotNull FilterRegistrationBean<SentrySpringFilter> sentrySpringFilter(
Expand All @@ -296,11 +292,10 @@ public FilterRegistrationBean<SentryTracingFilter> sentryTracingFilter(
return filter;
}

/** Wraps exception resolver @Bean because the return type is loaded too early otherwise */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HandlerExceptionResolver.class)
@Open
static class SentryExceptionResolverConfigurationWrapper {
static class SentryMvcModeConfig {

@Bean
@ConditionalOnMissingBean
Expand All @@ -311,6 +306,24 @@ static class SentryExceptionResolverConfigurationWrapper {
return new SentryExceptionResolver(
sentryHub, transactionNameProvider, options.getExceptionResolverOrder());
}

@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringMvcTransactionNameProvider();
}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver")
@Open
static class SentryServletModeConfig {

@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringServletTransactionNameProvider();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import io.sentry.spring.jakarta.SentryUserFilter
import io.sentry.spring.jakarta.SentryUserProvider
import io.sentry.spring.jakarta.SpringSecuritySentryUserProvider
import io.sentry.spring.jakarta.tracing.SentryTracingFilter
import io.sentry.spring.jakarta.tracing.SpringServletTransactionNameProvider
import io.sentry.spring.jakarta.tracing.TransactionNameProvider
import io.sentry.transport.ITransport
import io.sentry.transport.ITransportGate
import io.sentry.transport.apache.ApacheHttpClientTransportFactory
Expand Down Expand Up @@ -445,6 +447,15 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `when Spring MVC is not on the classpath, fallback TransactionNameProvider is configured`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true")
.withClassLoader(FilteredClassLoader(HandlerExceptionResolver::class.java))
.run {
assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf(SpringServletTransactionNameProvider::class.java)
}
}

@Test
fun `when tracing is enabled, creates tracing filter`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.traces-sample-rate=1.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.sentry.spring.tracing.SentryTracingFilter;
import io.sentry.spring.tracing.SentryTransactionPointcutConfiguration;
import io.sentry.spring.tracing.SpringMvcTransactionNameProvider;
import io.sentry.spring.tracing.SpringServletTransactionNameProvider;
import io.sentry.spring.tracing.TransactionNameProvider;
import io.sentry.transport.ITransportGate;
import io.sentry.transport.apache.ApacheHttpClientTransportFactory;
Expand All @@ -49,6 +50,7 @@
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.ConditionalOnMissingClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;
Expand Down Expand Up @@ -265,12 +267,6 @@ static class SentrySecurityConfiguration {
return new SentryRequestResolver(hub);
}

@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringMvcTransactionNameProvider();
}

@Bean
@ConditionalOnMissingBean(name = "sentrySpringFilter")
public @NotNull FilterRegistrationBean<SentrySpringFilter> sentrySpringFilter(
Expand All @@ -294,11 +290,10 @@ public FilterRegistrationBean<SentryTracingFilter> sentryTracingFilter(
return filter;
}

/** Wraps exception resolver @Bean because the return type is loaded too early otherwise */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HandlerExceptionResolver.class)
@Open
static class SentryExceptionResolverConfigurationWrapper {
static class SentryMvcModeConfig {

@Bean
@ConditionalOnMissingBean
Expand All @@ -309,6 +304,24 @@ static class SentryExceptionResolverConfigurationWrapper {
return new SentryExceptionResolver(
sentryHub, transactionNameProvider, options.getExceptionResolverOrder());
}

@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringMvcTransactionNameProvider();
}
}

@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingClass("org.springframework.web.servlet.HandlerExceptionResolver")
@Open
static class SentryServletModeConfig {

@Bean
@ConditionalOnMissingBean(TransactionNameProvider.class)
public @NotNull TransactionNameProvider transactionNameProvider() {
return new SpringServletTransactionNameProvider();
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import io.sentry.spring.SentryUserFilter
import io.sentry.spring.SentryUserProvider
import io.sentry.spring.SpringSecuritySentryUserProvider
import io.sentry.spring.tracing.SentryTracingFilter
import io.sentry.spring.tracing.SpringServletTransactionNameProvider
import io.sentry.spring.tracing.TransactionNameProvider
import io.sentry.transport.ITransport
import io.sentry.transport.ITransportGate
import io.sentry.transport.apache.ApacheHttpClientTransportFactory
Expand Down Expand Up @@ -444,6 +446,17 @@ class SentryAutoConfigurationTest {
}
}

@Test
fun `when Spring MVC is not on the classpath, fallback TransactionNameProvider is configured`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.send-default-pii=true")
.withClassLoader(FilteredClassLoader(HandlerExceptionResolver::class.java))
.run {
assertThat(it.getBean(TransactionNameProvider::class.java)).isInstanceOf(
SpringServletTransactionNameProvider::class.java
)
}
}

@Test
fun `when tracing is enabled, creates tracing filter`() {
contextRunner.withPropertyValues("sentry.dsn=http://key@localhost/proj", "sentry.traces-sample-rate=1.0")
Expand Down
6 changes: 6 additions & 0 deletions sentry-spring-jakarta/api/sentry-spring-jakarta.api
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,12 @@ public final class io/sentry/spring/jakarta/tracing/SpringMvcTransactionNameProv
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
}

public final class io/sentry/spring/jakarta/tracing/SpringServletTransactionNameProvider : io/sentry/spring/jakarta/tracing/TransactionNameProvider {
public fun <init> ()V
public fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
}

public abstract interface class io/sentry/spring/jakarta/tracing/TransactionNameProvider {
public abstract fun provideTransactionName (Ljakarta/servlet/http/HttpServletRequest;)Ljava/lang/String;
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.sentry.spring.jakarta.tracing;

import io.sentry.protocol.TransactionNameSource;
import jakarta.servlet.http.HttpServletRequest;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** Fallback TransactionNameProvider when Spring is used in servlet mode (without MVC). */
@ApiStatus.Internal
public final class SpringServletTransactionNameProvider implements TransactionNameProvider {
@Override
public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) {
return request.getMethod() + " " + request.getRequestURI();
}

@Override
@ApiStatus.Internal
public @NotNull TransactionNameSource provideTransactionSource() {
return TransactionNameSource.URL;
}
}
6 changes: 6 additions & 0 deletions sentry-spring/api/sentry-spring.api
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ public final class io/sentry/spring/tracing/SpringMvcTransactionNameProvider : i
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
}

public final class io/sentry/spring/tracing/SpringServletTransactionNameProvider : io/sentry/spring/tracing/TransactionNameProvider {
public fun <init> ()V
public fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
}

public abstract interface class io/sentry/spring/tracing/TransactionNameProvider {
public abstract fun provideTransactionName (Ljavax/servlet/http/HttpServletRequest;)Ljava/lang/String;
public fun provideTransactionSource ()Lio/sentry/protocol/TransactionNameSource;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.sentry.spring.tracing;

import io.sentry.protocol.TransactionNameSource;
import javax.servlet.http.HttpServletRequest;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** Fallback TransactionNameProvider when Spring is used in servlet mode (without MVC). */
@ApiStatus.Internal
public final class SpringServletTransactionNameProvider implements TransactionNameProvider {
@Override
public @Nullable String provideTransactionName(final @NotNull HttpServletRequest request) {
return request.getMethod() + " " + request.getRequestURI();
}

@Override
@ApiStatus.Internal
public @NotNull TransactionNameSource provideTransactionSource() {
return TransactionNameSource.URL;
}
}

0 comments on commit 39e8942

Please sign in to comment.