Comment déployer une application web en Java et Angular dans un seul artéfact JAR ou WAR

Updated:

UPDATED Je viens de faire une mise à jour de la solution, avec une architecture plus elegante, pour Spring Boot 3 avec Angular 15, actuellement uniquement en Anglais:
https://marmo.dev/angular-with-java

Vieille version

Je suis en train de mettre à jour le projet avec Angular 13 et Spring Boot 2.6.2, il est encore en ‘test’, vous pouvez trouver les sources ici:
https://github.com/marco76/java-angular-basic/tree/origin/angular-13-spring-boot-262
si vous allez le tester, merci de me laisser un commentaire.

Dans votre projet, vous pouvez déployer votre application Angular et Java dans le même artefact (.jar ou .war) ou dans des artefacts séparés.

Si les équipes frontend et backend travaillent indépendamment et que l'équipe frontend utilise un monorépo, des artefacts séparés peuvent présenter certains avantages.

Un tutoriel vidéo se trouve sur ici (pour le français envoyer un commentaire) :

Un JAR avec Spring Boot ou Java EE / Jakarta EE

Ce projet contient 3 modules, le parent et 2 enfants (frontend et backend).

Dans cet exemple nous utilisons Spring Boot, la même chose s'applique pour un projet Java EE / Jakarta EE.

Nous construisons un fat JAR Spring qui contient le backend et le frontend.

Vous pouvez trouver le projet sur GitHub ici : https://github.com/marco76/java-angular-basic

Vous pouvez voir ici comment le projet est structuré :

L'artefact du projet backend contiendra les classes compilées en Java et l'application Angular :

Pour construire et démarrer le projet localement :

 
git clone https://github.com/marco76/java-angular-basic.git 
 
cd ./java-angular-basic.git 
 
mvn clean package 
 
cd ./backend/target 
 
java -jar backend-0.1-SNAPSHOT.jar 

pom.xml parent

Le fichier parent pom.xml déclare les 2 modules à construire. frontend pour Angular et backend pour Java.

Les 2 modules peuvent être démarrés séparément pendant le développement. L'application Angular utilisera le port 4200, l'application Java utilisera le port 8080.

<?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>dev.marco</groupId> 
    <artifactId>java-angular-example</artifactId> 
    <packaging>pom</packaging> 
    <version>0.1-SNAPSHOT</version> 
    <parent> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-parent</artifactId> 
        <version>2.4.3</version> 
    </parent> 
    <modules> 
        <module>frontend</module> 
        <module>backend</module> 
    </modules> 
 
    <dependencies> 
        <dependency> 
            <groupId>org.springframework</groupId> 
            <artifactId>spring-context-support</artifactId> 
            <version>5.3.4</version> 
        </dependency> 
        <dependency> 
            <groupId>org.springframework.boot</groupId> 
            <artifactId>spring-boot-starter-web</artifactId> 
            <version>2.4.3</version> 
        </dependency> 
    </dependencies> 
</project> 

Frontend pom.xml

Le module frontend contient une application Angular CLI standard.

Pendant le développement, vous pouvez démarrer l'application avec un ng serve classique.

Le gros du travail est fait par le plugin https://github.com/eirslett/frontend-maven-plugin. Ce plugin télécharge node, installe les bibliothèques et construit le projet.

Le résultat est un fichier jar qui contient l'application Angular compilée.

<?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> 
        <groupId>dev.marco</groupId> 
        <artifactId>java-angular-example</artifactId> 
        <version>0.1-SNAPSHOT</version> 
    </parent> 
    <modelVersion>4.0.0</modelVersion> 
    <artifactId>frontend</artifactId> 
    <packaging>jar</packaging> 
    <build> 
        <plugins> 
            <!-- clean the dist directory used by Angular --> 
            <plugin> 
                <artifactId>maven-clean-plugin</artifactId> 
                <version>3.1.0</version> 
                <configuration> 
                    <filesets> 
                        <fileset> 
                            <directory>dist</directory> 
                        </fileset> 
                    </filesets> 
                </configuration> 
            </plugin> 
 
            <plugin> 
                <groupId>com.github.eirslett</groupId> 
                <artifactId>frontend-maven-plugin</artifactId> 
                <version>1.11.2</version> 
 
                <executions> 
                    <!-- Install node and npm --> 
                    <execution> 
                        <id>Install Node and NPM</id> 
                        <goals> 
                            <goal>install-node-and-npm</goal> 
                        </goals> 
                        <configuration> 
                            <nodeVersion>v14.16.0</nodeVersion> 
                        </configuration> 
                    </execution> 
 
                    <!-- clean install --> 
                    <execution> 
                        <id>npm install</id> 
                        <goals> 
                            <goal>npm</goal> 
                        </goals> 
                    </execution> 
 
                    <!-- build app --> 
                    <execution> 
                        <id>npm run build</id> 
                        <goals> 
                            <goal>npm</goal> 
                        </goals> 
                        <configuration> 
                            <arguments>run build --prod</arguments> 
                        </configuration> 
                    </execution> 
 
                    <!-- code validation --> 
                    <execution> 
                        <id>npm run lint</id> 
                        <goals> 
                            <goal>npm</goal> 
                        </goals> 
                        <phase>test</phase> 
                        <configuration> 
                            <arguments>run lint</arguments> 
                        </configuration> 
                    </execution> 
                </executions> 
            </plugin> 
        </plugins> 
 
        <resources> 
            <resource> 
                <!-- we copy the content of the frontend directory in the final artifact --> 
                <directory>dist/frontend</directory> 
            </resource> 
        </resources> 
    </build> 
