Skip to content

Commit a7f57cf

Browse files
committed
better proxy unwrap
1 parent 54a9f00 commit a7f57cf

File tree

5 files changed

+87
-30
lines changed

5 files changed

+87
-30
lines changed

instrumentation/spring/spring-webmvc/spring-webmvc-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v3_1/DispatcherServletInstrumentation.java

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
import io.opentelemetry.context.Context;
1717
import io.opentelemetry.context.Scope;
18+
import io.opentelemetry.javaagent.bootstrap.IndyProxy;
1819
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1920
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
20-
import java.lang.reflect.Field;
2121
import java.util.List;
2222
import net.bytebuddy.asm.Advice;
2323
import net.bytebuddy.description.type.TypeDescription;
@@ -67,20 +67,8 @@ public static void afterRefresh(
6767
return;
6868
}
6969
Object bean = springCtx.getBean("otelAutoDispatcherFilter");
70-
OpenTelemetryHandlerMappingFilter filter;
71-
if (bean instanceof OpenTelemetryHandlerMappingFilter) {
72-
// TODO: remove this branch once advices are not inlined anymore as it's no longer relevant
73-
// inline advice: no proxy class is used
74-
filter = (OpenTelemetryHandlerMappingFilter) bean;
75-
} else {
76-
// non-inlined advice: proxy class is used
77-
try {
78-
Field delegate = bean.getClass().getField("delegate");
79-
filter = (OpenTelemetryHandlerMappingFilter) delegate.get(bean);
80-
} catch (NoSuchFieldException | IllegalAccessException e) {
81-
throw new IllegalStateException(e);
82-
}
83-
}
70+
OpenTelemetryHandlerMappingFilter filter =
71+
IndyProxy.unwrapIfNeeded(bean, OpenTelemetryHandlerMappingFilter.class);
8472
filter.setHandlerMappings(handlerMappings);
8573
}
8674
}

instrumentation/spring/spring-webmvc/spring-webmvc-6.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/spring/webmvc/v6_0/DispatcherServletInstrumentation.java

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
import io.opentelemetry.context.Context;
1717
import io.opentelemetry.context.Scope;
18+
import io.opentelemetry.javaagent.bootstrap.IndyProxy;
1819
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1920
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
20-
import java.lang.reflect.Field;
2121
import java.util.List;
2222
import net.bytebuddy.asm.Advice;
2323
import net.bytebuddy.description.type.TypeDescription;
@@ -68,19 +68,8 @@ public static void afterRefresh(
6868
}
6969

7070
Object bean = springCtx.getBean("otelAutoDispatcherFilter");
71-
OpenTelemetryHandlerMappingFilter filter;
72-
if (bean instanceof OpenTelemetryHandlerMappingFilter) {
73-
// inline advice: no proxy class is used
74-
filter = (OpenTelemetryHandlerMappingFilter) bean;
75-
} else {
76-
// non-inlined advice: proxy class is used
77-
try {
78-
Field delegate = bean.getClass().getField("delegate");
79-
filter = (OpenTelemetryHandlerMappingFilter) delegate.get(bean);
80-
} catch (NoSuchFieldException | IllegalAccessException e) {
81-
throw new IllegalStateException(e);
82-
}
83-
}
71+
OpenTelemetryHandlerMappingFilter filter =
72+
IndyProxy.unwrapIfNeeded(bean, OpenTelemetryHandlerMappingFilter.class);
8473
filter.setHandlerMappings(handlerMappings);
8574
}
8675
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.bootstrap;
7+
8+
import java.lang.reflect.Field;
9+
10+
/** Interface added to proxy classes injected for indy instrumentation */
11+
@SuppressWarnings("InterfaceWithOnlyStatics") // TODO: remove this once we have a method
12+
public interface IndyProxy {
13+
14+
/**
15+
* Unwraps the proxied object
16+
*
17+
* @return unwrapped object
18+
*/
19+
default Object unwrap() {
20+
try {
21+
// current implementation based on introspection + public delegate field
22+
Field delegate = this.getClass().getField("delegate");
23+
return delegate.get(this);
24+
} catch (NoSuchFieldException | IllegalAccessException e) {
25+
throw new IllegalStateException(e);
26+
}
27+
}
28+
29+
/**
30+
* Transparently unwraps an object that might have been proxied for indy instrumentation. When
31+
* unwrapping is not needed, for example for inlined advices, the original object is returned
32+
* effectively making this equivalent to a type cast.
33+
*
34+
* @param o object that might need unwrapping
35+
* @param type expected unwrapped object type
36+
* @param <T> type of the proxied object
37+
* @return unwrapped object
38+
* @throws IllegalArgumentException when object can't be cast or unwrapped to the desired type
39+
*/
40+
static <T> T unwrapIfNeeded(Object o, Class<T> type) {
41+
if (type.isAssignableFrom(o.getClass())) {
42+
return type.cast(o);
43+
}
44+
if (o instanceof IndyProxy) {
45+
Object unwrapped = ((IndyProxy) o).unwrap();
46+
if (type.isAssignableFrom(unwrapped.getClass())) {
47+
return type.cast(unwrapped);
48+
}
49+
}
50+
throw new IllegalArgumentException("unexpected object unwrap");
51+
}
52+
}

