Investigate and document how we can hook into JRebel and call our own methods (for multiplexing reload message to all open UIs). Related to #7740.
Using org.zeroturnaround:jr-sdk and org.zeroturnaround:jr-util from https://repos.zeroturnaround.com/nexus/content/groups/zt-public/
class changes can be hooked into server-side by registering a listener as follows
this.reloadListener = new ClassEventListenerAdapter(0) {
@Override
public void onClassEvent(int eventType, Class<?> klass) throws Exception {
// Notify interested parties of reload
}
}
ReloaderFactory.getInstance().addClassReloadListener(WeakUtil.weak(this.reloadListener));
The above will register the listener via a weak reference so make sure it is stored in a field so that it doesn't get collected immediately and is tied to the lifetime of some object. Manual deregistration is also possible via ReloaderFactory.getInstance().removeClassReloadListener()
Note that usually many classes change in a short interval so may want to buffer the events if an action like browser reload is desired.
The code is safe to run when JRebel is not present as the factories in jr-sdk return NO-OP instances in that case.
@murkaje thank you so much for your comment.
To be able to get notification about class reload one should:
<repositories>
<repository>
<id>JRebel Directory</id>
<url>https://repos.zeroturnaround.com/nexus/content/groups/zt-public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>jr-sdk</artifactId>
<version>2020.1.1</version>
</dependency>
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>jr-utils</artifactId>
<version>2020.1.1</version>
<scope>provided</scope>
</dependency>
listener = new ClassEventListenerAdapter(0) {
@Override
public void onClassEvent(int eventType, Class<?> klass)
throws Exception {
}
};
ReloaderFactory.getInstance()
.addClassReloadListener(WeakUtil.weak(listener));
I've checked that this code allows to get notifications via onClassEvent method impl in the simplest project (at least).
Now here are my thoughts:
ClassEventListener programatically : you need a dedicated place which should add this listener. And this place is an alien in your project: it has no relation to the business logic.What I suggest:
ClassEventListener we will need some servlet listener . E.g. a ServletContextListener (like an existing ServletDeployer e.g.) or ServletContainerInitializer (like existing DevModeInitializer e.g.). This listener should be properly declared so that it's invoked by the servlet container at the proper time. The listener then will register a ClassEventListener and store it somehow so that it's not GCed.ClassEventListener implementation.@murkaje in Vaadin 15, ApplicationRouteRegistry.getInstance receives an argument of type VaadinContext instead of ServletContext as is in V14-, thus when running with V15, although single class recompile trigger the onClassEvent, below exception is logged. V14 apps don't trigger any exception, ofcourse.
2020-03-13 10:53:23 JRebel: ERROR Class 'com.vaadin.flow.server.startup.RouteRegistryInitializer' could not be processed by org.zeroturnaround.jrebel.vaadin.cbp.RouteRegistryInitializerCBP@org.eclipse.jetty.webapp.WebAppClassLoader@2be2e0ac: org.zeroturnaround.bundled.javassist.CannotCompileException: [source error] getInstance(javax.servlet.ServletContext) not found in com.vaadin.flow.server.startup.ApplicationRouteRegistry
at org.zeroturnaround.bundled.javassist.CtNewMethod.make(SourceFile:84)
at org.zeroturnaround.bundled.javassist.CtNewMethod.make(SourceFile:50)
at org.zeroturnaround.bundled.javassist.CtMethod.make(SourceFile:140)
at org.zeroturnaround.jrebel.vaadin.cbp.RouteRegistryInitializerCBP.process(RouteRegistryInitializerCBP.java:23)
at org.zeroturnaround.javarebel.integration.support.JavassistClassBytecodeProcessor.process(SourceFile:111)
at org.zeroturnaround.javarebel.integration.support.CacheAwareJavassistClassBytecodeProcessor.process(SourceFile:34)
at org.zeroturnaround.javarebel.integration.support.JavassistClassBytecodeProcessor.process(SourceFile:75)
at com.zeroturnaround.javarebel.zl.a(SourceFile:377)
at com.zeroturnaround.javarebel.zl.a(SourceFile:304)
at com.zeroturnaround.javarebel.SDKIntegrationImpl.runBytecodeProcessors(SourceFile:43)
at com.zeroturnaround.javarebel.wp.transform(SourceFile:134)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:42009)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:174)
at java.base/java.net.URLClassLoader.defineClass(URLClassLoader.java:545)
at java.base/java.net.URLClassLoader.access$100(URLClassLoader.java:83)
at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:453)
at java.base/java.net.URLClassLoader$1.run(URLClassLoader.java:447)
at java.base/java.security.AccessController.doPrivileged(Native Method)
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:446)
at org.eclipse.jetty.webapp.WebAppClassLoader.foundClass(WebAppClassLoader.java:670)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadAsResource(WebAppClassLoader.java:639)
at org.eclipse.jetty.webapp.WebAppClassLoader.loadClass(WebAppClassLoader.java:545)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:496)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:375)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.nextProviderClass(ServiceLoader.java:1204)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNextService(ServiceLoader.java:1215)
at java.base/java.util.ServiceLoader$LazyClassPathLookupIterator.hasNext(ServiceLoader.java:1259)
at java.base/java.util.ServiceLoader$2.hasNext(ServiceLoader.java:1294)
at java.base/java.util.ServiceLoader$3.hasNext(ServiceLoader.java:1379)
at org.eclipse.jetty.annotations.AnnotationConfiguration.getNonExcludedInitializers(AnnotationConfiguration.java:838)
at org.eclipse.jetty.annotations.AnnotationConfiguration.configure(AnnotationConfiguration.java:348)
at org.eclipse.jetty.webapp.WebAppContext.configure(WebAppContext.java:517)
at org.eclipse.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1454)
at org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:854)
at org.eclipse.jetty.servlet.ServletContextHandler.doStart(ServletContextHandler.java:278)
at org.eclipse.jetty.webapp.WebAppContext.doStart(WebAppContext.java:545)
at org.eclipse.jetty.maven.plugin.JettyWebAppContext.doStart(JettyWebAppContext.java:428)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:68)
at org.eclipse.jetty.maven.plugin.JettyRunMojo.restartWebApp(JettyRunMojo.java:526)
at org.eclipse.jetty.maven.plugin.JettyRunMojo$1.onPathWatchEvents(JettyRunMojo.java:378)
at org.eclipse.jetty.util.PathWatcher.notifyEvents(PathWatcher.java:1366)
at org.eclipse.jetty.util.PathWatcher.run(PathWatcher.java:1190)
at java.base/java.lang.Thread.run(Thread.java:844)
Caused by: compile error: getInstance(javax.servlet.ServletContext) not found in com.vaadin.flow.server.startup.ApplicationRouteRegistry
at org.zeroturnaround.bundled.javassist.compiler.TypeChecker.atMethodCallCore(SourceFile:777)
at org.zeroturnaround.bundled.javassist.compiler.TypeChecker.atCallExpr(SourceFile:723)
at org.zeroturnaround.bundled.javassist.compiler.JvstTypeChecker.atCallExpr(SourceFile:170)
at org.zeroturnaround.bundled.javassist.compiler.ast.CallExpr.accept(SourceFile:49)
at org.zeroturnaround.bundled.javassist.compiler.CodeGen.doTypeCheck(SourceFile:266)
at org.zeroturnaround.bundled.javassist.compiler.CodeGen.atDeclarator(SourceFile:773)
at org.zeroturnaround.bundled.javassist.compiler.ast.Declarator.accept(SourceFile:103)
at org.zeroturnaround.bundled.javassist.compiler.CodeGen.atStmnt(SourceFile:383)
at org.zeroturnaround.bundled.javassist.compiler.ast.Stmnt.accept(SourceFile:53)
at org.zeroturnaround.bundled.javassist.compiler.CodeGen.atStmnt(SourceFile:383)
at org.zeroturnaround.bundled.javassist.compiler.ast.Stmnt.accept(SourceFile:53)
at org.zeroturnaround.bundled.javassist.compiler.CodeGen.atMethodBody(SourceFile:321)
at org.zeroturnaround.bundled.javassist.compiler.CodeGen.atMethodDecl(SourceFile:303)
at org.zeroturnaround.bundled.javassist.compiler.ast.MethodDecl.accept(SourceFile:47)
at org.zeroturnaround.bundled.javassist.compiler.Javac.compileMethod(SourceFile:175)
at org.zeroturnaround.bundled.javassist.compiler.Javac.compile(SourceFile:102)
at org.zeroturnaround.bundled.javassist.CtNewMethod.make(SourceFile:79)
... 43 more
Most helpful comment
Using org.zeroturnaround:jr-sdk and org.zeroturnaround:jr-util from https://repos.zeroturnaround.com/nexus/content/groups/zt-public/
class changes can be hooked into server-side by registering a listener as follows
The above will register the listener via a weak reference so make sure it is stored in a field so that it doesn't get collected immediately and is tied to the lifetime of some object. Manual deregistration is also possible via
ReloaderFactory.getInstance().removeClassReloadListener()Note that usually many classes change in a short interval so may want to buffer the events if an action like browser reload is desired.
The code is safe to run when JRebel is not present as the factories in jr-sdk return NO-OP instances in that case.