Maven – How to create a multi module project
In this tutorial, we will show you how to use Maven to manage a Multi-module project containing four modules :
- Password module – Interface only.
- Password md5 module – Password module implementation, MD5 password hashing.
- Password sha module – Password module implementation, SHA password hashing.
- Web module – A simple MVC web app to hash an input with either MD5 or SHA algorithm.
The module dependency.
$ password
$ password <-- password-md5
$ password <-- password-sha
$ web <-- (password-md5 | password-sha) <-- password
Some commands to a build a multi-module project, for examples :
$ mvn -pl password compile # compile password module only
$ mvn -pl password-sha compile # compile password-sha module, also dependency - password
$ mvn -pl web compile # compile web module only
$ mvn -am -pl web compile # compile web module, also dependency - password-sha or password-md5, password
$ mvn -pl web jetty:run # run web module with Jetty
$ mvn compile # compile everything
Technologies used :
- Maven 3.5.3
- JDK 8
- Spring 5.1.0.RELEASE
- thymeleaf 3.0.10.RELEASE
1. Directory Structure
In a multi-module project layout, a parent project contains a parent pom.xml
and each sub modules (sub projects) also containing own pom.xml
2. Maven POM
Let review the Maven multi-module POM files.
2.1 A Parent POM file, packaging is 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>
<!-- parent pom -->
<groupId>com.mkyong.multi</groupId>
<artifactId>java-multi-modules</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<!-- sub modules -->
<modules>
<module>web</module>
<module>password</module>
<module>password-sha</module>
<module>password-md5</module>
</modules>
</project>
2.2 In password module.
<?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">
<!-- parent pom -->
<parent>
<artifactId>java-multi-modules</artifactId>
<groupId>com.mkyong.multi</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- password info -->
<groupId>com.mkyong.password</groupId>
<artifactId>password</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
</project>
2.3 In password-md5 module.
<?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">
<parent>
<artifactId>java-multi-modules</artifactId>
<groupId>com.mkyong.multi</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- password-md5 info -->
<groupId>com.mkyong.password</groupId>
<artifactId>password-md5</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
2.4 In password-sha module.
<?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">
<parent>
<artifactId>java-multi-modules</artifactId>
<groupId>com.mkyong.multi</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- password-sha info -->
<groupId>com.mkyong.password</groupId>
<artifactId>password-sha</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
2.4 In web module.
<?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">
<!-- this is a parent pom -->
<parent>
<groupId>com.mkyong.multi</groupId>
<artifactId>java-multi-modules</artifactId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- web project info -->
<groupId>com.mkyong</groupId>
<artifactId>web</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<dependencies>
<!-- md5 or sha hashing -->
<!-- md5 hash
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password-md5</artifactId>
<version>1.0</version>
</dependency>
-->
<!-- sha -->
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password-sha</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
3. Parent Project
3.1 Parent pom, those properties and dependencies (JDK 8, JUnit 5, Spring 5) are shared by all sub modules.
<?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.mkyong.multi</groupId>
<artifactId>java-multi-modules</artifactId>
<packaging>pom</packaging>
<version>1.0</version>
<properties>
<!-- https://maven.apache.org/general.html#encoding-warning -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>5.3.1</junit.version>
<spring.version>5.1.0.RELEASE</spring.version>
</properties>
<modules>
<module>web</module>
<module>password</module>
<module>password-sha</module>
<module>password-md5</module>
</modules>
<dependencies>
<!-- Spring DI for all modules -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- unit test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.1.1</version>
</plugin>
</plugins>
</build>
</project>
4. Password Module
4.1 A module containing only one interface.
4.2 POM file.
<?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">
<parent>
<artifactId>java-multi-modules</artifactId>
<groupId>com.mkyong.multi</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mkyong.password</groupId>
<artifactId>password</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
</project>
4.3 An interface.
package com.mkyong.password;
public interface PasswordService {
String hash(String input);
String algorithm();
}
5. Password-md5 Module
5.1 Password module implementation.
5.2 POM file.
<?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">
<parent>
<artifactId>java-multi-modules</artifactId>
<groupId>com.mkyong.multi</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- password-md5 info -->
<groupId>com.mkyong.password</groupId>
<artifactId>password-md5</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
5.3 MD5 hashing.
package com.mkyong.password;
import org.springframework.stereotype.Service;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
@Service
public class PasswordServiceImpl implements PasswordService {
@Override
public String hash(String input) {
return md5(input);
}
@Override
public String algorithm() {
return "md5";
}
private String md5(String input) {
StringBuilder result = new StringBuilder();
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
byte[] hashInBytes = md.digest(input.getBytes(StandardCharsets.UTF_8));
for (byte b : hashInBytes) {
result.append(String.format("%02x", b));
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException(e);
}
return result.toString();
}
}
5.4 Unit test.
package com.mkyong.password;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPasswordService {
PasswordService passwordService;
@BeforeEach
void init() {
passwordService = new PasswordServiceImpl();
}
@DisplayName("md5 -> hex")
@ParameterizedTest
@CsvSource({
"123456, e10adc3949ba59abbe56e057f20f883e",
"hello world, 5eb63bbbe01eeed093cb22bb8f5acdc3"
})
void testMd5hex(String input, String expected) {
assertEquals(expected, passwordService.hash(input));
}
}
6. Password-sha Module
6.1 Password module implementation.
6.2 POM file.
<?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">
<parent>
<artifactId>java-multi-modules</artifactId>
<groupId>com.mkyong.multi</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.mkyong.password</groupId>
<artifactId>password-sha</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<properties>
<commos.codec.version>1.11</commos.codec.version>
</properties>
<dependencies>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commos.codec.version}</version>
</dependency>
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</project>
6.3 SHA hashing with the Apache common codec library
package com.mkyong.password;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Service;
@Service
public class PasswordServiceImpl implements PasswordService {
@Override
public String hash(String input) {
return DigestUtils.sha256Hex(input);
}
@Override
public String algorithm() {
return "sha256";
}
}
6.4 Unit test.
package com.mkyong.password;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class TestPasswordService {
PasswordService passwordService;
@BeforeEach
void init() {
passwordService = new PasswordServiceImpl();
}
@DisplayName("sha256 -> hex")
@ParameterizedTest
@CsvSource({
"123456, 8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92",
"hello world, b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
})
void testSha256hex(String input, String expected) {
assertEquals(expected, passwordService.hash(input));
}
}
7. Web Module
7.1 A Spring MCV + thymeleaf web application, hash an input with a md5 or sha algorithm.
7.2 POM file.
<?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">
<!-- this is a parent pom -->
<parent>
<groupId>com.mkyong.multi</groupId>
<artifactId>java-multi-modules</artifactId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<!-- web project info -->
<groupId>com.mkyong</groupId>
<artifactId>web</artifactId>
<version>1.0</version>
<packaging>war</packaging>
<properties>
<thymeleaf.version>3.0.10.RELEASE</thymeleaf.version>
</properties>
<dependencies>
<!-- md5 hash
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password-md5</artifactId>
<version>1.0</version>
</dependency>
-->
<!-- sha -->
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password-sha</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- logging , spring 5 no more bridge, thanks spring-jcl -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<!-- need this for unit test -->
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-library</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<!-- servlet api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- thymeleaf view -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>${thymeleaf.version}</version>
</dependency>
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>${thymeleaf.version}</version>
</dependency>
</dependencies>
<build>
<finalName>java-web-project</finalName>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.12.v20180830</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
</project>
7.3 Web module dependency
$ mvn -pl web dependency:tree
[INFO] com.mkyong:web:war:1.0
[INFO] +- com.mkyong.password:password-sha:jar:1.0:compile
[INFO] | +- commons-codec:commons-codec:jar:1.11:compile
[INFO] | \- com.mkyong.password:password:jar:1.0:compile
[INFO] +- org.springframework:spring-webmvc:jar:5.1.0.RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:5.1.0.RELEASE:compile
[INFO] | +- org.springframework:spring-beans:jar:5.1.0.RELEASE:compile
[INFO] | +- org.springframework:spring-core:jar:5.1.0.RELEASE:compile
[INFO] | | \- org.springframework:spring-jcl:jar:5.1.0.RELEASE:compile
[INFO] | +- org.springframework:spring-expression:jar:5.1.0.RELEASE:compile
[INFO] | \- org.springframework:spring-web:jar:5.1.0.RELEASE:compile
[INFO] +- org.springframework:spring-test:jar:5.1.0.RELEASE:compile
[INFO] +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | +- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | \- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] | \- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- javax.servlet:javax.servlet-api:jar:3.1.0:provided
[INFO] +- org.thymeleaf:thymeleaf:jar:3.0.10.RELEASE:compile
[INFO] | +- ognl:ognl:jar:3.1.12:compile
[INFO] | | \- org.javassist:javassist:jar:3.20.0-GA:compile
[INFO] | +- org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
[INFO] | \- org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
[INFO] +- org.thymeleaf:thymeleaf-spring5:jar:3.0.10.RELEASE:compile
[INFO] +- org.springframework:spring-context:jar:5.1.0.RELEASE:compile
[INFO] +- org.junit.jupiter:junit-jupiter-engine:jar:5.3.1:test
[INFO] | +- org.apiguardian:apiguardian-api:jar:1.0.0:test
[INFO] | +- org.junit.platform:junit-platform-engine:jar:1.3.1:test
[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.3.1:test
[INFO] | | \- org.opentest4j:opentest4j:jar:1.1.1:test
[INFO] | \- org.junit.jupiter:junit-jupiter-api:jar:5.3.1:test
[INFO] \- org.junit.jupiter:junit-jupiter-params:jar:5.3.1:test
[INFO] ------------------------------------------------------------------------
7.4 Spring stuff, integrate thymeleaf and configuration.
package com.mkyong.web.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
@EnableWebMvc
@Configuration
@ComponentScan({"com.mkyong"})
public class SpringConfig implements WebMvcConfigurer {
@Autowired
private ApplicationContext applicationContext;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode(TemplateMode.HTML);
templateResolver.setCacheable(true);
return templateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
templateEngine.setEnableSpringELCompiler(true);
return templateEngine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
return viewResolver;
}
}
package com.mkyong.web;
import com.mkyong.web.config.SpringConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{SpringConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
package com.mkyong.web.controller;
import com.mkyong.password.PasswordService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class WelcomeController {
private final Logger logger = LoggerFactory.getLogger(WelcomeController.class);
@Autowired
private PasswordService passwordService;
@GetMapping("/")
public String welcome(@RequestParam(name = "query",
required = false, defaultValue = "123456") String query, Model model) {
logger.debug("Welcome to mkyong.com... Query : {}", query);
model.addAttribute("query", query);
model.addAttribute("hash", passwordService.hash(query));
model.addAttribute("algorithm", passwordService.algorithm());
return "index";
}
}
7.5 Unit Test for Spring MVC.
package com.mkyong.web;
import com.mkyong.web.config.SpringConfig;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringJUnitWebConfig(SpringConfig.class)
@DisplayName("Test Spring MVC default view")
public class TestWelcome {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webAppContext;
@BeforeEach
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();
}
@Test
public void testDefault() throws Exception {
this.mockMvc.perform(
get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"))
.andExpect(model().attribute("query", "123456"));
//.andExpect(model().attribute("sha256", "8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92"))
//.andExpect(model().attribute("md5", "e10adc3949ba59abbe56e057f20f883e"));
}
}
7.6 Thymeleaf view.
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<h1 th:text="'Input : ' + ${query}"/>
<h1 th:text="'Algorithm : ' + ${algorithm}"/>
<h3 th:text="${hash}" />
</body>
</html>
Done.
8. Demo
8.1 In parent project, uses the standard mvn compile
to compile all the modules, Maven will decide the build order.
$ mvn compile
[INFO] Reactor Summary:
[INFO]
[INFO] java-multi-modules 1.0 ............................. SUCCESS [ 0.007 s]
[INFO] password ........................................... SUCCESS [ 0.539 s]
[INFO] password-sha ....................................... SUCCESS [ 0.038 s]
[INFO] web ................................................ SUCCESS [ 0.080 s]
[INFO] password-md5 1.0 ................................... SUCCESS [ 0.035 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.822 s
[INFO] Finished at: 2018-10-23T15:32:21+08:00
[INFO] ------------------------------------------------------------------------
8.2 Install all the modules into the local repository.
$ mvn install
8.3 Compiles the web module only (Maven will find the password module dependency from the local repository, that’s why we need the mvn install
$ mvn -pl web compile
8.4 Alternatively, add -am
to compile the web module and also its dependency modules (password-sha and password).
$ mvn -am -pl web compile
[INFO] Scanning for projects...
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] java-multi-modules [pom]
[INFO] password [jar]
[INFO] password-sha [jar]
[INFO] web [war]
......
[INFO] Reactor Summary:
[INFO]
[INFO] java-multi-modules 1.0 ............................. SUCCESS [ 0.009 s]
[INFO] password ........................................... SUCCESS [ 0.534 s]
[INFO] password-sha ....................................... SUCCESS [ 0.036 s]
[INFO] web 1.0 ............................................ SUCCESS [ 0.077 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.780 s
[INFO] Finished at: 2018-10-23T15:36:16+08:00
[INFO] ------------------------------------------------------------------------
8.5 Test the web module with mvn -pl web jetty:run
command.
$ mvn install
$ mvn -pl web jetty:run
//...
[INFO] 1 Spring WebApplicationInitializers detected on classpath
[INFO] DefaultSessionIdManager workerName=node0
[INFO] No SessionScavenger set, using defaults
[INFO] node0 Scavenging every 600000ms
[INFO] Started ServerConnector@40a8a26f{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
[INFO] Started @6398ms
[INFO] Started Jetty Server
8.6 http://localhost:8080
8.7 http://localhost:8080/?query=mkyong
8.8 Update to md5 algorithm. Restart the Jetty server.
<!-- md5 hash -->
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password-md5</artifactId>
<version>1.0</version>
</dependency>
<!-- sha
<dependency>
<groupId>com.mkyong.password</groupId>
<artifactId>password-sha</artifactId>
<version>1.0</version>
</dependency>
-->
8.9 http://localhost:8080/?query=mkyong
Download Source Code
$ cd java-multi-modules
$ mvn install
$ mvn -pl web jetty:run
Great efforts! But you didn’t mention about the best practices for releasing such kind of projects and dealing with the repository. as the whole project with its submodules are located in single repository, how do you mange releases of different sub module versions (e.g. a bug in password-sha module will result to password-sha:2.0 module). Considering big project having concurrent sprints and different teams involved. In another words, how do you commit that to git repository and when do you mark it as tagged?
The scenario which you are discussing is closely related to microservices. But this example’s aim is just to showcase multi-module maven projects.
I think you don’t need to declare explicitly the password module as a dependency in the password-sha and the password-md5 pom files.
If you declare modules in the parent pom respectively (from top to bottom), maven provides upper modules for bottom side modules.
Also I think you should declare the web module as a last module in the parent pom module no the first.
hi, I have issues with the install, I have this error:
——————————————————————————-
Test set: com.mkyong.web.TestWelcome
——————————————————————————-
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 6.754 s <<< FAILURE! – in com.mkyong.web.TestWelcome
testDefault Time elapsed: 4.125 s <<< ERROR!
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "ServletContext resource [/WEB-INF/views/index.html]")
at com.mkyong.web.TestWelcome.testDefault(TestWelcome.java:37)
Caused by: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "ServletContext resource [/WEB-INF/views/index.html]")
at com.mkyong.web.TestWelcome.testDefault(TestWelcome.java:37)
Caused by: java.io.FileNotFoundException: Could not open ServletContext resource [/WEB-INF/views/index.html]
at com.mkyong.web.TestWelcome.testDefault(TestWelcome.java:37)
I took the files from git, and indeed there is no WEB-INF directory in your project, no web.xml, no index.html (although I did see that one in the steps above).
Can you please help?
thank you
yes it is missing and i wonder why nobody answers your question. You should only create the file index.html under the directory java-multi-modules/web/src/main/webapp/WEB-INF/views/index.html
you can copy the content of the file from above
Same here, did you figure this out? That .html whatever cause maven to fail build.
What is the point in specifying he package versions and also dependency’s versions in the submodules?
This is great. Thanks !
Hello!Can i built a Jar file in root folder which will be contain all submodules and when i run it the app will start?
Spring Boot, application.properties and multi module
Should all modules must contain the “application.properties” file?
I am leaving my file only in “application.properties”, however the application log is no longer being created in the given directory. I see the information now only on the Eclipse console. Do you know how I can solve it?
It’s really a good example !
I did every thing you showed here, but I still cannot use the interface from the dependency module.
You could check this question if you are interested : https://stackoverflow.com/questions/57866901/cannot-use-interface-or-class-from-another-local-dependency-module
Thanks again for your example!