Enable serialization of proxied entity objects. Enable the use of default implementation for getter methods to supply default values when the map contains a 'null' (or doesn't contain that key).
This commit is contained in:
parent
89303f9179
commit
1c68ebdcb8
2 changed files with 35 additions and 21 deletions
2
pom.xml
2
pom.xml
|
@ -5,7 +5,7 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>net.helenus</groupId>
|
||||
<artifactId>helenus-core</artifactId>
|
||||
<version>2.0.17-SNAPSHOT</version>
|
||||
<version>2.0.19-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>helenus</name>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package net.helenus.core.reflect;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
@ -24,9 +25,11 @@ import java.lang.reflect.Proxy;
|
|||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import net.helenus.mapping.annotation.Transient;
|
||||
import net.helenus.support.HelenusException;
|
||||
|
||||
public class MapperInvocationHandler<E> implements InvocationHandler {
|
||||
public class MapperInvocationHandler<E> implements InvocationHandler, Serializable {
|
||||
private static final long serialVersionUID = -7044209982830584984L;
|
||||
|
||||
private final Map<String, Object> src;
|
||||
private final Class<E> iface;
|
||||
|
@ -36,25 +39,31 @@ public class MapperInvocationHandler<E> implements InvocationHandler {
|
|||
this.iface = iface;
|
||||
}
|
||||
|
||||
private Object invokeDefault(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
// NOTE: This is reflection magic to invoke (non-recursively) a default method implemented on an interface
|
||||
// that we've proxied (in ReflectionDslInstantiator). I found the answer in this article.
|
||||
// https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles/
|
||||
|
||||
// First, we need an instance of a private inner-class found in MethodHandles.
|
||||
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class,
|
||||
int.class);
|
||||
constructor.setAccessible(true);
|
||||
|
||||
// Now we need to lookup and invoke special the default method on the interface class.
|
||||
final Class<?> declaringClass = method.getDeclaringClass();
|
||||
Object result = constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
|
||||
.unreflectSpecial(method, declaringClass)
|
||||
.bindTo(proxy)
|
||||
.invokeWithArguments(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
|
||||
if (method.isDefault()) {
|
||||
// NOTE: This is reflection magic to invoke (non-recursively) a default method implemented on an interface
|
||||
// that we've proxied (in ReflectionDslInstantiator). I found the answer in this article.
|
||||
// https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles/
|
||||
|
||||
// First, we need an instance of a private inner-class found in MethodHandles.
|
||||
Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
|
||||
constructor.setAccessible(true);
|
||||
|
||||
// Now we need to lookup and invoke special the default method on the interface class.
|
||||
final Class<?> declaringClass = method.getDeclaringClass();
|
||||
Object result = constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
|
||||
.unreflectSpecial(method, declaringClass)
|
||||
.bindTo(proxy)
|
||||
.invokeWithArguments(args);
|
||||
return result;
|
||||
// Transient, default methods should simply be invoked as-is.
|
||||
if (method.isDefault() && method.getDeclaredAnnotation(Transient.class) == null) {
|
||||
return invokeDefault(proxy, method, args);
|
||||
}
|
||||
|
||||
String methodName = method.getName();
|
||||
|
@ -92,15 +101,20 @@ public class MapperInvocationHandler<E> implements InvocationHandler {
|
|||
|
||||
if (value == null) {
|
||||
|
||||
if (returnType.isPrimitive()) {
|
||||
// Default implementations of non-Transient methods in entities are the default value when the
|
||||
// map contains 'null'.
|
||||
if (method.isDefault()) {
|
||||
return invokeDefault(proxy, method, args);
|
||||
}
|
||||
|
||||
// Otherwise, if the return type of the method is a primitive Java type then we'll return the standard
|
||||
// default values to avoid a NPE in user code.
|
||||
if (returnType.isPrimitive()) {
|
||||
DefaultPrimitiveTypes type = DefaultPrimitiveTypes.lookup(returnType);
|
||||
if (type == null) {
|
||||
throw new HelenusException("unknown primitive type " + returnType);
|
||||
}
|
||||
|
||||
return type.getDefaultValue();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue