Add caching to config invocation handler (#788)
This commit is contained in:
@@ -28,6 +28,8 @@ import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -36,26 +38,49 @@ class ConfigInvocationHandler implements InvocationHandler
|
||||
{
|
||||
private final ConfigManager manager;
|
||||
|
||||
// Caches for annotation values
|
||||
private static final Map<Class<?>, String> groupValueCache = new HashMap<>();
|
||||
private static final Map<Method, String> methodKeyNameCache = new HashMap<>();
|
||||
|
||||
public ConfigInvocationHandler(ConfigManager manager)
|
||||
{
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
private static String groupValueFromProxy(Class<?> proxyClass)
|
||||
{
|
||||
Class<?> iface = proxyClass.getInterfaces()[0];
|
||||
ConfigGroup group = iface.getAnnotation(ConfigGroup.class);
|
||||
|
||||
return group == null ? null : group.value();
|
||||
}
|
||||
|
||||
private static String keyNameFromMethod(Method method)
|
||||
{
|
||||
ConfigItem item = method.getAnnotation(ConfigItem.class);
|
||||
|
||||
return item == null ? null : item.keyName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
|
||||
{
|
||||
Class<?> iface = proxy.getClass().getInterfaces()[0];
|
||||
|
||||
ConfigGroup group = iface.getAnnotation(ConfigGroup.class);
|
||||
ConfigItem item = method.getAnnotation(ConfigItem.class);
|
||||
|
||||
if (group == null)
|
||||
String itemKeyName, groupValue;
|
||||
try
|
||||
{
|
||||
groupValue = groupValueCache.computeIfAbsent(proxy.getClass(), ConfigInvocationHandler::groupValueFromProxy);
|
||||
}
|
||||
catch (NullPointerException e)
|
||||
{
|
||||
log.warn("Configuration proxy class {} has no @ConfigGroup!", proxy.getClass());
|
||||
return null;
|
||||
}
|
||||
|
||||
if (item == null)
|
||||
try
|
||||
{
|
||||
itemKeyName = methodKeyNameCache.computeIfAbsent(method, ConfigInvocationHandler::keyNameFromMethod);
|
||||
}
|
||||
catch (NullPointerException e)
|
||||
{
|
||||
log.warn("Configuration method {} has no @ConfigItem!", method);
|
||||
return null;
|
||||
@@ -64,38 +89,43 @@ class ConfigInvocationHandler implements InvocationHandler
|
||||
if (args == null)
|
||||
{
|
||||
// Getting configuration item
|
||||
String value = manager.getConfiguration(group.value(), item.keyName());
|
||||
|
||||
if (value == null)
|
||||
return manager.getConfigObjectFromCacheOrElse(groupValue, itemKeyName, (value) ->
|
||||
{
|
||||
if (method.isDefault())
|
||||
try
|
||||
{
|
||||
return callDefaultMethod(proxy, method, null);
|
||||
value = manager.getConfiguration(value);
|
||||
if (value == null)
|
||||
{
|
||||
if (method.isDefault())
|
||||
{
|
||||
return callDefaultMethod(proxy, method, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert value to return type
|
||||
Class<?> returnType = method.getReturnType();
|
||||
|
||||
try
|
||||
{
|
||||
return ConfigManager.stringToObject(value, returnType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("Unable to unmarshal {}.{} ", groupValue, itemKeyName, e);
|
||||
if (method.isDefault())
|
||||
{
|
||||
return callDefaultMethod(proxy, method, null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Convert value to return type
|
||||
Class<?> returnType = method.getReturnType();
|
||||
|
||||
try
|
||||
{
|
||||
return ConfigManager.stringToObject(value, returnType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.warn("Unable to unmarshal {}.{} ", group.value(), item.keyName(), e);
|
||||
if (method.isDefault())
|
||||
catch (Throwable throwable)
|
||||
{
|
||||
Object defaultValue = callDefaultMethod(proxy, method, null);
|
||||
|
||||
manager.setConfiguration(group.value(), item.keyName(), defaultValue);
|
||||
|
||||
return defaultValue;
|
||||
log.error("Unable to resolve configuration value {}.{}", groupValue, itemKeyName, throwable);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -103,13 +133,13 @@ class ConfigInvocationHandler implements InvocationHandler
|
||||
|
||||
if (args.length != 1)
|
||||
{
|
||||
throw new RuntimeException("Invalid number of arguents to configuration method");
|
||||
throw new RuntimeException("Invalid number of arguments to configuration method");
|
||||
}
|
||||
|
||||
Object newValue = args[0];
|
||||
|
||||
Class<?> type = method.getParameterTypes()[0];
|
||||
Object oldValue = manager.getConfiguration(group.value(), item.keyName(), type);
|
||||
Object oldValue = manager.getConfiguration(groupValue, itemKeyName, type);
|
||||
|
||||
if (Objects.equals(oldValue, newValue))
|
||||
{
|
||||
@@ -124,19 +154,19 @@ class ConfigInvocationHandler implements InvocationHandler
|
||||
if (Objects.equals(newValue, defaultValue))
|
||||
{
|
||||
// Just unset if it goes back to the default
|
||||
manager.unsetConfiguration(group.value(), item.keyName());
|
||||
manager.unsetConfiguration(groupValue, itemKeyName);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (newValue == null)
|
||||
{
|
||||
manager.unsetConfiguration(group.value(), item.keyName());
|
||||
manager.unsetConfiguration(groupValue, itemKeyName);
|
||||
}
|
||||
else
|
||||
{
|
||||
String newValueStr = ConfigManager.objectToString(newValue);
|
||||
manager.setConfiguration(group.value(), item.keyName(), newValueStr);
|
||||
manager.setConfiguration(groupValue, itemKeyName, newValueStr);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
@@ -81,6 +82,7 @@ public class ConfigManager
|
||||
private final ScheduledExecutorService executor;
|
||||
private final ConfigInvocationHandler handler = new ConfigInvocationHandler(this);
|
||||
private final Properties properties = new Properties();
|
||||
private final Map<String, Object> configObjectCache = new HashMap<>();
|
||||
private final Map<String, String> pendingChanges = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
@@ -220,6 +222,20 @@ public class ConfigManager
|
||||
}
|
||||
}
|
||||
|
||||
// Attempts to fetch the config value from the cache if present. Otherwise it calls the get value function and caches the result
|
||||
Object getConfigObjectFromCacheOrElse(String groupName, String key, Function<String, Object> getValue)
|
||||
{
|
||||
String configItemKey = groupName + "." + key;
|
||||
return configObjectCache.computeIfAbsent(configItemKey, getValue);
|
||||
}
|
||||
|
||||
// Posts the configchanged event to the event bus and remove the changed key from the cache
|
||||
private void postConfigChanged(ConfigChanged configChanged)
|
||||
{
|
||||
configObjectCache.remove(configChanged.getGroup() + "." + configChanged.getKey());
|
||||
eventBus.post(configChanged);
|
||||
}
|
||||
|
||||
public <T> T getConfig(Class<T> clazz)
|
||||
{
|
||||
if (!Modifier.isPublic(clazz.getModifiers()))
|
||||
@@ -245,6 +261,11 @@ public class ConfigManager
|
||||
return properties.getProperty(groupName + "." + key);
|
||||
}
|
||||
|
||||
public String getConfiguration(String propertyKey)
|
||||
{
|
||||
return properties.getProperty(propertyKey);
|
||||
}
|
||||
|
||||
public <T> T getConfiguration(String groupName, String key, Class<T> clazz)
|
||||
{
|
||||
String value = getConfiguration(groupName, key);
|
||||
@@ -284,7 +305,7 @@ public class ConfigManager
|
||||
configChanged.setOldValue(oldValue);
|
||||
configChanged.setNewValue(value);
|
||||
|
||||
eventBus.post(configChanged);
|
||||
postConfigChanged(configChanged);
|
||||
}
|
||||
|
||||
public void setConfiguration(String groupName, String key, Object value)
|
||||
|
||||
Reference in New Issue
Block a user