Let's say I have a very simple maven project ProjA
which has no dependencies itself. This project ProjA
has classes X
and Y
as follows:
class X
package proja;
public class X {
static {
System.out.println("X loaded");
}
public void something() {
System.out.println("X hello world");
}
}
class Y
package proja;
public class Y {
static {
System.out.println("Y loaded");
}
public void something() {
System.out.println("Y hello world");
}
}
ProjA .pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tomac</groupId>
<artifactId>ProjA</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
Next I have a second maven project ProjB
which has project ProjA
as dependency.
I project ProjB
I have a class Run
as follows:
class Run
package projb;
import proja.X;
import proja.Y;
import java.util.Scanner;
public class Run {
public void run() {
Scanner scanner = new Scanner(System.in);
while (true) {
String msg = scanner.nextLine();
switch (msg) {
case "x":
new X().something();
break;
case "y":
new Y().something();
break;
}
}
}
public static void main(String[] args) {
new Run().run();
}
}
ProjB .pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tomac</groupId>
<artifactId>ProjB</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ProjA</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
</project>
I install project ProjA
using mvn install
and then compile project ProjB
using mvn compile
Now, I run main method from class Run
using:
mvn exec:java -Dexec.mainClass="projb.Run"
Then I type x
<ENTER> and got output:
X loaded
X hello world
After that I type y
<ENTER> and got output:
Y loaded
Y hello world
Now, consider specific ordering of actions:
Start class
Run
(loads classRun
and waits onScanner.nextLine()
)Type
x
<ENTER> (loads classX
and outputsX loaded
X hello world
)Now while
Run
is running, edit something in classY
, for example body ofsomething()
method to:System.out.println("Y hello world new");
Re-install project
ProjA
usingmvn install
(which causes compilation of classY
packaging into target jar and installing packaged jar into local .m2 repository)Go back to running app and type
y
<ENTER>Now loading of class
Y
causes:
Stack trace:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NoClassDefFoundError: proja/Y
at projb.Run.run(Run.java:18)
at projb.Run.main(Run.java:25)
... 6 more
Caused by: java.lang.ClassNotFoundException: proja.Y
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 8 more
Note that this class-loading error is only reproducible if some yet unloaded class in dependency project is changed, deployed and then class from dependant project (which already has at least one class loaded from dependency project) tries to load this newly changed class.
Project and class structure is just extracted as concept from bigger system which has many more classes with main()
methods. And many of them run on same machines in parallel in separate JVMs.
Question: How can I prevent this from happening?
Note, I don't need any kind of dynamic class reloading at runtime.
I know that changes in incompatible ways (example: add a parameter in method something(String str)
) would break no matter what.
One workaround would be to restart everything in project ProjB
when something in project ProjA
is changed and deployed. But some of processes have relatively costly initial operations on startup so it's not an option.
Another workaround would be to somehow force (using e.g. Reflections Library) class loading of all classes from project ProjA
on startup of each process from project ProjB
. But this is overkill for me and could cause a lot of unnecessary class loads and potentialy lead to OutOfMemoryException
.
Yet another option wold be to merge all projects into one big project, but then all point of separating different stuff into different projects would be lost.
How can I better organize my develop->build->run/restart flow so that when some process is started and in some point in future it loads classes, so that those loaded classes definitions are equal to point in time of codebase builded before time of this process's startup?
Edit
Add pom files of ProjA
and ProjB