Developing a multi-module application where the backend runs on Spring Boot and the frontend is powered by Angular is far less complicated than one might expect. Check out how you can build such a project and manage it as a single jar file.
开发一个多模块应用程序,使后端在Spring Boot上运行,并且前端由Angular驱动,这比预期的要复杂得多。了解如何构建这样的项目并将其作为单个jar文件进行管理
What we are going to build
The goal is to design and create a multi-module application, established with frameworks: Spring Boot for the backend and Angular for the frontend. After that we will build the project into a single jar file using Maven.
目标是设计和创建一个使用以下框架建立的多模块应用程序:用于后端的Spring Boot和用于前端的Angular。之后,我们将使用Maven将项目构建到单个jar文件中
The finished project is available in the following GitHub repository: little-pinecone/spring-boot-angular-scaffolding.
Requirements
I try to keep the project up to date. Visit the releases list to keep track of the current versions of frameworks and libraries used.
-
We are working on a fresh Spring Boot project with the Web package dependency. You can read about setting up this project with Spring Initializr in How to create a new Spring Boot Project. In this tutorial I named the Spring Boot project
spring-boot-angular-scaffolding
.我们正在开发一个具有Web包依赖关系的全新Spring Boot项目。您可以在如何创建新的Spring Boot项目中阅读有关使用Spring Initializr设置此项目的信息。在本教程中,我将Spring Boot项目命名为spring-boot-angular-scaffolding
-
Angular CLI – a command line interface tool that generates a project as well as performs many development tasks.
一个命令行界面工具,可生成项目并执行许多开发任务
I’m working on:
$ ng --version / _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _| / △ | '_ / _` | | | | |/ _` | '__| | | | | | | / ___ | | | | (_| | |_| | | (_| | | | |___| |___ | | /_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___| |___/ Angular CLI: 9.1.11 Node: 12.18.2 OS: win32 x64 Angular: ... Ivy Workspace: Package Version ------------------------------------------------------ @angular-devkit/architect 0.901.11 @angular-devkit/core 9.1.11 @angular-devkit/schematics 9.1.11 @schematics/angular 9.1.11 @schematics/update 0.901.11 rxjs 6.5.4
-
This project was created on Kubuntu (Ubuntu 18.04 LTS) – remember to adjust commands if you don’t work on Linux OS.
-
For the IDE I recommend IntelliJ IDEA Community Edition.
Architecture overview
架构概述
In our multi-module maven project we want all components to be built together and share some metadata, but to differ in configuration. Fortunately, we can specify a parent project in both modules and define submodules in the parent – we will utilize both Project Inheritance and Aggregation.
在多模块Maven项目中,希望将所有组件构建在一起并共享一些元数据,但是配置有所不同。幸运的是,可以在两个模块中都指定一个父项目,并在父模块中定义子模块–我们将同时利用Project Inheritance和Aggregation
The final project directory tree:
Manage the Spring Boot module structure
管理Spring Boot模块结构
We will start with preparing the backend module. Go to the main folder of the Spring Boot app:
首先从准备后端模块开始。然后转到Spring Boot应用程序的主文件夹
$ pwd
/home/little_pinecone/projects/spring_boot_with_angular/spring-boot-angular-scaffolding
Create the directory where the backend part of our app will be stored:
创建目录,其中将存储应用程序的后端部分:
$ mkdir backend
Now, move the src
directory to the freshly created backend
folder:
现在,将src目录移动到新创建的后端文件夹
$ mv src backend
We also need a child pom.xml
file to specify Spring Boot dependencies, so copy the already existing file from the parent project to our backend
directory:
还需要一个子pom.xml文件来指定Spring Boot依赖项,因此将已经存在的文件从父项目复制到我们的后端目录中:
$ cp pom.xml backend
In pom.xml
reserved for the Spring Boot module we will leave all required maven dependencies unchanged – the application will look for them here.
在为Spring Boot模块保留的pom.xml中,我们将保留所有必需的maven依赖关系-应用程序将在此处查找它们
However, we will completely alter the content of the <parent>
element:
但是,我们将完全更改
-
to specify which artifact is the parent for this pom we will use the fully qualified artifact name of the parent pom –
spring-boot-angular-scaffolding
;为了指定哪个pom是该pom的父代,我们将使用父pom的完全限定的工件名称-spring-boot-angular-scaffolding
-
to keep the groupId and the version of the module the same as its parent, we will enclose those fields here.
为了使groupId和模块的版本与其父模块相同,我们将在此处包含这些字段
Furthermore, there are some metadata that require to be updated, namely:
此外,有些元数据需要更新,即:
<artifactId>
– we will putbackend
here<name>
– we will putbackend
here<description>
– we will putThe backend module built with Spring Boot
here<properties>
– remove it<packaging>
– remove it
Thanks to those changes the backend can be later recognised by the parent application as its module.
Please, double check which pom.xml you are editing – now we are working on spring-boot-angular-scaffolding/backend/pom.xml.
请仔细检查您要编辑的pom.xml-现在我们正在研究spring-boot-angular-scaffolding/backend/pom.xml
Leave the rest of the properties unaltered and check out the updated values:
保留其余的所有属性不变,并检查更新后的值
<!-- backend/pom.xml -->
…
<artifactId>backend</artifactId>
<name>backend</name>
<description>The backend module built with Spring Boot</description>
…
<parent>
<groupId>in.keepgrowing</groupId>
<artifactId>spring-boot-angular-scaffolding</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
…
<dependencies>
…
</dependencies>
<build>
…
</build>
Initialize a git repository in the main project:
$ git init
We will use the .gitignore
file from the parent project, copy the file to the backend folder:
我们将使用父项目中的.gitignore文件,将文件复制到后端文件夹:
$ cp .gitignore backend
We’ve just prepared the backend module within our project – it won’t work as intended yet, we need to create the frontend module, and, later, bring all components together.
刚刚在项目中准备了后端模块-尚无法按预期工作,还需要创建前端模块,然后将所有组件整合在一起
The work done in this section is contained in the commit ed4c7fe5127deaeffc81fe1012d9bca7a7038cee.
Manage the Angular module structure
管理Angular模块结构
In the parent project create the frontend/src/main/
folder for the web application sources – the src
and main
directories are here to follow the Maven standard directory layout. Use -p
(–parents) option to create all non-existing parent directories:
在父项目中,为Web应用程序源创建frontend/src/main/文件夹src和main目录在此处遵循Maven标准目录布局。使用-p(-parents)选项创建所有不存在的父目录:
$ mkdir -p frontend/src/main/
Similar to the backend, this module also needs its own pom.xml
so copy the one prepared in the backend:
与后端类似,该模块也需要自己的pom.xml,因此请复制在后端准备的模块
$ cp backend/pom.xml frontend
Update the following elements:
<artifactId>
– we will putfrontend
here,<name>
– we will putfrontend
here,<description>
– we will putThe frontend module built with Angular
here,<dependencies>
– remove it,<plugins>
– remove the following plugin
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
Please, double check which pom.xml you are editing – now we are working on spring-boot-angular-scaffolding/frontend/pom.xml.
Check out the updated file:
<!-- frontend/pom.xml -->
…
<artifactId>frontend</artifactId>
<name>frontend</name>
<description>The frontend module built with Angular</description>
<parent>
…
</parent>
<build>
<plugins></plugins>
</build>
We will use the .gitignore
file from the parent project, copy the file to the frontend folder:
我们将使用父项目中的.gitignore文件,将文件复制到frontend文件夹:
$ cp .gitignore frontend
You can adjust the .gitignore to your needs or use the standard one created automatically by Angular CLI – in both cases remember to include the /target/
folder.
您可以根据需要调整.gitignore或使用由Angular CLI自动创建的标准版本-在这两种情况下都请记住包括/target/文件夹
The frontend module is prepared but still empty, we will come back to it after creating the Angular project. For now, let’s move to aggregating the created modules with the parent application.
前端模块已经准备好了,但仍然是空的,在创建Angular项目之后我们将返回到它。现在,让我们开始将创建的模块与父应用程序聚合在一起。
The work done in this section is contained in the commit cfcf3aad897d9adca9aa81c50e77e8bf25073074.
Tie the modules together
将模块捆绑在一起
The backend and frontend pom.xml
files contains the <parent>
field so the modules know from which project they inherit. Now the main application requires data to identify which submodules should be aggregated during the build. We will provide them by updating the main pom.xml
file by adding a new field – <modules>
– with directories of its modules specified: backend
and fronted
.
后端和前端pom.xml文件包含
Update the following properties:
-
<packaging>
– we will putpom
here, -
<dependencies>
– remove it, -
<build>
– remove it.Please, double check which pom.xml you are editing – now we are working on spring-boot-angular-scaffolding/pom.xml.
Check out the updated file:
<!-- pom.xml -->
…
<groupId>in.keepgrowing</groupId>
<artifactId>spring-boot-angular-scaffolding</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>spring-boot-angular-scaffolding</name>
<description>A multi-module project built with Spring Boot and Angular</description>
<parent>
…
</parent>
<properties>
…
</properties>
<modules>
<module>backend</module>
<module>frontend</module>
</modules>
If you are working in InteliJ IDE choose Enable Autoimport
option when prompted or confirm this option in settings.
如果您使用的是IntelliJ IDEA,请在出现提示时选择"启用自动导入"选项,或在设置中确认此选项
Finally, we have a multi-module application. The backend module is the Spring Boot project created with Spring Initializr, what we need now is to set up an Angular project in the frontend module.
最后,我们有一个多模块应用程序。后端模块是使用Spring Initializr创建的Spring Boot项目,现在我们需要在前端模块中设置Angular项目
The work done in this section is contained in the commit 6963bee6b2aeb0dfa83b4fde28664cfdcd0ab3b0.
Generate an Angular project
Create an Angular project in the frontend/main/src/
folder. We will use the --skip-git
option cause we are already tracking the whole project from the main directory, so we don’t need another repository within the project. I will simply name the project angular
:
在frontend/main/src/文件夹中创建一个Angular项目。我们将使用--skip-git选项,因为我们已经从主目录跟踪了整个项目,因此我们在项目内不需要其它存储库。我只是将项目命名为angular
$ cd frontend/src/main
$ ng new --skip-git angular
The angular
directory contains the new project:
The work done in this section is contained in the commit ccc98ee03ef361bce2ccd29501d67cb58c2f4a3b.
Build project with Maven
Configure the frontend-maven-plugin
To build the frontend module with Maven we will use frontend-maven-plugin. It installs node and npm as well as builds our angular project. Be sure that you include the latest tagged version of the plugin (I used 1.6) and add the following code from the <plugins>
field to the frontend/pom.xml
:
为了使用Maven构建前端模块,我们将使用frontend-maven-plugin。它安装node和npm以及构建Angular项目。确保您包括该插件的最新标记版本(我使用1.6),并将
<!-- frontend/pom.xml -->
…
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.6</version>
<configuration>
<workingDirectory>src/main/angular</workingDirectory>
<nodeVersion>v8.11.3</nodeVersion>
<npmVersion>6.2.0</npmVersion>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
</execution>
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
</executions>
</plugin>
…
</plugins>
Take a closer look to the following part of the plugin configuration:
仔细查看插件配置的以下部分
<!-- frontend/pom.xml -->
…
<execution>
<id>npm run build</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build</arguments>
</configuration>
</execution>
…
npm run build
calls the npm run command (don’t confuse it with npm build), which will run the angular ng build command to compile an application into an output directory. You can configure the build command in the frontend’s package.json
file, so we will add the –prod option to uglify the javascript code:
npm run build调用npm run命令(不要将其与npm build混淆),它将运行angular ng build命令将应用程序编译到输出目录中。您可以在前端的package.json文件中配置构建命令,因此我们将添加–prod选项以丑化javascript代码:
//frontend/src/main/angular/package.json
{
…
"scripts": {
…
"build": "ng build --prod",
…
},
Alter the angular output path
To keep up with the Maven standards we need to alternate the outputPath
option for our Angular project, instead of "dist/angular"
use "../../../target/frontend"
:
为了跟上Maven标准,我们需要为Angular项目替换outputPath选项,使用“ ../../../target/frontend”代替“ dist/angular”:
// frontend/src/main/angular/angular.json
…
"projects": {
"angular": {
"architect": {
"build": {
…
"options": {
"outputPath": "../../../target/frontend",
…
We have just changed the path for the built project, so we need to add the information about the new path to the frontend/pom.xml
:
我们刚刚更改了构建项目的路径,因此我们需要将有关新路径的信息添加到frontend/pom.xml中:
<!-- frontend/pom.xml -->
…
<build>
…
<resources>
<resource>
<directory>target/frontend</directory>
<targetPath>static</targetPath>
</resource>
</resources>
</build>
…
Add the frontend module dependency
Add the frontend dependency to the Spring Boot backend:
将前端依赖项添加到Spring Boot后端:
<!-- backend/pom.xml -->
…
<dependencies>
…
<dependency>
<groupId>in.keepgrowing</groupId>
<artifactId>frontend</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependencies>
We used ${project.version}
to access the project.version
variable and avoid repetition. It’s possible thanks to the fact that “any field of the model that is a single value element can be referenced as a variable” – see maven documentation.
**我们使用${project.version}来访问project.version变量并避免重复。这有可能要归因于“可以将作为单个值元素的模型的任何字段都引用为变量”的事实–请参见maven文档。
Build the project
You can build your project with the Maven plugin directly from the InteliJ IDE – double click clean
and then install
:
您可以直接从IntelliJ IDEA使用Maven插件构建项目–双击clean,然后安装:
The successful outcome:
Alternatively install maven on your machine and execute the build command:
或者在您的计算机上安装maven并执行build命令:
$ sudo apt install maven
$ mvn clean install
Clean the git repository
The frontend-maven-plugin
downloads Node and npm from https://nodejs.org/ dist and extracts them into a node
folder created in frontend/src/main/angular/
directory. To avoid commiting this folder, add it alongside node_modules
to the .gitignore
created during generating the Angular project:
frontend-maven-plugin从https://nodejs.org/ dist下载Node和npm并将其解压缩到在frontend/src /main/angular/目录中创建的node文件夹中。为了避免提交此文件夹,请将其与node_modules一起添加到生成Angular项目期间创建的.gitignore中:
# frontend/src/main/angular/.gitignore
# dependencies
/node_modules
/node
The work done in this section is contained in the commit f3f29ede3c5271e455ee3ccfa222563a7e28fc9f.
Prepare the app for development
准备要开发的应用程序
Fix CORS issues on localhost
修复localhost上的CORS问题
To speed up the development process you may want to run the frontend module separately. In that case, the host that serves the frontend (http://localhost:4200/
) is different from the host that serves the data (http://localhost:8080/
). You are going to encounter the following error:
为了加快开发过程,您可能需要单独运行前端模块。在这种情况下,服务于前端的主机(http://localhost:4200/)与服务于数据的主机(http://localhost:8080/)不同。您将遇到以下错误:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:4200' is therefore not allowed access.
所请求的资源上没有“ Access-Control-Allow-Origin”标头。因此,不允许访问源“ http://localhost:4200”
Read the Fix CORS issues between Spring Boot and Angular on localhost post to learn how to enable the cross-domain communication on a development environment.
阅读localhost上的Spring Boot和Angular之间的Fix CORS问题,以了解如何在开发环境中启用跨域通信
Allow Angular to handle the routing
All paths implemented in Angular are accessible when the server is started with $ ng serve . However, when you run the whole project built with Maven, the app will give you a Whitelabel Error Page:
使用$ng serve启动服务器时,可以访问Angular中实现的所有路径。但是,当您运行使用Maven构建的整个项目时,该应用程序将为您提供Whitelabel错误页面
There was an unexpected error (type=Not Found, status=404)
发生意外错误(类型=未找到,状态= 404)
The reason is that Spring Boot tries to handle all routes by itself. Check out the Make Spring Boot surrender routing control to Angular post to solve this issue.
原因是Spring Boot尝试自行处理所有路由。
Troubleshooting
Check the global settings of maven:
Apache Maven 3.6.3
Maven home: /usr/share/maven
Java version: 1.8.0_251, vendor: Oracle Corporation, runtime: /opt/jdk/jdk1.8.0_251/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-72-generic", arch: "amd64", family: "unix"
or you can fix dependencies issues. e.g. data-jpa requires jaxb-api in JDK9, so add it to the backend pom file:
或者您可以解决依赖关系问题。例如data-jpa在JDK9中需要jaxb-api,因此将其添加到后端pom文件中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
The problem doesn’t occur when you build the project directly from InteliJ.
直接从IntelliJ生成项目时,不会发生此问题