Skip to content

Commit a2e839d

Browse files
fix: Assign method call listeners directly to the proxy instance (#2102)
1 parent 6ac3d9b commit a2e839d

File tree

5 files changed

+74
-171
lines changed

5 files changed

+74
-171
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* See the NOTICE file distributed with this work for additional
5+
* information regarding copyright ownership.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.appium.java_client.proxy;
18+
19+
public interface HasMethodCallListeners {
20+
/**
21+
* The setter is dynamically created by ByteBuddy to store
22+
* method call listeners on the instrumented proxy instance.
23+
*
24+
* @param methodCallListeners Array of method call listeners assigned to the proxy instance.
25+
*/
26+
void setMethodCallListeners(MethodCallListener[] methodCallListeners);
27+
28+
/**
29+
* The getter is dynamically created by ByteBuddy to access
30+
* method call listeners on the instrumented proxy instance.
31+
*
32+
* @return Array of method call listeners assigned the proxy instance.
33+
*/
34+
MethodCallListener[] getMethodCallListeners();
35+
}

src/main/java/io/appium/java_client/proxy/Helpers.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919
import com.google.common.base.Preconditions;
2020
import net.bytebuddy.ByteBuddy;
2121
import net.bytebuddy.description.method.MethodDescription;
22+
import net.bytebuddy.description.modifier.Visibility;
2223
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
24+
import net.bytebuddy.implementation.FieldAccessor;
2325
import net.bytebuddy.implementation.MethodDelegation;
2426
import net.bytebuddy.matcher.ElementMatcher;
2527
import net.bytebuddy.matcher.ElementMatchers;
@@ -118,16 +120,18 @@ public static <T> T createProxy(
118120
.subclass(cls)
119121
.method(extraMethodMatcher == null ? matcher : matcher.and(extraMethodMatcher))
120122
.intercept(MethodDelegation.to(Interceptor.class))
123+
// https://github.com/raphw/byte-buddy/blob/2caef35c172897cbdd21d163c55305a64649ce41/byte-buddy-dep/src/test/java/net/bytebuddy/ByteBuddyTutorialExamplesTest.java#L346
124+
.defineField("methodCallListeners", MethodCallListener[].class, Visibility.PRIVATE)
125+
.implement(HasMethodCallListeners.class).intercept(FieldAccessor.ofBeanProperty())
121126
.make()
122127
.load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
123128
.getLoaded()
124129
.asSubclass(cls);
125130

126131
try {
127-
return ProxyListenersContainer.getInstance().setListeners(
128-
cls.cast(proxy.getConstructor(constructorArgTypes).newInstance(constructorArgs)),
129-
listeners
130-
);
132+
T result = cls.cast(proxy.getConstructor(constructorArgTypes).newInstance(constructorArgs));
133+
((HasMethodCallListeners) result).setMethodCallListeners(listeners.toArray(MethodCallListener[]::new));
134+
return result;
131135
} catch (SecurityException | ReflectiveOperationException e) {
132136
throw new IllegalStateException(String.format("Unable to create a proxy of %s", cls.getName()), e);
133137
}

src/main/java/io/appium/java_client/proxy/Interceptor.java

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
import org.slf4j.LoggerFactory;
2626

2727
import java.lang.reflect.Method;
28-
import java.util.Collection;
29-
import java.util.UUID;
3028
import java.util.concurrent.Callable;
3129

30+
import static io.appium.java_client.proxy.MethodCallListener.UNSET;
31+
3232
public class Interceptor {
3333
private static final Logger LOGGER = LoggerFactory.getLogger(Interceptor.class);
3434

@@ -37,7 +37,9 @@ private Interceptor() {
3737

3838
/**
3939
* A magic method used to wrap public method calls in classes
40-
* patched by ByteBuddy and acting as proxies.
40+
* patched by ByteBuddy and acting as proxies. The performance
41+
* of this method is mission-critical as it gets called upon
42+
* every invocation of any method of the proxied class.
4143
*
4244
* @param self The reference to the original instance.
4345
* @param method The reference to the original method.
@@ -53,12 +55,12 @@ public static Object intercept(
5355
@AllArguments Object[] args,
5456
@SuperCall Callable<?> callable
5557
) throws Throwable {
56-
Collection<MethodCallListener> listeners = ProxyListenersContainer.getInstance().getListeners(self);
57-
if (listeners.isEmpty()) {
58+
var listeners = ((HasMethodCallListeners) self).getMethodCallListeners();
59+
if (listeners == null || listeners.length == 0) {
5860
return callable.call();
5961
}
6062

61-
listeners.forEach(listener -> {
63+
for (var listener : listeners) {
6264
try {
6365
listener.beforeCall(self, method, args);
6466
} catch (NotImplementedException e) {
@@ -68,32 +70,39 @@ public static Object intercept(
6870
self.getClass().getName(), method.getName(), e
6971
);
7072
}
71-
});
73+
}
7274

73-
final UUID noResult = UUID.randomUUID();
74-
Object result = noResult;
75-
for (MethodCallListener listener : listeners) {
75+
Object result = UNSET;
76+
for (var listener : listeners) {
7677
try {
7778
result = listener.call(self, method, args, callable);
78-
break;
79+
if (result != UNSET) {
80+
break;
81+
}
7982
} catch (NotImplementedException e) {
8083
// ignore
8184
} catch (Exception e) {
8285
try {
83-
return listener.onError(self, method, args, e);
86+
result = listener.onError(self, method, args, e);
87+
if (result != UNSET) {
88+
return result;
89+
}
8490
} catch (NotImplementedException ignore) {
8591
// ignore
8692
}
8793
throw e;
8894
}
8995
}
90-
if (noResult.equals(result)) {
96+
if (UNSET == result) {
9197
try {
9298
result = callable.call();
9399
} catch (Exception e) {
94-
for (MethodCallListener listener : listeners) {
100+
for (var listener : listeners) {
95101
try {
96-
return listener.onError(self, method, args, e);
102+
result = listener.onError(self, method, args, e);
103+
if (result != UNSET) {
104+
return result;
105+
}
97106
} catch (NotImplementedException ignore) {
98107
// ignore
99108
}
@@ -102,8 +111,8 @@ public static Object intercept(
102111
}
103112
}
104113

105-
final Object endResult = result == noResult ? null : result;
106-
listeners.forEach(listener -> {
114+
final Object endResult = result == UNSET ? null : result;
115+
for (var listener : listeners) {
107116
try {
108117
listener.afterCall(self, method, args, endResult);
109118
} catch (NotImplementedException e) {
@@ -113,7 +122,7 @@ public static Object intercept(
113122
self.getClass().getName(), method.getName(), e
114123
);
115124
}
116-
});
125+
}
117126
return endResult;
118127
}
119128
}

src/main/java/io/appium/java_client/proxy/MethodCallListener.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
package io.appium.java_client.proxy;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.UUID;
2021
import java.util.concurrent.Callable;
2122

2223
public interface MethodCallListener {
24+
UUID UNSET = UUID.randomUUID();
2325

2426
/**
2527
* The callback to be invoked before any public method of the proxy is called.
@@ -31,7 +33,6 @@ public interface MethodCallListener {
3133
* @param args Array of method arguments
3234
*/
3335
default void beforeCall(Object obj, Method method, Object[] args) {
34-
throw new NotImplementedException();
3536
}
3637

3738
/**
@@ -48,7 +49,7 @@ default void beforeCall(Object obj, Method method, Object[] args) {
4849
* @return The type of the returned result should be castable to the returned type of the original method.
4950
*/
5051
default Object call(Object obj, Method method, Object[] args, Callable<?> original) throws Throwable {
51-
throw new NotImplementedException();
52+
return UNSET;
5253
}
5354

5455
/**
@@ -61,7 +62,6 @@ default Object call(Object obj, Method method, Object[] args, Callable<?> origin
6162
* @param args Array of method arguments
6263
*/
6364
default void afterCall(Object obj, Method method, Object[] args, Object result) {
64-
throw new NotImplementedException();
6565
}
6666

6767
/**
@@ -77,6 +77,6 @@ default void afterCall(Object obj, Method method, Object[] args, Object result)
7777
* type of the returned argument could be cast to the returned type of the original method.
7878
*/
7979
default Object onError(Object obj, Method method, Object[] args, Throwable e) throws Throwable {
80-
throw new NotImplementedException();
80+
return UNSET;
8181
}
8282
}

src/main/java/io/appium/java_client/proxy/ProxyListenersContainer.java

Lines changed: 0 additions & 145 deletions
This file was deleted.

0 commit comments

Comments
 (0)