Skip to content

Commit 7c82dc4

Browse files
authored
make servlet3 + spring webmvc + jaxrs 2.0 indy compatible (#12432)
1 parent 219149f commit 7c82dc4

File tree

21 files changed

+371
-64
lines changed

21 files changed

+371
-64
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package instrumentation;
7+
8+
/**
9+
* Class that will be injected in target classloader with inline instrumentation and proxied with
10+
* indy instrumentation
11+
*/
12+
public class TestHelperClass {
13+
14+
public TestHelperClass() {}
15+
}

instrumentation/internal/internal-reflection/javaagent-integration-tests/src/main/java/instrumentation/TestInstrumentationModule.java

+19-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@
1010
import com.google.auto.service.AutoService;
1111
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1212
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.ClassInjector;
15+
import io.opentelemetry.javaagent.extension.instrumentation.internal.injection.InjectionMode;
16+
import java.util.Arrays;
1317
import java.util.List;
1418

1519
@AutoService(InstrumentationModule.class)
16-
public class TestInstrumentationModule extends InstrumentationModule {
20+
public class TestInstrumentationModule extends InstrumentationModule
21+
implements ExperimentalInstrumentationModule {
1722
public TestInstrumentationModule() {
1823
super("test-instrumentation");
1924
}
@@ -22,4 +27,17 @@ public TestInstrumentationModule() {
2227
public List<TypeInstrumentation> typeInstrumentations() {
2328
return singletonList(new TestTypeInstrumentation());
2429
}
30+
31+
@Override
32+
public List<String> getAdditionalHelperClassNames() {
33+
// makes the class from instrumentation from the instrumented class with inlined advice
34+
return Arrays.asList("instrumentation.TestHelperClass");
35+
}
36+
37+
@Override
38+
public void injectClasses(ClassInjector injector) {
39+
injector
40+
.proxyBuilder("instrumentation.TestHelperClass")
41+
.inject(InjectionMode.CLASS_AND_RESOURCE);
42+
}
2543
}

instrumentation/internal/internal-reflection/javaagent-integration-tests/src/test/java/ReflectionTest.java

+41-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import static org.assertj.core.api.Assertions.assertThat;
77

8+
import instrumentation.TestHelperClass;
9+
import io.opentelemetry.javaagent.bootstrap.InstrumentationProxy;
810
import io.opentelemetry.javaagent.bootstrap.VirtualFieldAccessorMarker;
911
import io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker;
1012
import java.io.ObjectStreamClass;
@@ -42,7 +44,45 @@ void testOurFieldsAndMethodsAreNotVisibleWithReflection() {
4244
void testGeneratedSerialVersionUid() {
4345
// expected value is computed with serialver utility that comes with jdk
4446
assertThat(ObjectStreamClass.lookup(TestClass.class).getSerialVersionUID())
45-
.isEqualTo(-1508684692096503670L);
47+
.isEqualTo(-4292813100633930936L);
4648
assertThat(TestClass.class.getDeclaredFields().length).isEqualTo(0);
4749
}
50+
51+
@Test
52+
void testInjectedClassProxyUnwrap() throws Exception {
53+
TestClass testClass = new TestClass();
54+
Class<?> helperType = testClass.testHelperClass();
55+
assertThat(helperType)
56+
.describedAs("unable to resolve injected class from instrumented class")
57+
.isNotNull();
58+
59+
Object instance = helperType.getConstructor().newInstance();
60+
if (InstrumentationProxy.class.isAssignableFrom(helperType)) {
61+
// indy advice: must be an indy proxy
62+
63+
for (Method method : helperType.getMethods()) {
64+
assertThat(method.getName())
65+
.describedAs("proxy method must be hidden from reflection")
66+
.isNotEqualTo("__getIndyProxyDelegate");
67+
}
68+
69+
for (Class<?> interfaceType : helperType.getInterfaces()) {
70+
assertThat(interfaceType)
71+
.describedAs("indy proxy interface must be hidden from reflection")
72+
.isNotEqualTo(InstrumentationProxy.class);
73+
}
74+
75+
assertThat(instance).isInstanceOf(InstrumentationProxy.class);
76+
77+
Object proxyDelegate = ((InstrumentationProxy) instance).__getIndyProxyDelegate();
78+
assertThat(proxyDelegate).isNotInstanceOf(InstrumentationProxy.class);
79+
80+
} else {
81+
// inline advice: must be of the expected type
82+
assertThat(helperType).isEqualTo(TestHelperClass.class);
83+
assertThat(instance)
84+
.isInstanceOf(TestHelperClass.class)
85+
.isNotInstanceOf(InstrumentationProxy.class);
86+
}
87+
}
4888
}

instrumentation/internal/internal-reflection/javaagent-integration-tests/src/test/java/TestClass.java

+9
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,13 @@ public String testMethod() {
1717
public String testMethod2() {
1818
return "not instrumented";
1919
}
20+
21+
public Class<?> testHelperClass() {
22+
try {
23+
return Class.forName(
24+
"instrumentation.TestHelperClass", false, TestClass.class.getClassLoader());
25+
} catch (ClassNotFoundException e) {
26+
return null;
27+
}
28+
}
2029
}

instrumentation/internal/internal-reflection/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/internal/reflection/ReflectionHelper.java

+20-13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
package io.opentelemetry.javaagent.instrumentation.internal.reflection;
77

8+
import io.opentelemetry.javaagent.bootstrap.InstrumentationProxy;
89
import io.opentelemetry.javaagent.bootstrap.VirtualFieldAccessorMarker;
910
import io.opentelemetry.javaagent.bootstrap.VirtualFieldDetector;
1011
import io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker;
@@ -37,18 +38,20 @@ public static Field[] filterFields(Class<?> containingClass, Field[] fields) {
3738
}
3839

3940
public static Method[] filterMethods(Class<?> containingClass, Method[] methods) {
40-
if (methods.length == 0
41-
|| !VirtualFieldInstalledMarker.class.isAssignableFrom(containingClass)) {
42-
// nothing to filter when class does not have any added virtual fields
41+
// nothing to filter when class does not have any added virtual fields or is not a proxy
42+
if (methods.length == 0 || noInterfaceToHide(containingClass)) {
4343
return methods;
4444
}
4545
List<Method> result = new ArrayList<>(methods.length);
4646
for (Method method : methods) {
47-
// virtual field accessor methods are marked as synthetic
48-
if (method.isSynthetic()
49-
&& (method.getName().startsWith("__get__opentelemetryVirtualField$")
50-
|| method.getName().startsWith("__set__opentelemetryVirtualField$"))) {
51-
continue;
47+
// virtual field accessor or proxy methods are marked as synthetic
48+
if (method.isSynthetic()) {
49+
String name = method.getName();
50+
if ((name.startsWith("__get__opentelemetryVirtualField$")
51+
|| name.startsWith("__set__opentelemetryVirtualField$")
52+
|| name.equals("__getIndyProxyDelegate"))) {
53+
continue;
54+
}
5255
}
5356
result.add(method);
5457
}
@@ -57,9 +60,8 @@ public static Method[] filterMethods(Class<?> containingClass, Method[] methods)
5760

5861
@SuppressWarnings("unused")
5962
public static Class<?>[] filterInterfaces(Class<?>[] interfaces, Class<?> containingClass) {
60-
if (interfaces.length == 0
61-
|| !VirtualFieldInstalledMarker.class.isAssignableFrom(containingClass)) {
62-
// nothing to filter when class does not have any added virtual fields
63+
// nothing to filter when class does not have any added virtual fields or is not a proxy
64+
if (interfaces.length == 0 || noInterfaceToHide(containingClass)) {
6365
return interfaces;
6466
}
6567
List<Class<?>> result = new ArrayList<>(interfaces.length);
@@ -69,6 +71,8 @@ public static Class<?>[] filterInterfaces(Class<?>[] interfaces, Class<?> contai
6971
// filter out virtual field marker and accessor interfaces
7072
if (interfaceClass == VirtualFieldInstalledMarker.class) {
7173
continue;
74+
} else if (interfaceClass == InstrumentationProxy.class) {
75+
continue;
7276
} else if (VirtualFieldAccessorMarker.class.isAssignableFrom(interfaceClass)
7377
&& interfaceClass.isSynthetic()
7478
&& interfaceClass.getName().contains("VirtualFieldAccessor$")) {
@@ -77,11 +81,14 @@ public static Class<?>[] filterInterfaces(Class<?>[] interfaces, Class<?> contai
7781
}
7882
result.add(interfaceClass);
7983
}
80-
8184
if (!virtualFieldClassNames.isEmpty()) {
8285
VirtualFieldDetector.markVirtualFields(containingClass, virtualFieldClassNames);
8386
}
84-
8587
return result.toArray(new Class<?>[0]);
8688
}
89+
90+
private static boolean noInterfaceToHide(Class<?> containingClass) {
91+
return !VirtualFieldInstalledMarker.class.isAssignableFrom(containingClass)
92+
&& !InstrumentationProxy.class.isAssignableFrom(containingClass);
93+
}
8794
}

instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/jaxrs/v2_0/JerseyInstrumentationModule.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111
import com.google.auto.service.AutoService;
1212
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1313
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1415
import java.util.List;
1516
import net.bytebuddy.matcher.ElementMatcher;
1617

1718
@AutoService(InstrumentationModule.class)
18-
public class JerseyInstrumentationModule extends InstrumentationModule {
19+
public class JerseyInstrumentationModule extends InstrumentationModule
20+
implements ExperimentalInstrumentationModule {
1921
public JerseyInstrumentationModule() {
2022
super("jaxrs", "jaxrs-2.0", "jersey", "jersey-2.0");
2123
}
@@ -26,11 +28,9 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
2628
}
2729

2830
@Override
29-
public boolean isIndyModule() {
30-
// net.bytebuddy.pool.TypePool$Resolution$NoSuchTypeException: Cannot resolve type description
31-
// for
32-
// io.opentelemetry.javaagent.instrumentation.servlet.v3_0.snippet.Servlet3SnippetInjectingResponseWrapper
33-
return false;
31+
public String getModuleGroup() {
32+
// depends on Servlet3SnippetInjectingResponseWrapper
33+
return "servlet";
3434
}
3535

3636
@Override

instrumentation/servlet/servlet-3.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/servlet/v3_0/Servlet3InstrumentationModule.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.google.auto.service.AutoService;
1212
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1313
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1415
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncContextInstrumentation;
1516
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncContextStartInstrumentation;
1617
import io.opentelemetry.javaagent.instrumentation.servlet.common.async.AsyncStartInstrumentation;
@@ -21,7 +22,8 @@
2122
import net.bytebuddy.matcher.ElementMatcher;
2223

2324
@AutoService(InstrumentationModule.class)
24-
public class Servlet3InstrumentationModule extends InstrumentationModule {
25+
public class Servlet3InstrumentationModule extends InstrumentationModule
26+
implements ExperimentalInstrumentationModule {
2527
private static final String BASE_PACKAGE = "javax.servlet";
2628

2729
public Servlet3InstrumentationModule() {
@@ -34,9 +36,9 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
3436
}
3537

3638
@Override
37-
public boolean isIndyModule() {
38-
// GrailsTest fails
39-
return false;
39+
public String getModuleGroup() {
40+
// depends on servlet instrumentation
41+
return "servlet";
4042
}
4143

4244
@Override

instrumentation/spring/spring-security-config-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/security/config/v6_0/servlet/SpringSecurityConfigServletInstrumentationModule.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
import io.opentelemetry.javaagent.bootstrap.internal.AgentCommonConfig;
1313
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1414
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
15+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1516
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
1617
import java.util.List;
1718
import net.bytebuddy.matcher.ElementMatcher;
1819

1920
/** Instrumentation module for servlet-based applications that use spring-security-config. */
2021
@AutoService(InstrumentationModule.class)
21-
public class SpringSecurityConfigServletInstrumentationModule extends InstrumentationModule {
22+
public class SpringSecurityConfigServletInstrumentationModule extends InstrumentationModule
23+
implements ExperimentalInstrumentationModule {
2224
public SpringSecurityConfigServletInstrumentationModule() {
2325
super("spring-security-config-servlet", "spring-security-config-servlet-6.0");
2426
}
@@ -47,6 +49,12 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
4749
"org.springframework.security.authentication.ObservationAuthenticationManager");
4850
}
4951

52+
@Override
53+
public String getModuleGroup() {
54+
// depends on servlet instrumentation
55+
return "servlet";
56+
}
57+
5058
@Override
5159
public List<TypeInstrumentation> typeInstrumentations() {
5260
return singletonList(new HttpSecurityInstrumentation());

instrumentation/spring/spring-web/spring-web-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/springweb/v3_1/SpringWebInstrumentationModule.java

+20-7
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
1414
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1515
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
16+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1617
import java.util.List;
1718
import net.bytebuddy.matcher.ElementMatcher;
1819

1920
@AutoService(InstrumentationModule.class)
20-
public class SpringWebInstrumentationModule extends InstrumentationModule {
21+
public class SpringWebInstrumentationModule extends InstrumentationModule
22+
implements ExperimentalInstrumentationModule {
2123
public SpringWebInstrumentationModule() {
2224
super("spring-web", "spring-web-3.1");
2325
}
@@ -32,12 +34,23 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
3234

3335
@Override
3436
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
35-
// make the filter class file loadable by ClassPathResource - in some cases (e.g. spring-guice,
36-
// see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/7428) Spring
37-
// might want to read the class file metadata; this line will make the filter class file visible
38-
// to the bean class loader
39-
helperResourceBuilder.register(
40-
"org/springframework/web/servlet/v3_1/OpenTelemetryHandlerMappingFilter.class");
37+
if (!isIndyModule()) {
38+
// make the filter class file loadable by ClassPathResource - in some cases (e.g.
39+
// spring-guice,
40+
// see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/7428)
41+
// Spring
42+
// might want to read the class file metadata; this line will make the filter class file
43+
// visible
44+
// to the bean class loader
45+
helperResourceBuilder.register(
46+
"org/springframework/web/servlet/v3_1/OpenTelemetryHandlerMappingFilter.class");
47+
}
48+
}
49+
50+
@Override
51+
public String getModuleGroup() {
52+
// depends on OpenTelemetryHandlerMappingFilter
53+
return "servlet";
4154
}
4255

4356
@Override

instrumentation/spring/spring-web/spring-web-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/springweb/v3_1/WebApplicationContextInstrumentation.java

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ public static class FilterInjectingAdvice {
6161
public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) {
6262
if (beanFactory instanceof BeanDefinitionRegistry
6363
&& !beanFactory.containsBean("otelAutoDispatcherFilter")) {
64+
65+
// Explicitly loading classes allows to catch any class-loading issue or deal with cases
66+
// where the class is not visible.
6467
try {
6568
// Firstly check whether DispatcherServlet is present. We need to load an instrumented
6669
// class from spring-webmvc to trigger injection that makes

instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/SpringWebInstrumentationModule.java

+20-7
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import io.opentelemetry.javaagent.extension.instrumentation.HelperResourceBuilder;
1313
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
1414
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
15+
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
1516
import java.util.List;
1617
import net.bytebuddy.matcher.ElementMatcher;
1718

1819
@AutoService(InstrumentationModule.class)
19-
public class SpringWebInstrumentationModule extends InstrumentationModule {
20+
public class SpringWebInstrumentationModule extends InstrumentationModule
21+
implements ExperimentalInstrumentationModule {
2022

2123
public SpringWebInstrumentationModule() {
2224
super("spring-web", "spring-web-6.0");
@@ -30,12 +32,23 @@ public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
3032

3133
@Override
3234
public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) {
33-
// make the filter class file loadable by ClassPathResource - in some cases (e.g. spring-guice,
34-
// see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/7428) Spring
35-
// might want to read the class file metadata; this line will make the filter class file visible
36-
// to the bean class loader
37-
helperResourceBuilder.register(
38-
"org/springframework/web/servlet/v6_0/OpenTelemetryHandlerMappingFilter.class");
35+
if (!isIndyModule()) {
36+
// make the filter class file loadable by ClassPathResource - in some cases (e.g.
37+
// spring-guice,
38+
// see https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/7428)
39+
// Spring
40+
// might want to read the class file metadata; this line will make the filter class file
41+
// visible
42+
// to the bean class loader
43+
helperResourceBuilder.register(
44+
"org/springframework/web/servlet/v6_0/OpenTelemetryHandlerMappingFilter.class");
45+
}
46+
}
47+
48+
@Override
49+
public String getModuleGroup() {
50+
// depends on OpenTelemetryHandlerMappingFilter
51+
return "servlet";
3952
}
4053

4154
@Override

instrumentation/spring/spring-web/spring-web-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/web/v6_0/WebApplicationContextInstrumentation.java

+2
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public static class FilterInjectingAdvice {
6161
public static void onEnter(@Advice.Argument(0) ConfigurableListableBeanFactory beanFactory) {
6262
if (beanFactory instanceof BeanDefinitionRegistry
6363
&& !beanFactory.containsBean("otelAutoDispatcherFilter")) {
64+
// Explicitly loading classes allows to catch any class-loading issue or deal with cases
65+
// where the class is not visible.
6466
try {
6567
// Firstly check whether DispatcherServlet is present. We need to load an instrumented
6668
// class from spring-webmvc to trigger injection that makes

0 commit comments

Comments
 (0)