diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootAppCache.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootAppCache.java index 37d6e1046..8d430d31d 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootAppCache.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/LocalSpringBootAppCache.java @@ -27,6 +27,7 @@ public class LocalSpringBootAppCache { private static final Duration EXPIRE_AFTER = Duration.ofMillis(500); //Limits rate at which we refresh list of apps private long nextRefreshAfter = Long.MIN_VALUE; + private final MemoizingProxy.Builder memoizingProxyBuilder = MemoizingProxy.builder(LocalSpringBootApp.class, Duration.ofMillis(4500), VirtualMachineDescriptor.class); private ImmutableMap apps = ImmutableMap.of(); @@ -46,7 +47,7 @@ public class LocalSpringBootAppCache { newAppsBuilder.put(vm, existingApp); } else { try { - LocalSpringBootApp localApp = MemoizingProxy.create(LocalSpringBootApp.class, Duration.ofMillis(4500), new Class[] {VirtualMachineDescriptor.class}, vm); + LocalSpringBootApp localApp = memoizingProxyBuilder.newInstance(vm); newAppsBuilder.put(vm, localApp); } catch (Exception e) { //Ignore problems attaching to a VM. We will try again on next polling loop, if vm still exists. @@ -63,4 +64,5 @@ public class LocalSpringBootAppCache { apps = newApps; nextRefreshAfter = System.currentTimeMillis() + EXPIRE_AFTER.toMillis(); } + } diff --git a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/RemoteSpringBootApp.java b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/RemoteSpringBootApp.java index 822b3fcae..af06e67c3 100644 --- a/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/RemoteSpringBootApp.java +++ b/headless-services/commons/commons-boot-app-cli/src/main/java/org/springframework/ide/vscode/commons/boot/app/cli/RemoteSpringBootApp.java @@ -19,6 +19,10 @@ import java.util.Properties; import org.springframework.ide.vscode.commons.util.MemoizingProxy; public class RemoteSpringBootApp extends AbstractSpringBootApp { + + private static MemoizingProxy.Builder memoizingProxyBuilder = MemoizingProxy.builder(RemoteSpringBootApp.class, Duration.ofMillis(4900), + String.class, String.class, String.class, String.class, boolean.class + ); private final String jmxUrl; private final String host; @@ -27,8 +31,7 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp { private boolean keepChecking; public static SpringBootApp create(String jmxUrl, String host, String port, String urlScheme, boolean keepChecking) { - return MemoizingProxy.create(RemoteSpringBootApp.class, Duration.ofMillis(4900), new Class[] {String.class, String.class, String.class, String.class, boolean.class}, - jmxUrl, host, port, urlScheme, keepChecking); + return memoizingProxyBuilder.newInstance(jmxUrl, host, port, urlScheme, keepChecking); } protected RemoteSpringBootApp(String jmxUrl, String host, String port, String urlScheme, boolean keepChecking) { diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/MemoizingProxy.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/MemoizingProxy.java index b099fe92d..3bbb38839 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/MemoizingProxy.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/MemoizingProxy.java @@ -154,85 +154,89 @@ public class MemoizingProxy { } } - public static Builder builder(Class klass, Duration duration, Class... argTypes) throws Exception { - if (klass.isInterface()) { - Assert.isLegal(argTypes.length==0, "Should not provide constructor argument types for interface type "+klass.getSimpleName()); - DynamicType.Builder builder = new ByteBuddy() - .subclass(klass, ConstructorStrategy.Default.NO_CONSTRUCTORS) - .defineField(F_DURATION, long.class, Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC).value(duration.toMillis()) - .defineField(F_CACHE, Cache.class, Opcodes.ACC_PRIVATE) - .defineField(F_DELEGATE, klass, Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL) - .method(ElementMatchers.isAbstract()).intercept(MethodDelegation.to(DelegateMethodInterceptor.class)) - .defineConstructor(Visibility.PUBLIC).withParameters(klass).intercept( - MethodCall.invoke(Object.class.getConstructor()) - .andThen(FieldAccessor.ofField(F_DELEGATE).setsArgumentAt(0)) - .andThen(METHOD_DELEGATION.to(ClassConstructorInterceptor.class)) - ); - - Constructor constructor = builder.make().load(klass.getClassLoader()).getLoaded().getConstructor(klass); - return new Builder() { - - @Override - public T newInstance(Object... args) { - throw new UnsupportedOperationException( - "Can't use 'newInstance' because '"+klass.getSimpleName()+" is an interface.\n"+ - "Use 'delegateTo' instead." - ); - } - - @Override - public T delegateTo(T delegate) { - try { - return constructor.newInstance(delegate); - } catch (Exception e) { - throw ExceptionUtil.unchecked(e); - } - } - - }; - } else { - DynamicType.Builder builder = new ByteBuddy() - .subclass(klass, ConstructorStrategy.Default.NO_CONSTRUCTORS) - .defineField(F_DURATION, long.class, Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC).value(duration.toMillis()) - .defineField(F_CACHE, Cache.class, Opcodes.ACC_PRIVATE) - .method(CACHABLE_METHODS).intercept(MethodDelegation.to(SuperMethodInterceptor.class)) - .defineConstructor(Visibility.PUBLIC).withParameters(argTypes).intercept( - MethodCall.invoke(klass.getConstructor(argTypes)).withAllArguments() - .andThen(METHOD_DELEGATION.to(ClassConstructorInterceptor.class)) - ); - - Constructor constructor = builder.make().load(klass.getClassLoader()).getLoaded().getConstructor(argTypes); - return new Builder() { - @Override - public T newInstance(Object... args) { - try { - return constructor.newInstance(args); - } catch (Exception e) { - throw ExceptionUtil.unchecked(e); - } - } - - @Override - public T delegateTo(T delegate) { - throw new UnsupportedOperationException( - "Can't use 'delegateTo' because '"+klass.getSimpleName()+" is not an interface.\n"+ - "Use 'newInstance' instead." - ); - } - }; - } - } - - /** - * Deprecated: use the 'builder' method instead - */ - @Deprecated - public static T create(Class klass, Duration duration, Class[] argTypes, Object... args) { + public static Builder builder(Class klass, Duration duration, Class... argTypes) { try { - return builder(klass, duration, argTypes).newInstance(args); + if (klass.isInterface()) { + Assert.isLegal(argTypes.length==0, "Should not provide constructor argument types for interface type "+klass.getSimpleName()); + DynamicType.Builder builder = new ByteBuddy() + .subclass(klass, ConstructorStrategy.Default.NO_CONSTRUCTORS) + .defineField(F_DURATION, long.class, Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC).value(duration.toMillis()) + .defineField(F_CACHE, Cache.class, Opcodes.ACC_PRIVATE) + .defineField(F_DELEGATE, klass, Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL) + .method(ElementMatchers.isAbstract()).intercept(MethodDelegation.to(DelegateMethodInterceptor.class)) + .defineConstructor(Visibility.PUBLIC).withParameters(klass).intercept( + MethodCall.invoke(Object.class.getConstructor()) + .andThen(FieldAccessor.ofField(F_DELEGATE).setsArgumentAt(0)) + .andThen(METHOD_DELEGATION.to(ClassConstructorInterceptor.class)) + ); + + Constructor constructor = builder.make().load(klass.getClassLoader()).getLoaded().getConstructor(klass); + return new Builder() { + + @Override + public T newInstance(Object... args) { + throw new UnsupportedOperationException( + "Can't use 'newInstance' because '"+klass.getSimpleName()+" is an interface.\n"+ + "Use 'delegateTo' instead." + ); + } + + @Override + public T delegateTo(T delegate) { + try { + return constructor.newInstance(delegate); + } catch (Exception e) { + throw ExceptionUtil.unchecked(e); + } + } + + }; + } else { + DynamicType.Builder builder = new ByteBuddy() + .subclass(klass, ConstructorStrategy.Default.NO_CONSTRUCTORS) + .defineField(F_DURATION, long.class, Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC).value(duration.toMillis()) + .defineField(F_CACHE, Cache.class, Opcodes.ACC_PRIVATE) + .method(CACHABLE_METHODS).intercept(MethodDelegation.to(SuperMethodInterceptor.class)) + .defineConstructor(Visibility.PUBLIC).withParameters(argTypes).intercept( + MethodCall.invoke(klass.getConstructor(argTypes)).withAllArguments() + .andThen(METHOD_DELEGATION.to(ClassConstructorInterceptor.class)) + ); + + Constructor constructor = builder.make().load(klass.getClassLoader()).getLoaded().getConstructor(argTypes); + return new Builder() { + @Override + public T newInstance(Object... args) { + try { + return constructor.newInstance(args); + } catch (Exception e) { + throw ExceptionUtil.unchecked(e); + } + } + + @Override + public T delegateTo(T delegate) { + throw new UnsupportedOperationException( + "Can't use 'delegateTo' because '"+klass.getSimpleName()+" is not an interface.\n"+ + "Use 'newInstance' instead." + ); + } + }; + } } catch (Exception e) { throw ExceptionUtil.unchecked(e); } } + +// /** +// * Deprecated: use the 'builder' method instead +// */ +// @Deprecated +// public static T create(Class klass, Duration duration, Class[] argTypes, Object... args) { +// try { +// return builder(klass, duration, argTypes).newInstance(args); +// } catch (Exception e) { +// throw ExceptionUtil.unchecked(e); +// } +// } } diff --git a/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/MemoizingProxyClassTest.java b/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/MemoizingProxyClassTest.java index b694abf4b..d43095dd7 100644 --- a/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/MemoizingProxyClassTest.java +++ b/headless-services/commons/commons-util/src/test/java/org/springframework/ide/vscode/commons/util/MemoizingProxyClassTest.java @@ -64,8 +64,8 @@ public class MemoizingProxyClassTest { } } - private TestSubject defaultTestSubject() { - return MemoizingProxy.create(TestSubject.class, Duration.ofMinutes(1), CONSTRUCTOR_ARG_TYPES, "Johny", 45); + private TestSubject defaultTestSubject() throws Exception { + return MemoizingProxy.builder(TestSubject.class, Duration.ofMinutes(1), CONSTRUCTOR_ARG_TYPES).newInstance("Johny", 45); } private void assertInvocations(String...expectedInvocations) { @@ -148,7 +148,7 @@ public class MemoizingProxyClassTest { } @Test public void cacheExpires() throws Exception { - this.proxy = MemoizingProxy.create(TestSubject.class, Duration.ofMillis(10), CONSTRUCTOR_ARG_TYPES, "Johny", 45); + this.proxy = MemoizingProxy.builder(TestSubject.class, Duration.ofMillis(10), CONSTRUCTOR_ARG_TYPES).newInstance("Johny", 45); assertEquals("Johny", proxy.getMyName()); assertInvocations("getMyName", "getName"); sleep(20); @@ -157,15 +157,10 @@ public class MemoizingProxyClassTest { } @Test public void proxyClassReused() throws Exception { - //The old (deprecated) way creates a new proxy class for every proxy: - TestSubject proxy1 = MemoizingProxy.create(TestSubject.class, Duration.ofMinutes(1), CONSTRUCTOR_ARG_TYPES, "Johny", 45); - TestSubject proxy2 = MemoizingProxy.create(TestSubject.class, Duration.ofMinutes(1), CONSTRUCTOR_ARG_TYPES, "Johny", 45); - assertFalse(proxy1.getClass().equals(proxy2.getClass())); - //Using the new 'builder' api allows re-using the same class (if used properly) Builder builder = MemoizingProxy.builder(TestSubject.class, Duration.ofMinutes(1), CONSTRUCTOR_ARG_TYPES); - proxy1 = builder.newInstance("Freddy", 12); - proxy2 = builder.newInstance("Johny", 45); + TestSubject proxy1 = builder.newInstance("Freddy", 12); + TestSubject proxy2 = builder.newInstance("Johny", 45); assertFalse(proxy1.equals(proxy2)); // different instance... assertEquals(proxy1.getClass(), proxy2.getClass()); //same class } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java index 6a2293f3f..75ae29d34 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringLiveHoverWatchdog.java @@ -38,9 +38,6 @@ import org.springframework.ide.vscode.commons.protocol.HighlightParams; import org.springframework.ide.vscode.commons.util.MemoizingProxy; import org.springframework.ide.vscode.commons.util.text.TextDocument; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; - /** * @author Martin Lippert */ @@ -60,6 +57,7 @@ public class SpringLiveHoverWatchdog { private ScheduledThreadPoolExecutor timer; private JavaProjectFinder projectFinder; private final Map> watchedDocs; + public SpringLiveHoverWatchdog( @@ -208,18 +206,17 @@ public class SpringLiveHoverWatchdog { } } + private final MemoizingProxy.Builder memoizingProxyBuilder = MemoizingProxy.builder(SpringBootApp.class, Duration.ofMillis(20000)); + private Collection createAppCaches(Collection runningBootApps) { return runningBootApps.stream().map(app -> { - MethodInterceptor handler = new MemoizingProxy.MemoizingProxyHandler(app, Duration.ofMillis(20000)); - SpringBootApp proxied = (SpringBootApp) Enhancer.create(SpringBootApp.class, handler); - + SpringBootApp proxied = memoizingProxyBuilder.delegateTo(app); try { proxied.getProcessName(); proxied.getProcessID(); } catch (Exception e) { } - return proxied; }).filter(app -> app != null).collect(Collectors.toList()); }