Last active
August 1, 2020 19:01
-
-
Save basinilya/d257de5b5add484d23c15e7e99ef4f03 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.foo.testproxy; | |
import java.beans.BeanInfo; | |
import java.beans.Introspector; | |
import java.beans.PropertyDescriptor; | |
import java.lang.reflect.Method; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Objects; | |
import org.aopalliance.intercept.MethodInterceptor; | |
import org.aopalliance.intercept.MethodInvocation; | |
import org.springframework.aop.framework.ProxyFactory; | |
public class CoalescingProperties { | |
public static void main(String[] args) throws Exception { | |
CoalescingProperties inst = new CoalescingProperties(); | |
A a = new A(); | |
inst.setABCValue(a, null); | |
if (null != a.getB()) { | |
throw new Exception(); | |
} | |
inst.setABCValue(a, "xxx"); | |
if (!"xxx".equals(a.getB().getC().getValue())) { | |
throw new Exception(); | |
} | |
} | |
public String setABCValue(A a, String newValue) throws Exception { | |
a = Coalescing.wrap(a); | |
C c = a.getB().getC(); | |
String oldValue = c.getValue(); | |
if (!Objects.equals(oldValue, newValue)) { | |
c.setValue(newValue); | |
} | |
return oldValue; | |
} | |
public static class Coalescing { | |
public static <T> T wrap(T obj) throws Exception { | |
return wrap(obj, null, null, null); | |
} | |
public static <T> T wrap(T obj, Object parent, Method parentSetter, CoalescingInterceptor<?> parentProxy) throws Exception { | |
ProxyFactory factory = new ProxyFactory(obj); | |
CoalescingInterceptor<T> adv = new CoalescingInterceptor<T>(obj, parent, parentSetter, parentProxy); | |
factory.addAdvice(adv); | |
//factory.addInterface(obj.getClass()); | |
@SuppressWarnings("unchecked") | |
T proxy = (T)factory.getProxy(); | |
return proxy; | |
} | |
} | |
public static class CoalescingInterceptor<T> implements MethodInterceptor { | |
private Object parent; | |
private Method parentSetter; | |
private CoalescingInterceptor<?> parentProxy; | |
private Map<Method, PropertyDescriptor> propMap = new HashMap<>(); | |
private Map<String, Object> props = new HashMap<>(); | |
public CoalescingInterceptor(T obj, Object parent, Method parentSetter, CoalescingInterceptor<?> parentProxy) throws Exception { | |
this.parent = parent; | |
this.parentSetter = parentSetter; | |
this.parentProxy = parentProxy; | |
Class<?> clazz = obj.getClass(); | |
BeanInfo beanInfo = Introspector.getBeanInfo(clazz); | |
PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); | |
for (PropertyDescriptor prop : props) { | |
Method getter = prop.getReadMethod(); | |
Method setter = prop.getWriteMethod(); | |
if (getter != null && setter != null && getter.getParameterTypes().length == 0 | |
&& setter.getParameterTypes().length == 1 | |
) { | |
propMap.put(getter, prop); | |
propMap.put(setter, prop); | |
} | |
} | |
} | |
private static boolean isComplexType(Class<?> clazz) { | |
return !(clazz.isPrimitive() || | |
clazz.getPackage().getName().startsWith("java")); | |
} | |
public Object invoke(MethodInvocation invocation) throws Throwable { | |
Object res = invocation.proceed(); | |
Method method = invocation.getMethod(); | |
PropertyDescriptor prop = propMap.get(method); | |
Object self = invocation.getThis(); | |
if (prop != null) { | |
String propName = prop.getName(); | |
Method readMethod = prop.getReadMethod(); | |
if (method.equals(readMethod)) { | |
Object cachedProxy = props.get(propName); | |
if (cachedProxy != null) { | |
res = cachedProxy; | |
} else if (isComplexType(prop.getPropertyType())) { | |
if (res == null) { | |
Class<?> resClazz = method.getReturnType(); | |
res = resClazz.newInstance(); | |
} | |
res = Coalescing.wrap(res, self, prop.getWriteMethod(), this); | |
props.put(propName, res); | |
} | |
} else { // setter | |
commit(self); | |
} | |
} | |
return res; | |
} | |
private void commit(Object self) throws Exception { | |
if (parent != null) { | |
parentSetter.invoke(parent, self); | |
parentProxy.commit(parent); | |
} | |
} | |
} | |
public static class A { | |
private B b; | |
public B getB() { | |
return b; | |
} | |
public void setB(B b) { | |
this.b = b; | |
} | |
} | |
public static class B { | |
private C c; | |
public C getC() { | |
return c; | |
} | |
public void setC(C b) { | |
this.c = b; | |
} | |
} | |
public static class C { | |
private String value; | |
public String getValue() { | |
return value; | |
} | |
public void setValue(String value) { | |
this.value = value; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.foo.testproxy; | |
import java.beans.BeanInfo; | |
import java.beans.Introspector; | |
import java.beans.PropertyDescriptor; | |
import java.lang.invoke.MethodHandle; | |
import java.lang.invoke.MethodHandles; | |
import java.lang.invoke.MethodHandles.Lookup; | |
import java.lang.invoke.MethodType; | |
import java.lang.ref.WeakReference; | |
import java.lang.reflect.Method; | |
import java.text.MessageFormat; | |
import java.util.AbstractMap; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.Map.Entry; | |
import java.util.function.Function; | |
import org.aopalliance.intercept.MethodInterceptor; | |
import org.aopalliance.intercept.MethodInvocation; | |
import org.springframework.aop.framework.ProxyFactory; | |
public class PropFromLambda { | |
public static ChildBean getOrCreateFast(ParentBean bean, Function<ParentBean, ChildBean> getter) throws Exception { | |
ChildBean res = getter.apply(bean); | |
if (res == null) { | |
res = new ChildBean(); | |
bean.setB(res); | |
} | |
return res; | |
} | |
public static <T,R> R getOrCreate(T bean, Function<T, R> getter) throws Exception { | |
log("getOrCreate"); | |
R res = getter.apply(bean); | |
if (res == null) { | |
log("getter returned null, trying INITER_CACHE"); | |
WeakIdentityHashMap<Function<T, R>, FunctionWithExceptions<T, R, Exception>> initerCache = getIniterCache(); | |
FunctionWithExceptions<T, R, Exception> initer = initerCache.get(getter); | |
if (initer != null) { | |
log("initer found"); | |
return (R)initer.apply(bean); | |
} | |
log("initer not found, trying proxy"); | |
Entry<T, CoalescingInterceptor<T>> pair = wrap(bean); | |
ThreadLocal<FunctionWithExceptions<T, R, Exception>> initerTls = | |
pair.getValue().getIniterTls(); | |
try { | |
res = getter.apply( pair.getKey() ); | |
initer = initerTls.get(); | |
} finally { | |
initerTls.set(null); | |
} | |
if (initer != null) { | |
res = initer.apply(bean); | |
} else { | |
initer = cast(NULL_INITER); | |
} | |
String comment = "getter " + System.identityHashCode(getter); | |
log(comment); | |
initerCache.put(getter, initer, comment); | |
} | |
return res; | |
} | |
public static class ChildBean { | |
ChildBean() { | |
log("ChildBean"); | |
} | |
} | |
public static class ParentBean { | |
ChildBean b; | |
public ChildBean getB() { | |
return b; | |
} | |
public void setB(ChildBean a) { | |
log("setB"); | |
this.b = a; | |
} | |
} | |
public static void main(String[] args) throws Exception { | |
doStuff(); | |
long stime = System.currentTimeMillis(); | |
long etime = stime + 1000 * 10; | |
int i = 0; | |
for (;etime - System.currentTimeMillis() > 0;i++) { | |
doStuff(); | |
log(""); | |
//gc(); | |
log(""); | |
doStuff(); | |
} | |
System.out.println(MessageFormat.format("{0}", i)); | |
} | |
static void log(String msg) { | |
//System.out.println(msg); | |
} | |
private static void gc() { | |
for (int i = 0; i < 100; i++) { | |
(new byte[1000000]).getClass(); | |
System.gc(); | |
} | |
} | |
private static void doStuff() throws Exception { | |
ParentBean a = new ParentBean(); | |
ChildBean b; | |
boolean useMRef = true; | |
if (useMRef) { | |
//b = getOrCreate(a, PropFromLambda::dummy); | |
b = getOrCreate(a, ParentBean::getB); | |
} else { | |
double f = Math.random() + 1.0; | |
b = getOrCreate(a, (a2) -> { return f != 0 ? a2.getB() : null; }); | |
} | |
String.valueOf(b); | |
log(""); | |
a.setB(null); | |
if (useMRef) { | |
//b = getOrCreate(a, PropFromLambda::dummy); | |
b = getOrCreate(a, ParentBean::getB); | |
} else { | |
double f = Math.random(); | |
b = getOrCreate(a, (a2) -> { return f != 0 ? a2.getB() : null; }); | |
} | |
String.valueOf(b); | |
} | |
static ChildBean dummy(ParentBean bean) { | |
return null; | |
} | |
static final WeakIdentityHashMap<?,?> INITER_CACHE2 = | |
new WeakIdentityHashMap<>(); | |
static <T, R> WeakIdentityHashMap<Function<T, R>, FunctionWithExceptions<T, R, Exception>> | |
getIniterCache() { | |
return cast(INITER_CACHE2); | |
} | |
static final WeakIdentityHashMap<?, ?> HOOK_CACHE2 = new WeakIdentityHashMap<>(); | |
static <T> WeakIdentityHashMap<T, WeakReference<Map.Entry<T, CoalescingInterceptor<T> > > > getHookCache() { | |
return cast(HOOK_CACHE2); | |
} | |
private static final FunctionWithExceptions<?, ?, Exception> NULL_INITER = x -> null; | |
@SuppressWarnings("unchecked") | |
private static <T> Entry<T, CoalescingInterceptor<T>> wrap(T obj ) throws Exception { | |
Entry<T, CoalescingInterceptor<T>> pair = null; | |
CoalescingInterceptor<T> inter = null; | |
T proxy = null; | |
WeakIdentityHashMap<T, WeakReference<Entry<T, CoalescingInterceptor<T>>>> hookCache = | |
getHookCache(); | |
WeakReference<Map.Entry<T, CoalescingInterceptor<T>>> ref = hookCache.get(obj); | |
if (ref != null) { | |
pair = ref.get(); | |
if (pair != null) { | |
log("proxy found"); | |
proxy = pair.getKey(); | |
inter = pair.getValue(); | |
} | |
} | |
if (pair == null) { | |
log("proxy not found, creating"); | |
ProxyFactory factory = new ProxyFactory(obj); | |
inter = findInterceptor(obj.getClass()); | |
factory.addAdvice(inter); | |
proxy = (T)factory.getProxy(); | |
pair = new AbstractMap.SimpleEntry<>(proxy, inter); | |
String comment = "object " + System.identityHashCode(obj); | |
log(comment); | |
hookCache.put(obj, new WeakReference<>(pair), comment); | |
} | |
return pair; | |
} | |
private static <T> CoalescingInterceptor<T> findInterceptor(Class<?> clazz) throws Exception { | |
return new CoalescingInterceptor<T>(clazz); | |
} | |
private static class CoalescingInterceptor<T> implements MethodInterceptor { | |
private final ThreadLocal<Object> initerTls = new ThreadLocal<>(); | |
<R> ThreadLocal< FunctionWithExceptions<T, R, Exception> > getIniterTls() { | |
return cast(initerTls); | |
} | |
private Map<Method, PropertyDescriptor> propMap = new HashMap<>(); | |
CoalescingInterceptor(Class<?> clazz) throws Exception { | |
log("CoalescingInterceptor"); | |
BeanInfo beanInfo = Introspector.getBeanInfo(clazz); | |
PropertyDescriptor[] props = beanInfo.getPropertyDescriptors(); | |
for (PropertyDescriptor prop : props) { | |
Method getter = prop.getReadMethod(); | |
Method setter = prop.getWriteMethod(); | |
if (getter != null && setter != null && getter.getParameterTypes().length == 0 | |
&& setter.getParameterTypes().length == 1) { | |
propMap.put(getter, prop); | |
} | |
} | |
} | |
@Override | |
public Object invoke(MethodInvocation invocation) throws Throwable { | |
Object res = invocation.proceed(); | |
if (res == null) { | |
Method method = invocation.getMethod(); | |
log("invoke " + method.getName()); | |
PropertyDescriptor prop = propMap.get(method); | |
if (prop != null) { | |
Class<?> resClazz = method.getReturnType(); | |
Method setterMethod = prop.getWriteMethod(); | |
final int DIRECT = 0; // 257 095 851 | |
final int REFLECT = 1; // 221 991 485 | |
final int INVOKE = 2; // 197 712 765 | |
final int EXACT_PARAMS_CAST = 3; // 210 515 640 | |
final int EXACT_HANDLERS_ADAPTED = 4; // 220 141 084 | |
int mode = REFLECT; | |
FunctionWithExceptions<T, ?, Throwable> initer; | |
switch (mode) { | |
case DIRECT: | |
initer = target -> { | |
ChildBean res2 = new ChildBean(); | |
((ParentBean)target).setB(res2); | |
return res2; | |
}; | |
break; | |
case REFLECT: | |
initer = target -> { | |
Object res2 = resClazz.newInstance(); | |
setterMethod.invoke(target, res2); | |
return res2; | |
}; | |
break; | |
default: | |
Lookup lookup = MethodHandles.lookup(); | |
final MethodHandle setterHandle = lookup.unreflect(setterMethod); | |
final MethodHandle constructorHandle = lookup.findConstructor(resClazz, MethodType.methodType(void.class)); | |
if (mode == INVOKE) { | |
initer = target -> { | |
Object res2 = constructorHandle.invoke(); | |
setterHandle.invoke(target, res2); | |
return res2; | |
}; | |
} else if (mode == EXACT_HANDLERS_ADAPTED) { | |
final MethodHandle setterHandle2 = setterHandle.asType( MethodType.methodType(void.class, Object.class,Object.class)); | |
final MethodHandle constructorHandle2 = constructorHandle.asType(MethodType.methodType( Object.class )); | |
initer = target -> { | |
Object res2 = constructorHandle2.invokeExact(); | |
setterHandle2.invokeExact (target, res2); | |
return res2; | |
}; | |
} else { | |
initer = target -> { | |
ChildBean res2 = (ChildBean)constructorHandle.invokeExact(); | |
setterHandle.invokeExact ((ParentBean)target, res2); | |
return res2; | |
}; | |
} | |
} | |
initerTls.set(initer); | |
} | |
} | |
return res; | |
} | |
} | |
static <T> T invokeExact(MethodHandle constructorHandle) throws Throwable { | |
return (T)constructorHandle.invokeExact(); | |
} | |
@SuppressWarnings("unchecked") | |
static <T> T cast(Object x) { | |
return (T)x; | |
} | |
@FunctionalInterface | |
private interface FunctionWithExceptions<T, R, E extends Throwable> { | |
R apply(T t) throws E; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.foo.testproxy; | |
import java.lang.ref.Reference; | |
import java.lang.ref.ReferenceQueue; | |
import java.lang.ref.WeakReference; | |
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* <p>A map where keys are compared using identity comparison (like | |
* IdentityHashMap) but where the presence of an object as a key in | |
* the map does not prevent it being garbage collected (like | |
* WeakHashMap). This class does not implement the Map interface | |
* because it is difficult to ensure correct semantics for iterators | |
* over the entrySet().</p> | |
* | |
* <p>Because we do not implement Map, we do not copy the questionable | |
* interface where you can call get(k) or remove(k) for any type of k, | |
* which of course can only have an effect if k is of type K.</p> | |
* | |
* <p>This map does not support null keys.</p> | |
*/ | |
/* | |
* The approach | |
* is to wrap each key in a WeakReference and use the wrapped value as | |
* a key in an ordinary HashMap. The WeakReference has to be a | |
* subclass IdentityWeakReference (IWR) where two IWRs are equal if | |
* they refer to the same object. This enables us to find the entry | |
* again. | |
*/ | |
class WeakIdentityHashMap<K, V> { | |
WeakIdentityHashMap() {} | |
V get(K key) { | |
expunge(); | |
WeakReference<K> keyref = makeReference(key); | |
return map.get(keyref); | |
} | |
public void clear() { | |
map.clear(); | |
} | |
public V put(K key, V value, String comment) { | |
expunge(); | |
if (key == null) | |
throw new IllegalArgumentException("Null key"); | |
WeakReference<K> keyref = makeReference(key, refQueue, comment); | |
return map.put(keyref, value); | |
} | |
public V remove(K key) { | |
expunge(); | |
WeakReference<K> keyref = makeReference(key); | |
return map.remove(keyref); | |
} | |
private void expunge() { | |
Reference<? extends K> ref; | |
while ((ref = refQueue.poll()) != null) { | |
PropFromLambda.log("expunge: " + ((IdentityWeakReference<?>)ref).comment); | |
map.remove(ref); | |
} | |
} | |
private WeakReference<K> makeReference(K referent) { | |
return new IdentityWeakReference<K>(referent); | |
} | |
private WeakReference<K> makeReference(K referent, ReferenceQueue<K> q, String comment) { | |
return new IdentityWeakReference<K>(referent, q, comment); | |
} | |
/** | |
* WeakReference where equals and hashCode are based on the | |
* referent. More precisely, two objects are equal if they are | |
* identical or if they both have the same non-null referent. The | |
* hashCode is the value the original referent had. Even if the | |
* referent is cleared, the hashCode remains. Thus, objects of | |
* this class can be used as keys in hash-based maps and sets. | |
*/ | |
private static class IdentityWeakReference<T> extends WeakReference<T> { | |
private final String comment; | |
IdentityWeakReference(T o) { | |
this(o, null, null); | |
} | |
IdentityWeakReference(T o, ReferenceQueue<T> q, String comment) { | |
super(o, q); | |
this.hashCode = (o == null) ? 0 : System.identityHashCode(o); | |
this.comment = comment; | |
} | |
public boolean equals(Object o) { | |
if (this == o) | |
return true; | |
if (!(o instanceof IdentityWeakReference<?>)) | |
return false; | |
IdentityWeakReference<?> wr = (IdentityWeakReference<?>) o; | |
Object got = get(); | |
return (got != null && got == wr.get()); | |
} | |
public int hashCode() { | |
return hashCode; | |
} | |
private final int hashCode; | |
} | |
private Map<WeakReference<K>, V> map = new HashMap<>(); | |
private ReferenceQueue<K> refQueue = new ReferenceQueue<K>(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment