-
Clone this project to your computer
-
Download and install JDK11
-
Verify that it's working fine with java 8:
- run
mvn clean install
from command line - run
mvn spring-boot:run --projects=service
- run
-
Open the swagger UI in the browser:
- On windows:
SET JAVA_HOME=<where you install jdk11
SET PATH=%JAVA_HOME%\bin;%PATH%
- On linux:
export JAVA_HOME=<where you install jdk11
export $PATH=$JAVA_HOME/bin:$PATH
Run java --version
and mvn --version
to see that your are using the
right JDK version and mvn is also using JDK 11.
- Verify that it's working fine with java 11:
- run
mvn clean install
from command line - run
mvn spring-boot:run --projects=service
- run
In the parent pom there is a property telling maven which Java version to use.
Note that since Java 9, the property has changed name to java.version
. So in the properties
section, add
<java.version>11</java.version>
or replace 11 with your version.
When migration to later Java versions, make sure all your maven plugins are up-to-date. Java 9, 10 and 11 support are typically supported only in later versions of the plugins.
Add the following propertis in the parent pom.
<maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
<maven.surfire.plugin.version>2.22.1</maven.surfire.plugin.version>
In the pluginManagement
section make sure the correct version of the compiler-plugin
and surefire-plugin
are used.
To prepare for modules, we start by reserving the module names, which is done by
setting Automatic-Module-Name
in the Manifest.mf file. This is most easily done by configuring the maven jar plugin.
In pluginManagement
in parent pom, add jar-plugin
with latest version. Feel free to make a property
of the version number.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.1.2</version>
</plugin>
In the plugins
section (not pluginManagement
) in the parent pom.xml
file,
jar plugin.
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Automatic-Module-Name>${module.name}</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
The jar-plugin
will now run for each module, including the parent, so all three
pom.xml
files needs to have it's own property named module.name
to be used by the plugin.
Module names should be com.jpmsworkshop.students.api
and
com.jpmsworkshop.students.service
for the two maven sub modules. and can be
com.jpmsworkshop.students.parent
for the parent pom.
Run mvn clean install
, extract the META-INF/MANIFEST.MF
file from the resulting
jar files (in the target folder in the modules) and verify that the property
Automatic-Module-Name
is set to your module name.
To modularize a maven module, we just add a module descriptor file module-info.java
in the src/main/java
folder for each module.
module com.jpmsworkshop.students.<modulename> {
}
Let the modulenames be api
and service
.
Modern IDEs and the compiler will change behaviour when this file is found, and you will at once get lots of compiler errors like:
Error:(3, 12) java: package java.sql is not visible
(package java.sql is declared in module java.sql, but module com.jpmsworkshop.students.service does not read it)
In the module descriptor file for the students service
module, we have to declare the the service
module requires
the java.sql
module.
Note: This has to be done in addition to the maven dependency in the pom.xml
file.
Go ahead and add dependencies to the module descriptors. Find the module name by:
- Check the jar files
META-INF/MANIFEST.mf
til for anAutomatic-Module-Name
entry or - Add it by using the name of the jar file without version number and extension (
json-path-2.4.0.jar
becomesjson.path
).
Example: requires json.path;
After writing a few requires statements, note that later versions of IntelliJ lets you
place the cursor on a "red" import statement and alt-enter
to add requires to the module descriptor.
You will probably see compilation errors like:
Error:(1, 1) java: package exists in another module: com.jpmsworkshop.students.api
Error:java: module spring.boot.autoconfigure reads package springfox.documentation.schema from both springfox.schema and springfox.core
In our code, Student (api module) and SpringBootJpmsApplication (service module) are both located in the same package,
com.jpmsworkshop.students
, which is not allowed. In code we control, we can fix this by moving the class
com.jpmsworkshop.students.Student
to a new package com.jpmsworkshop.students.api.Student
. Adding api
to the package
makes the package unique to that module. Remember to fix imports and module descriptor after refactoring
if the IDE does not do it for you.
For 3rd party libraries it's not easy to fix problems with same package in several modules. Let's look inside two jar files from springfox:
springfox-core-2.9.2.jar:
springfox.documentation.schema.Entry
springfox.documentation.schema.Enums
...
springfox-schema-2.9.2.jar:
springfox.documentation.schema.Annotations
springfox.documentation.schema.Maps
...
Both core
and schema
jar files contain classes in the springfox.documentation.schema
package. In these cases
our options are limited:
- Upgrade to a newer version may solve the problem
- Fix the problem in the library (if it's open source)
- Replace or remove the library if possible
- Re-package the jar files to one jar file and, potentially, add your own module descriptor
- Maven shade or assembly plugin may help
- Skip modularization (you can still compile and run with Java 11/12)
For the purpose of this workshop, just remove everything springfox related (dependencies, imports and code).
Run the application mvn spring-boot:run --projects=service
or start it from the IDE.
It runs fine, but without swagger-ui, you have to use the brower, or other tools
like curl or Postman to test the API. Open a browser and type http://localhost:8080/students
.
You should see the Json representing two student.
When running tests, we encounter a different problem. SpringBootTest
needs to do reflection on our
classes in the service module, but this module do not open to reflection (runtime access).
To resolve the problem, you have two options:
When migrating an application to the module system, it's easier to open the whole package to reflection. Note that this will not make the packages available compiletime.
open module com.jpmsworkshop.students.service{
...
}
If you know which packages that spring needs access to, you can open only them.
In our small, simple application we can open the com.jpmsworkshop.students
package.
module com.jpmsworkshop.students.service{
opens com.jpmsworkshop.students;
...
}
The test should now run green. If you run into an IllegalStateException due to the Spring configuration not being found, you will have to tell the SprintBootTest
which application class you're testing. See spring-projects/spring-boot#13581 (and related issues on Spring Framework and the JDK) for details.