javaagent-tooling/src/main/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyProxyFactory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55

66
package io.opentelemetry.javaagent.tooling.instrumentation.indy;
77

8+
import io.opentelemetry.javaagent.bootstrap.IndyProxy;
89
import java.lang.reflect.Method;
910
import java.lang.reflect.Modifier;
11+
import java.util.ArrayList;
1012
import java.util.List;
1113
import net.bytebuddy.ByteBuddy;
1214
import net.bytebuddy.description.method.MethodDescription;
1315
import net.bytebuddy.description.method.ParameterDescription;
16+
import net.bytebuddy.description.type.TypeDefinition;
1417
import net.bytebuddy.description.type.TypeDescription;
1518
import net.bytebuddy.dynamic.DynamicType;
1619
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
@@ -87,10 +90,12 @@ public IndyProxyFactory(Method bootstrapMethod, BootstrapArgsProvider bootstrapA
8790
public DynamicType.Unloaded<?> generateProxy(
8891
TypeDescription classToProxy, String proxyClassName) {
8992
TypeDescription.Generic superClass = classToProxy.getSuperClass();
93+
List<TypeDefinition> interfaces = new ArrayList<>(classToProxy.getInterfaces());
94+
interfaces.add(TypeDescription.ForLoadedType.of(IndyProxy.class));
9095
DynamicType.Builder<?> builder =
9196
new ByteBuddy()
9297
.subclass(superClass, ConstructorStrategy.Default.NO_CONSTRUCTORS)
93-
.implement(classToProxy.getInterfaces())
98+
.implement(interfaces)
9499
.name(proxyClassName)
95100
.annotateType(classToProxy.getDeclaredAnnotations())
96101
// field must be public to enable resolving the proxy target using introspection

javaagent-tooling/src/test/java/io/opentelemetry/javaagent/tooling/instrumentation/indy/IndyProxyFactoryTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import static org.assertj.core.api.Assertions.assertThat;
99

10+
import io.opentelemetry.javaagent.bootstrap.IndyProxy;
1011
import io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.DummyAnnotation;
1112
import java.lang.invoke.CallSite;
1213
import java.lang.invoke.ConstantCallSite;
@@ -319,6 +320,28 @@ void verifyNonPublicMembersIgnored() throws Exception {
319320
.anySatisfy(method -> assertThat(method.getName()).isEqualTo("publicStaticMethod"));
320321
}
321322

323+
@Test
324+
void verifyUnwrap() throws Exception {
325+
Class<?> proxy = generateProxy(WrappingTest.class);
326+
Object proxyInstance = proxy.getConstructor().newInstance();
327+
assertThat(proxyInstance)
328+
.describedAs("proxies should implement IndyProxy")
329+
.isNotInstanceOf(WrappingTest.class)
330+
.isInstanceOf(IndyProxy.class);
331+
332+
WrappingTest unwrapped = IndyProxy.unwrapIfNeeded(proxyInstance, WrappingTest.class);
333+
assertThat(unwrapped).isInstanceOf(WrappingTest.class).isNotInstanceOf(IndyProxy.class);
334+
335+
assertThat(IndyProxy.unwrapIfNeeded(unwrapped, WrappingTest.class))
336+
.describedAs("unwrap an already unwrapped is a no-op")
337+
.isSameAs(unwrapped);
338+
}
339+
340+
public static class WrappingTest {
341+
342+
public WrappingTest() {}
343+
}
344+
322345
private static Class<?> generateProxy(Class<?> clazz) {
323346
DynamicType.Unloaded<?> unloaded =
324347
proxyFactory.generateProxy(

0 commit comments

Comments
 (0)