</project> 

Le backend Java pom.xml

Vous devez importer le module frontend, le code du frontend sera inclus dans votre paquet final.

Il y a une dépendance avec le module frontend. Cette solution n'est pas idéale car le module backend n'est pas complètement indépendant.

Nous utilisons le maven-dependency-plugin pour extraire le contenu du frontend et copier le contenu dans notre artefact backend.

L'application Angular est copiée dans le dossier /classes/static, ce dossier est utilisé pour servir du contenu statique dans Spring Boot.

<?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> 
        <groupId>dev.marco</groupId> 
        <artifactId>java-angular-example</artifactId> 
        <version>0.1-SNAPSHOT</version> 
        <relativePath>../pom.xml</relativePath> 
    </parent> 
 
    <modelVersion>4.0.0</modelVersion> 
 
    <properties> 
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 
        <project.build.outputEncoding>UTF-8</project.build.outputEncoding> 
    </properties> 
 
    <artifactId>backend</artifactId> 
 
    <dependencies> 
        <dependency> 
            <groupId>dev.marco</groupId> 
            <artifactId>frontend</artifactId> 
            <version>0.1-SNAPSHOT</version> 
            <type>jar</type> 
        </dependency> 
    </dependencies> 
 
    <build> 
        <plugins> 
            <plugin> 
                <groupId>org.springframework.boot</groupId> 
                <artifactId>spring-boot-maven-plugin</artifactId> 
                <version>2.4.3</version> 
                <configuration> 
                    <mainClass>dev.marco.example.springboot.Application</mainClass> 
                </configuration> 
                <executions> 
                    <execution> 
                        <goals> 
                            <goal>repackage</goal> 
                        </goals> 
                    </execution> 
                </executions> 
            </plugin> 
 
            <plugin> 
                <groupId>org.apache.maven.plugins</groupId> 
                <artifactId>maven-dependency-plugin</artifactId> 
                <version>3.1.2</version> 
                <executions> 
                    <execution> 
                        <id>merge</id> 
                        <phase>initialize</phase> 
                        <goals> 
                            <goal>unpack</goal> 
                        </goals> 
                        <configuration> 
                            <artifactItems> 
                                <artifactItem> 
                                    <groupId>dev.marco</groupId> 
                                    <artifactId>frontend</artifactId> 
                                    <type>jar</type> 
                                    <overWrite>true</overWrite> 
                                    <outputDirectory>${project.build.directory}/classes/static</outputDirectory> 
                                </artifactItem> 
                            </artifactItems> 
                        </configuration> 
                    </execution> 
                </executions> 
            </plugin> 
        </plugins> 
    </build> 
</project> 

Spring

Dans notre exemple, nous avons ajouté un controller REST pour déployer et exécuter l'application de base.

Pendant la phase de développement, nous démarrons l'application Spring backend et l'application Angular dans des instances séparées.

Pour permettre la communication entre le HttpClient d'Angular et le RestController de Spring, nous devons activer un Cross Origin Resource Sharing dans Spring Boot ou nos requêtes seront refusées.

@RestController 
// we allow localhost:4200 for testing purposes 
@CrossOrigin(origins = "http://localhost:4200") 
public class HelloController { 
 
    @RequestMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE) 
    public Map<String, String> index() { 
        return Collections.singletonMap("message", "Greetings from Spring Boot!"); 
    } 
 
} 

Angular

Notre application Angular ne fait rien de spécial, nous appelons simplement le service backend et nous affichons le message reçu :

export class AppComponent implements OnInit{ 
  title = 'frontend'; 
  message = ''; 
 
  constructor(private http: HttpClient) { } 
 
  ngOnInit(): void { 
    this.http.get('http://localhost:8080/message').pipe( 
      first(), 
      tap(result => console.log('Message received from the server: ', result)), 
      map(result => this.message = (result as any).message) 
    ).subscribe(); 
  } 
 
} 

Déployer un WAR avec Tomcat

Si vous devez déployer un WAR avec Tomcat, vous pouvez utiliser la branche suivante

https://github.com/marco76/java-angular-basic/tree/feature/war-for-tomcat

Vous devez

  1. mvn clean package

  2. Dans le backend il y aura un fichier : angular-spring-example.war

  3. Déployer le fichier, il devrait être visible sous : localhost:8080/angular-spring-example, si vous voulez l'exécuter en tant que / vous devez le renommer dans ROOT avant le déploiement.

Changements majeurs par rapport à la version JAR :

backend/pom.xml

Nous changeons le type de packaging et nous ajoutons une dépendance à spring-boot-starter-tomcat

<packaging>war</packaging> 
 
<dependency> 
<groupId>org.springframework.boot</groupId> 
<artifactId>spring-boot-starter-tomcat</artifactId> 
<scope>provided</scope> 
</dependency> 

backend/src/main/java/dev/marco/example/springboot/Application.java

@SpringBootApplication 
// we extend SpringBootServletInitializer 
    public class Application extends SpringBootServletInitializer { 

You could be interested in

Right click context menu with Angular

Right click custom menu inside dynamic lists with Angular Material
2020-05-31

Enums in Angular templates

How to use enum in the html template
2019-01-21
WebApp built by Marco using SpringBoot 3.2.4 and Java 21, in a Server in Switzerland without 'Cloud'.