Skip to content

Commit 2ad1dbd

Browse files
Merge pull request #12 from remondis-it/proxy-class-cashing-bug
Proxy class cashing bug
2 parents c03fc71 + 64f8246 commit 2ad1dbd

7 files changed

Lines changed: 250 additions & 43 deletions

File tree

pom.xml

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<modelVersion>4.0.0</modelVersion>
66
<groupId>com.remondis</groupId>
77
<artifactId>resample</artifactId>
8-
<version>0.0.11</version>
8+
<version>0.0.12</version>
99
<packaging>jar</packaging>
1010
<name>ReSample</name>
1111
<url>https://github.com/remondis-it/resample</url>
@@ -257,7 +257,7 @@
257257
<plugin>
258258
<groupId>org.jacoco</groupId>
259259
<artifactId>jacoco-maven-plugin</artifactId>
260-
<version>0.8.2</version>
260+
<version>0.8.12</version>
261261
<executions>
262262
<execution>
263263
<id>default-prepare-agent</id>
@@ -291,6 +291,8 @@
291291
<exclude>com.remondis.resample.supplier.Suppliers</exclude>
292292
<exclude>com.remondis.resample.AutoSamplingException</exclude>
293293
<exclude>com.remondis.resample.Samples.Default</exclude>
294+
<exclude>com.remondis.resample.InvocationSensor</exclude>
295+
<exclude>com.remondis.resample.InterceptionHandler</exclude>
294296
</excludes>
295297
<element>CLASS</element>
296298
<limits>
@@ -419,7 +421,42 @@
419421
</limit>
420422
</limits>
421423
</rule>
422-
424+
<rule>
425+
<includes>
426+
<include>com.remondis.resample.InvocationSensor</include>
427+
</includes>
428+
<element>CLASS</element>
429+
<limits>
430+
<limit>
431+
<counter>BRANCH</counter>
432+
<value>COVEREDRATIO</value>
433+
<minimum>0.75</minimum>
434+
</limit>
435+
<limit>
436+
<counter>INSTRUCTION</counter>
437+
<value>COVEREDRATIO</value>
438+
<minimum>0.97</minimum>
439+
</limit>
440+
</limits>
441+
</rule>
442+
<rule>
443+
<includes>
444+
<include>com.remondis.resample.InterceptionHandler</include>
445+
</includes>
446+
<element>CLASS</element>
447+
<limits>
448+
<limit>
449+
<counter>BRANCH</counter>
450+
<value>COVEREDRATIO</value>
451+
<minimum>0.66</minimum>
452+
</limit>
453+
<limit>
454+
<counter>INSTRUCTION</counter>
455+
<value>COVEREDRATIO</value>
456+
<minimum>0.96</minimum>
457+
</limit>
458+
</limits>
459+
</rule>
423460
</rules>
424461
</configuration>
425462
</execution>
@@ -505,7 +542,7 @@
505542
<dependency>
506543
<groupId>net.bytebuddy</groupId>
507544
<artifactId>byte-buddy</artifactId>
508-
<version>1.12.8</version>
545+
<version>1.14.18</version>
509546
</dependency>
510547

511548
<!-- Test dependencies -->
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.remondis.resample;
2+
3+
import java.util.Collections;
4+
import java.util.LinkedList;
5+
import java.util.List;
6+
7+
import static java.util.Collections.unmodifiableList;
8+
import static java.util.Objects.isNull;
9+
import static java.util.Objects.nonNull;
10+
11+
public class InterceptionHandler<T> {
12+
13+
private T proxyObject;
14+
private final ThreadLocal<List<String>> threadLocalPropertyNames = ThreadLocal.withInitial(LinkedList::new);
15+
16+
public void setProxyObject(T proxyObject) {
17+
this.proxyObject = proxyObject;
18+
}
19+
20+
public T getProxyObject() {
21+
return proxyObject;
22+
}
23+
24+
public List<String> getTrackedPropertyNames() {
25+
List<String> list = threadLocalPropertyNames.get();
26+
// Reset thread local after access.
27+
reset();
28+
return isNull(list) ? Collections.emptyList() : unmodifiableList(list);
29+
}
30+
31+
public List<String> getThreadLocalPropertyNames() {
32+
return threadLocalPropertyNames.get();
33+
}
34+
35+
/**
36+
* Resets the thread local list of property names.
37+
*/
38+
void reset() {
39+
threadLocalPropertyNames.remove();
40+
}
41+
42+
public boolean hasTrackedProperties() {
43+
return nonNull(threadLocalPropertyNames.get()) && !threadLocalPropertyNames.get()
44+
.isEmpty();
45+
}
46+
}

src/main/java/com/remondis/resample/InvocationSensor.java

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@
44
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
55

66
import java.lang.reflect.Method;
7-
import java.util.Collections;
8-
import java.util.LinkedList;
97
import java.util.List;
8+
import java.util.Map;
9+
import java.util.concurrent.ConcurrentHashMap;
1010

