Better caching proxy using CGlib
Allows internal calls to this.getBlah() to also be cached.
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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[] {});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user