[maven] maven 创建 web 项目并嵌套项目
这里主要就创建另外一个 web 项目,并且创建一个 parent 项目比较方便的管理一下两个子项目。
maven web 项目
web 创建和 quickstart 的过程是差不多的,只不过这里换乘 webapp,配置方便的话可以搞的东西挺多的……这里就搞 servlet,上古版本的东西了。
create new web application
创建新的 maven 项目这里就换成 webapp,创建后的项目,pom 也会不太一样:
❯ tree productweb
productweb
├── pom.xml
├── src
│ └── main
│ └── webapp
│ ├── WEB-INF
│ │ └── web.xml
│ └── index.jsp
└── target
├── classes
├── m2e-wtp
│ └── web-resources
│ └── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── com.goldenaarcher.product
│ └── productweb
│ ├── pom.properties
│ └── pom.xml
└── test-classes
14 directories, 6 files
❯ cat productweb/pom.xml
<?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.goldenaarcher.product</groupId>
<artifactId>productweb</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>productweb Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<finalName>productweb</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
简单的过一下 pom 的配置:
- 如 pom 显示的一样,这里打包的包是 war 而不是 jar。
finalName
这个选项设置会重写默认的项目名
这时候项目还是没有办法运行的,因为目前项目没有服务器,所以这个时候需要完成服务器相关的配置。
-
eclipse 中添加 tomcat
完成这一步项目就不会报错,可以正常 build
这个也可以通过直接在 pom 里面设置,这里是直接配置本地已经下载好的 tomcat
具体步骤就是在项目属性里面选择 targeted runtime:
如果 tomcat 的配置像在之前的笔记 Mac 中安装 tomcat 及 Eclipse 配置 Tomcat 提过的一样已经搞好了,也可以通过 tomcat 进行修改:
-
添加 servlet 的依赖
依赖如下:
<dependency> <groupId>org.apache.geronimo.specs</groupId> <artifactId>geronimo-servlet_3.0_spec</artifactId> <version>1.0</version> <scope>provided</scope> </dependency>
两个都配置好了就可以运行 mvn clean install
了:
[[1;34mINFO[m] [1m--- [0;32mmaven-war-plugin:3.2.2:war[m [1m(default-war)[m @ [36mproductweb[0;1m ---[m
[[1;34mINFO[m] Packaging webapp
[[1;34mINFO[m] Assembling webapp [productweb] in [/Users/usr/study/maven/productweb/target/productweb]
[[1;34mINFO[m] Processing war project
[[1;34mINFO[m] Copying webapp resources [/Users/usr/study/maven/productweb/src/main/webapp]
[[1;34mINFO[m] Webapp assembled in [24 msecs]
[[1;34mINFO[m] Building war: /Users/usr/study/maven/productweb/target/productweb.war
[[1;34mINFO[m]
[[1;34mINFO[m] [1m--- [0;32mmaven-install-plugin:2.5.2:install[m [1m(default-install)[m @ [36mproductweb[0;1m ---[m
[[1;34mINFO[m] Installing /Users/usr/study/maven/productweb/target/productweb.war to /Users/usr/.m2/repository/com/goldenaarcher/product/productweb/0.0.1-SNAPSHOT/productweb-0.0.1-SNAPSHOT.war
[[1;34mINFO[m] Installing /Users/usr/study/maven/productweb/pom.xml to /Users/usr/.m2/repository/com/goldenaarcher/product/productweb/0.0.1-SNAPSHOT/productweb-0.0.1-SNAPSHOT.pom
[[1;34mINFO[m] [1m------------------------------------------------------------------------[m
[[1;34mINFO[m] [1;32mBUILD SUCCESS[m
[[1;34mINFO[m] [1m------------------------------------------------------------------------[m
[[1;34mINFO[m] Total time: 1.631 s
[[1;34mINFO[m] Finished at: 2023-09-11T21:12:28-04:00
[[1;34mINFO[m] [1m------------------------------------------------------------------------[m
注意这里 war 就已经打包好了
准备工作
maven 创建项目的时候,可能会漏结构,所以需要确认一下结构。Maven resource folder isn’t created 中提到的是资源文件夹没有创建,不过我这里是连 java 都瘸了……所以说,如果 main
没有三个文件夹,就需要创建缺失的文件夹,,随后更新一下 maven 项目,此时结构如下:
❯ cd productweb
❯ mkdir src/main/java
❯ mkdir src/main/resources
❯ tree .
.
├── pom.xml
├── src
│ └── main
│ ├── java
│ ├── resources
│ └── webapp
│ ├── WEB-INF
│ │ └── web.xml
│ └── index.jsp
└── target
├── classes
├── m2e-wtp
│ └── web-resources
│ └── META-INF
│ ├── MANIFEST.MF
│ └── maven
│ └── com.goldenaarcher.product
│ └── productweb
│ ├── pom.properties
│ └── pom.xml
├── maven-archiver
│ └── pom.properties
├── productweb
│ ├── META-INF
│ ├── WEB-INF
│ │ ├── classes
│ │ └── web.xml
│ └── index.jsp
├── productweb.war
└── test-classes
21 directories, 10 files
resources 还是挺重要的,一些资源的同步就靠这个文件夹。
创建 servlets
创建产品 servlet
这里点击项目右键选择创建新的 servlet,现在这个年代已经不是热门选项了,有可能需要到 more 里面去找:
下一步提供 servlet 的名称:
选择 URL mapping:
完成创建:
完成后的结构如下:
❯ tree src/main/java
src/main/java
└── com
└── goldenaarcher
└── product
└── servlets
└── CreateProductServlet.java
5 directories, 1 file
servlet 的文件内容为:
package com.goldenaarcher.product.servlets;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Servlet implementation class CreateProductServlet
*/
public class CreateProductServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public CreateProductServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}
}
展示产品 servlet
也是右键新建生成一个 servlet,这里选择 doGet
,然后生成一个空的 java 模板。
关于 mapping
这个跟自动生成有关系,有可能是通过注解 @WebServlet
实现,或者是通过 webapp/WEB-INF
下的 web.xml
实现 mapping,eclipse 自动生成 xml 文件:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>CreateProductServlet</servlet-name>
<display-name>CreateProductServlet</display-name>
<description></description>
<servlet-class>com.goldenaarcher.product.servlets.CreateProductServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CreateProductServlet</servlet-name>
<url-pattern>/CreateProductServlet</url-pattern>
</servlet-mapping>
</web-app>
stack overflow 上也有一个回答解释了 mapping 的问题: Not getting automatically web.xml file while creating servlet in Eclipse Juno 4.2
简单的说就是,@WebServlet("/CreateProductServlet")
可以代替这几行代码:
<servlet>
<servlet-name>CreateProductServlet</servlet-name>
<display-name>CreateProductServlet</display-name>
<description></description>
<servlet-class>com.goldenaarcher.product.servlets.CreateProductServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CreateProductServlet</servlet-name>
<url-pattern>/CreateProductServlet</url-pattern>
</servlet-mapping>
两种配置可以同时存在,但是同一个 servelet 不能被 map 两次,否则会运行失败。
tomecat 10 更新
tomcat10 的包从 javax.servlet.jsp
变成了 jakarta.servlet.jsp
,所以要下载对应的新 dependency
<!-- https://mvnrepository.com/artifact/jakarta.servlet/jakarta.servlet-api -->
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
创建 HTML
这个页面就是创建产品的页面,它的目录为 src/main/webapp
完整代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Create Product</title>
<style>
div {
display: flex;
}
input {
display: block;
}
</style>
</head>
<body>
<h3>Enter Product Details:</h3>
<form method="post" action="CreateProductServlet">
<div>Product Id: <input name="id" /></div>
<div>Product Name: <input name="name" /></div>
<div>Product Description: <input name="description" /></div>
<div>Product Price: <input name="price" /></div>
<div>
<input type="submit" />
</div>
</form>
</body>
</html>
此时的结构为:
❯ tree src
src
└── main
├── java
│ └── com
│ └── goldenaarcher
│ └── product
│ └── servlets
│ ├── CreateProductServlet.java
│ └── DisplayProductDetailsServelet.java
├── resources
└── webapp
├── WEB-INF
│ └── web.xml
├── index.jsp
└── product.html
10 directories, 5 files
运行项目
这回直接在 tomcat 上进行运行,这时候页面显示如下:
下一步要做的,就是把 product service 和 product web 两个项目连起来,实现一个可以运行的页面。
多模块项目
为了方便管理,主要这样可以只用跑一次 maven 相关的指令。
清理结构
我这里创建了一个新的文件夹,然后把两个 product 相关的文件夹搬进去了。这个可以用 GUI 也可以用命令行。
调整完了结构如下:
❯ tree -L 3
.
├── pom.xml
├── productservices
│ ├── pom.xml
│ ├── src
│ │ ├── main
│ │ └── test
│ └── target
│ ├── classes
│ ├── generated-sources
│ ├── generated-test-sources
│ ├── maven-archiver
│ ├── maven-status
│ ├── productservices-1.0.jar
│ ├── surefire-reports
│ └── test-classes
└── productweb
├── pom.xml
├── src
│ └── main
└── target
├── classes
├── generated-sources
├── m2e-wtp
├── maven-archiver
├── maven-status
├── productweb
└── productweb.war
23 directories, 5 files
新增&修改 pom 文件
总共需要修改 3 个 pom 文件
新增 parent 文件夹下的 pom
创建 parent
文件夹下的 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>
<groupId>com.goldenaarcher.product</groupId>
<artifactId>productparent</artifactId>
<version>1.0</version>
<packaging>pom</packaging>
<name>productparent</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<modules>
<module>productservices</module>
<module>productweb</module>
</modules>
</project>
这里主要需要注意的就是亮点:
- packaging 的格式是 pom
- modules 下包含所有的子项目
更新子项目的 pom
更新 parent 下的部分都是共同的,所有子项目都要重新定义一下父项目
productservices pom
<parent>
<groupId>com.goldenaarcher.product</groupId>
<artifactId>productparent</artifactId>
<version>1.0</version>
</parent>
<artifactId>productservices</artifactId>
<name>productservices</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
productweb pom
除了 parent,web 这里海需要更新 dependency,因为 web 需要调用 service 中的 bo 去实现具体功能。
<parent>
<groupId>com.goldenaarcher.product</groupId>
<artifactId>productparent</artifactId>
<version>1.0</version>
</parent>
<artifactId>productweb</artifactId>
<packaging>war</packaging>
<name>productweb Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<!-- 省略其他 -->
<dependency>
<groupId>com.goldenaarcher.product</groupId>
<artifactId>productservices</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
servelt 的 java 实现
这里主要实现的就是从 request 中获取数据,传到 bo 中实现功能即可
实现 CreateProductServlet
这里会从 THML 中获取对应的数据,调用 bo 里的 create 创建一个 product
package com.goldenaarcher.product.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.goldenaarcher.product.bo.ProductBO;
import com.goldenaarcher.product.bo.ProductBOImpl;
import com.goldenaarcher.product.dto.Product;
/**
* Servlet implementation class CreateProductServlet
*/
public class CreateProductServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Integer id = Integer.parseInt( request.getParameter("id"));
String name = request.getParameter("name");
String desc = request.getParameter("description");
Integer price = Integer.parseInt( request.getParameter("price"));
Product product = new Product();
product.setId(id);
product.setDescription(desc);
product.setName(name);
product.setPrice(price);
ProductBO bo = new ProductBOImpl();
bo.create(product);
PrintWriter out = response.getWriter();
out.print("product created");
}
}
实现 DisplayProductDetailsServelet
因为这里需要从 query param 中获取 id,所以如果没提供 query param 就会抛出 parse 异常
package com.goldenaarcher.product.servlets;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.goldenaarcher.product.bo.ProductBO;
import com.goldenaarcher.product.bo.ProductBOImpl;
import com.goldenaarcher.product.dto.Product;
/**
* Servlet implementation class DisplayProductDetailsServelet
*/
public class DisplayProductDetailsServelet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ProductBO bo = new ProductBOImpl();
Product product = bo.findProduct(Integer.parseInt(request.getParameter("id")));
PrintWriter out = response.getWriter();
out.print("Product details: ");
out.print("Product ID: " + product.getId());
out.print("Product Name: " + product.getName());
out.print("Product Description: " + product.getDescription());
out.print("Product Price: " + product.getPrice());
}
}
最终创建和运行
这里会把 build install 拆分成几个部分简单解释一下
reactor
reactor 指的是 maven 能够根据项目的依赖,调整打包顺序的能力,maven 会根据其依赖关系调整打包的顺序。
❯ mvn clean install
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] productparent [pom]
[INFO] productservices [jar]
[INFO] productweb Maven Webapp [war]
[INFO]
[INFO] --------------< com.goldenaarcher.product:productparent >---------------
[INFO] Building productparent 1.0 [1/3]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ productparent ---
[INFO]
[INFO] --- maven-install-plugin:2.4:install (default-install) @ productparent ---
[INFO] Installing /Users/usr/study/maven/parent/pom.xml to /Users/usr/.m2/repository/com/goldenaarcher/product/productparent/1.0/productparent-1.0.pom
打包子项目
这里 maven 会将 service 的项目打包到 m2 的文件夹中让其他的项目使用,这里 web 就需要调用 service 中的内容。
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ productservices ---
[INFO] Building jar: /Users/usr/study/maven/parent/productservices/target/productservices-1.0.jar
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ productservices ---
[INFO] Installing /Users/usr/study/maven/parent/productservices/target/productservices-1.0.jar to /Users/usr/.m2/repository/com/goldenaarcher/product/productservices/1.0/productservices-1.0.jar
[INFO] Installing /Users/usr/study/maven/parent/productservices/pom.xml to /Users/usr/.m2/repository/com/goldenaarcher/product/productservices/1.0/productservices-1.0.pom
[INFO]
其他项目打包部分与其他的 maven 差不多,这里不多赘述。
这里的功能实现完了,war 也打包完了,可以直接放到服务器上运行。本质上来说,war 中的内容和 target 中的 productweb 结构是一样的,前者是后者的压缩版。
最终运行结果
依旧是在 eclipse 中的 tomcat 运行服务器,所有的功能都实现了,最终的运行结果如下:
CSS 会不太一样,我笔记里改了一点 CSS。