JRebel and Multi-Module Projects in IntelliJ IDEA
When a developer using JRebel begins setting up multi-module projects in IntelliJ IDEA, the default settings create a situation where rebel.xml files override each other. This means that JRebel is unable to detect all the classes in the project (meaning = it cannot work its customary magic). In this post, we’ll do a technical deep-dive into what happens when the default setup is used, how it works and what to do to work around the defaults so that JRebel can detect all classes in the project and eliminate the need to restart for making changes to them.
Setting up Your IntelliJ IDEA Project
In Java apps, it is quite common that you group classes into reusable components, or libraries. The consequence is that you end up with the main module and a few dependencies opened in your project space. But did you actually think about how the application is deployed to the container? IntelliJ IDEA provides a number of options for deploying the application, but the flexibility comes at a pretty high price. Why don’t we look at the project structure and see what the caveats/conditions for deploying a multi-module application in IntelliJ.
To illustrate, I’ve created a simple web project: a ‘reporting-application’ which has two module dependencies - ‘business-components’ and ‘validators’.
To setup module dependencies, open module settings (Windows: Ctrl+Alt+Shift+S, Mac: Cmd+;), select ‘Modules’, select the desired module, navigate to ‘Dependencies’ tab, click “Add..” and select the ‘Module Dependency”.
It is very common to see a setup like this. All 3 modules might have dedicated repositories in the version control system, and ‘business-components’ and ‘validators’ are most likely to be used in some other projects besides our ‘reporting-application’. Since you might want to do some improvements to the re-used components, or maybe just debug your colleagues code, it is convenient to include ‘business-components’ and ‘validators’ as module dependencies and not as regular JARs.
Normally, you would expect a deployed artifact to look as follows. The web archive will include its own classes to WEB-INF/classes folder, and the dependencies will reside in WEB-INF/lib folder within the archive.
But this is only when you actually build the archive and deploy it. But if you work with the IDE, it might have slight differences in the details.
Let’s see how the application is deployed using Tomcat 7. Open the Run/Debug Configuration window and navigate to the ‘Deployments’ tab. We can see in the screenshot below that the artifact will be deployed in ‘exploded’ mode.
We can see now that the deployment artifact for the exploded web application will be created under ‘out/artifacts/reporting_application_war_exploded’ directory. But now the weird part begins.
The dependencies are not included into the deployment by default, so IntelliJ treats it as an error and suggests you to fix this problem - see the ‘Fix...’ button on the bottom-right part of the image? Well, sure, let’s fix that! You press the ‘Fix...’ button and select to add all the missing dependencies to the artifact - that’s the easiest fix you can do!
And then, voila! - the dependencies are added to the artifact, and IDE doesn’t complain any more:
As an attentive reader, you have probably noticed the potential problem with the setup we have described. All the classes are resources that reside in the modules as well as in the main project--these are copied to the same location in the deployment artifact.
When using JRebel, it is suggested to use the rebel.xml configuration file in order to map your deployed application to the active project. The configuration file is dedicated for each module and as in the example we have 3 modules - reporting-application, business-components and validators - we will end up with 3 configuration files, one for each module.
In IntelliJ, the rebel.xml configuration file can be generated with the “Generate rebel.xml” command selected from the context menu for a specific module. The most obvious location to save the file is the source folder, as when the compiler task is executed the file is copied to the target folder which is included into the CLASSPATH later. It goes pretty well until all the 3 modules are actually merged into one folder for the deployment artifact.
And here’s the problem - the rebel.xml of the main module (reporting-application) gets overridden by the rebel.xml file from one of the sub-modules. So when the application is deployed, it has a correct mapping only for one module in the project and not for all three, which is sad - JRebel will not be able to do its awesome redeploy-killing magic because it cannot find the correct location of the resources from the modules in which rebel.xml got overridden.
There are several solutions to this problem, each with its own conditions.
Multi-Module Project Options
Solution 1: Using Joint rebel.xml
The Good: It would be possible to add a feature to JRebel plugin so that it could generate a joint rebel.xml for all modules. In that case rebel.xml would be generated only for the “reporting-application” module in our example, which would include all the paths from the dependencies. Single rebel.xml for the application - problem solved.
The Bad: The problem with this approach is that we never know how will the project be actually deployed. And there’s also the disconnect in the project meta data from the deployment artifact. However, in the “Happy path” scenario, this solution is quite viable and has been mentioned before in the ZeroTurnaround support forums.
Solution 2: Using a Maven Project
The Good: As I’ve noticed, these problems don’t occur with multi-module Maven projects - rebel.xml never gets overridden and all the deployed modules are mapped correctly then.
The Bad: Maven isn’t the most popular thing with everyone.
Solution 3: Using Deployment Artifacts
The Good: Deployment artifacts are actually quite flexible in the setup. We can configure the deployment artifact to package the resources in a variety of ways. For instance we can tell the artifact to deploy some of the modules as exploded directories and for the others we can decide that those should be packaged. To solve our problem with overriding rebel.xml files, we can actually configure all the dependencies for the main module to be deployed as JARs. In this way, the dedicated rebel.xml files will be packaged to the root of each JAR and will not override each other during the deployment.
The Bad: The problem with this approach is that it is quite custom-built - you have to jump through some hoops in order to configure the artifact correctly.
The whole point of this post is the raise awareness about what is happening with your multi-module project before the application is deployed to the container. This should help you understand more about the environment you’re working with while using JRebel and IntelliJ. Please feel free to leave some feedback, and let us know below how it works out!