1111
import static com.remondis.resample.ReflectionUtil.defaultValue;
1212
import static com.remondis.resample.ReflectionUtil.invokeMethodProxySafe;
1313
import static com.remondis.resample.ReflectionUtil.toPropertyName;
14+
import static java.lang.ClassLoader.getSystemClassLoader;
15+
import static java.util.Objects.isNull;
1416
import static net.bytebuddy.implementation.InvocationHandlerAdapter.of;
1517
import static net.bytebuddy.matcher.ElementMatchers.isDeclaredBy;
1618
import static net.bytebuddy.matcher.ElementMatchers.isGetter;
@@ -24,30 +26,42 @@
2426
*/
2527
class InvocationSensor<T> {
2628

27-
private T proxyObject;
29+
static Map<Class<?>, InterceptionHandler<?>> interceptionHandlerCache = new ConcurrentHashMap<>();
2830

29-
private List<String> propertyNames = new LinkedList<>();
31+
private InterceptionHandler<T> interceptionHandler;
3032

3133
InvocationSensor(Class<T> superType) {
32-
Class<? extends T> proxyClass = new ByteBuddy().subclass(superType)
33-
.method(isGetter())
34-
.intercept(of((proxy, method, args) -> markPropertyAsCalled(method)))
35-
.method(isDeclaredBy(Object.class))
36-
.intercept(of((proxy, method, args) -> invokeMethodProxySafe(method, this, args)))
37-
.method(not(isGetter()).and(not(isDeclaredBy(Object.class))))
38-
.intercept(of((proxy, method, args) -> {
39-
throw ReflectionException.notAGetter(method);
40-
}))
41-
.make()
42-
.load(getClass().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
43-
.getLoaded();
44-
45-
proxyObject = superType.cast(ReflectionUtil.newInstance(proxyClass));
34+
ClassLoader classLoader;
35+
if (isNull(superType) || isNull(superType.getClassLoader())) {
36+
classLoader = getSystemClassLoader();
37+
} else {
38+
classLoader = superType.getClassLoader();
39+
}
40+
if (interceptionHandlerCache.containsKey(superType)) {
41+
this.interceptionHandler = (InterceptionHandler<T>) interceptionHandlerCache.get(superType);
42+
} else {
43+
Class<? extends T> proxyClass = new ByteBuddy().subclass(superType)
44+
.method(isGetter())
45+
.intercept(of((proxy, method, args) -> markPropertyAsCalled(method)))
46+
.method(isDeclaredBy(Object.class))
47+
.intercept(of((proxy, method, args) -> invokeMethodProxySafe(method, this, args)))
48+
.method(not(isGetter()).and(not(isDeclaredBy(Object.class))))
49+
.intercept(of((proxy, method, args) -> {
50+
throw ReflectionException.notAGetter(method);
51+
}))
52+
.make()
53+
.load(classLoader, ClassLoadingStrategy.Default.INJECTION)
54+
.getLoaded();
55+
this.interceptionHandler = new InterceptionHandler<>();
56+
this.interceptionHandler.setProxyObject(superType.cast(ReflectionUtil.newInstance(proxyClass)));
57+
interceptionHandlerCache.put(superType, this.interceptionHandler);
58+
}
4659
}
4760

4861
private Object markPropertyAsCalled(Method method) {
4962
String propertyName = toPropertyName(method);
50-
propertyNames.add(propertyName);
63+
interceptionHandler.getThreadLocalPropertyNames()
64+
.add(propertyName);
5165
return nullOrDefaultValue(method.getReturnType());
5266
}
5367

@@ -57,7 +71,7 @@ private Object markPropertyAsCalled(Method method) {
5771
* @return The proxy.
5872
*/
5973
T getSensor() {
60-
return proxyObject;
74+
return interceptionHandler.getProxyObject();
6175
}
6276

6377
/**
@@ -66,24 +80,21 @@ T getSensor() {
6680
* @return Returns the tracked property names.
6781
*/
6882
List<String> getTrackedPropertyNames() {
69-
return Collections.unmodifiableList(propertyNames);
83+
return interceptionHandler.getTrackedPropertyNames();
7084
}
7185

7286
/**
7387
* Checks if there were any properties accessed by get calls.
7488
*
75-
* @return Returns <code>true</code> if there were at least one interaction with
76-
* a property. Otherwise <code>false</code> is returned.
89+
* @return Returns <code>true</code> if there were at least one interaction with a property. Otherwise
90+
* <code>false</code> is returned.
7791
*/
7892
boolean hasTrackedProperties() {
79-
return !propertyNames.isEmpty();
93+
return interceptionHandler.hasTrackedProperties();
8094
}
8195

82-
/**
83-
* Resets all tracked information.
84-
*/
8596
void reset() {
86-
propertyNames.clear();
97+
interceptionHandler.reset();
8798
}
8899

89100
private static Object nullOrDefaultValue(Class<?> returnType) {
Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.remondis.resample;
22

3+
import java.util.Objects;
4+
35
public class Dummy {
46

57
private String field;
68

7-
public Dummy(String field) {
9+
private String anotherField;
10+
11+
public Dummy(String field, String anotherField) {
812
super();
913
this.field = field;
14+
this.anotherField = anotherField;
1015
}
1116

1217
public Dummy() {
@@ -21,12 +26,17 @@ public void setField(String field) {
2126
this.field = field;
2227
}
2328

29+
public String getAnotherField() {
30+
return anotherField;
31+
}
32+
33+
public void setAnotherField(String anotherField) {
34+
this.anotherField = anotherField;
35+
}
36+
2437
@Override
2538
public int hashCode() {
26-
final int prime = 31;
27-
int result = 1;
28-
result = prime * result + ((field == null) ? 0 : field.hashCode());
29-
return result;
39+
return Objects.hash(field, anotherField);
3040
}
3141

3242
@Override
@@ -38,12 +48,12 @@ public boolean equals(Object obj) {
3848
if (getClass() != obj.getClass())
3949
return false;
4050
Dummy other = (Dummy) obj;
41-
if (field == null) {
42-
if (other.field != null)
43-
return false;
44-
} else if (!field.equals(other.field))
45-
return false;
46-
return true;
51+
return Objects.equals(field, other.field) && Objects.equals(anotherField, other.anotherField);
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return "Dummy [field=" + field + ", anotherField=" + anotherField + "]";
4757
}
4858

4959
}

0 commit comments

Comments
 (0)