Better caching proxy using CGlib

Allows internal calls to this.getBlah() to also be cached.
This commit is contained in:
Kris De Volder
2018-07-12 16:06:43 -07:00
parent 1c376bcbea
commit 4b31ac99ee
7 changed files with 111 additions and 62 deletions

View File

@@ -78,15 +78,14 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
@Override
public abstract boolean isSpringBootApp();
@Override
public JMXConnector getJmxConnector() throws MalformedURLException, IOException {
protected JMXConnector getJmxConnector() throws MalformedURLException, IOException {
JMXServiceURL serviceUrl = getJmxUrl();
JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
return jmxConnector;
}
@Override
public final List<String> getActiveProfiles() {
public List<String> getActiveProfiles() {
try {
String _env = getEnvironment();
if (_env != null) {
@@ -161,7 +160,7 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
}
@Override
public final LiveBeansModel getBeans() {
public LiveBeansModel getBeans() {
try {
String domain = getDomainForActuator();
String json = getBeansFromActuator(domain);
@@ -200,7 +199,7 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
* @return JMX MBean domain containing actuator information, or null if not resolved.
* @throws Exception when resolving domain from JMX
*/
protected final String getDomainForActuator() throws Exception {
protected String getDomainForActuator() throws Exception {
if (this.jmxMbeanActuatorDomain == null) {
JMXConnector jmxConnector = null;
try {
@@ -252,7 +251,7 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
}
}
protected final Object getActuatorDataFromAttribute(ObjectName objectName, String attribute) throws Exception {
protected Object getActuatorDataFromAttribute(ObjectName objectName, String attribute) throws Exception {
JMXConnector jmxConnector = null;
try {
if (objectName != null) {
@@ -273,7 +272,7 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
}
}
protected final Object getActuatorDataFromOperation(ObjectName objectName, String operation) throws Exception {
protected Object getActuatorDataFromOperation(ObjectName objectName, String operation) throws Exception {
JMXConnector jmxConnector = null;
try {
if (objectName != null) {

View File

@@ -1,45 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.boot.app.cli;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class NoArgumentsCacheHandler implements InvocationHandler {
private Cache<String, Object> cache;
private Object delegate;
public NoArgumentsCacheHandler(Object delegate, Duration cacheDuration) {
this.delegate = delegate;
this.cache = CacheBuilder.newBuilder().expireAfterWrite(cacheDuration.toMillis(), TimeUnit.MILLISECONDS).build();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (args==null || args.length==0) {
return invokeCached(proxy, method);
}
return method.invoke(proxy, args);
}
private Object invokeCached(Object proxy, Method method) throws Exception {
return cache.get(method.getName(), () -> {
return method.invoke(delegate, new Object[] {});
});
}
}

View File

@@ -12,7 +12,6 @@ package org.springframework.ide.vscode.commons.boot.app.cli;
import java.io.IOException;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.time.Duration;
import java.util.Map.Entry;
@@ -20,11 +19,13 @@ import java.util.Properties;
import javax.management.remote.JMXServiceURL;
import org.springframework.ide.vscode.commons.util.MemoizingProxy;
public class RemoteSpringBootApp extends AbstractSpringBootApp {
private String jmxUrl;
private RemoteSpringBootApp(String jmxUrl) {
protected RemoteSpringBootApp(String jmxUrl) {
this.jmxUrl = jmxUrl;
}
@@ -75,14 +76,13 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp {
}
}
} catch (IOException e) {
logger.equals(e);
logger.error("", e);
}
return "Unknown";
}
public static SpringBootApp create(String jmxUrl) {
RemoteSpringBootApp delegate = new RemoteSpringBootApp(jmxUrl);
return (SpringBootApp) Proxy.newProxyInstance(RemoteSpringBootApp.class.getClassLoader(), new Class[] {SpringBootApp.class}, new NoArgumentsCacheHandler(delegate, Duration.ofMillis(4900)));
return MemoizingProxy.create(RemoteSpringBootApp.class, Duration.ofMillis(4900), jmxUrl);
}
}

View File

@@ -10,15 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.boot.app.cli;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import javax.management.remote.JMXConnector;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
@@ -38,7 +34,6 @@ public interface SpringBootApp {
List<String> getActiveProfiles();
Optional<List<LiveConditional>> getLiveConditionals() throws Exception;
Properties getSystemProperties() throws Exception;
JMXConnector getJmxConnector() throws MalformedURLException, IOException;
default String getSystemProperty(String string) throws Exception {
Object r = getSystemProperties().get(string);

View File

@@ -49,6 +49,13 @@
<artifactId>reactor-core</artifactId>
<version>${reactor-version}</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>${cglib-version}</version>
</dependency>
<!-- HTM -> Markdown converter -->
<dependency>
<groupId>com.kotcrab.remark</groupId>

View File

@@ -0,0 +1,92 @@
/*******************************************************************************
* Copyright (c) 2018 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.util;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
* Utility to instrument a given class, memoizing all it's zero-argument method invocations.
* Note that the memoization behaves a little different from guava's Suppliers.memoize in that
* it caches exception results as well as regularly returned values.
*/
public class MemoizingProxy {
static class Result {
Throwable e;
Object v;
Result(Callable<?> computer) {
try {
v = computer.call();
} catch (Throwable e) {
this.e = e;
}
}
public Object get() throws Exception {
if (e!=null) {
throw ExceptionUtil.exception(e);
}
return v;
}
}
/**
* Memoizes all zero-argument public methods for a given duration.
*/
@SuppressWarnings("unchecked")
public static <T> T create(Class<T> klass, Duration duration, Object... args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(klass);
enhancer.setCallback(new MethodInterceptor() {
Cache<String, Result> cache = CacheBuilder.newBuilder()
.expireAfterWrite(duration.toMillis(), TimeUnit.MILLISECONDS)
.build();
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
if (Modifier.isPublic(method.getModifiers()) && (args==null || args.length==0)) {
synchronized (cache) {
String mname = method.getName();
Result r = cache.get(mname, () -> new Result(() -> {
try {
return proxy.invokeSuper(obj, args);
} catch (Throwable e) {
throw ExceptionUtil.exception(e);
}
}));
return r.get();
}
} else {
return proxy.invokeSuper(obj, args);
}
}
});
Class<?>[] argumentTypes = new Class<?>[args.length];
for (int i = 0; i < argumentTypes.length; i++) {
argumentTypes[i] = args[i].getClass();
}
return (T) enhancer.create(argumentTypes, args);
}
}

View File

@@ -80,6 +80,7 @@
<jackson-2-version>2.5.0</jackson-2-version>
<jersey-2-version>2.10</jersey-2-version>
<lsp4j-version>0.4.0-SNAPSHOT</lsp4j-version>
<cglib-version>3.2.7</cglib-version>
<!-- NOTE: Reactor version must match version used by the CF client -->
<cloudfoundry-client-version>3.8.0.RELEASE</cloudfoundry-client-version>
<reactor-version>3.1.5.RELEASE</reactor-version>