阅读 806

手把手教你快速开发部署一个 Java 后端 Hello world 应用

0. 前言

使用 Java 语言实现服务端应用的企业占比很大,特别是现今 Web 应用大展异彩的时刻。作为 web 前端开发,掌握 Java 的开发有利于你的职业更进一步。为了让你快速入门 Java,今天纳撸多要分享的是如何快速开发部署一个 Java web 后端简单应用。

文章大纲:

  • 开发制品,编写 Java 代码
  • 制品构建,打包 Java 代码
  • 部署制品,部署 Java 代码

1. 如何开发制品

一个 web 应用简单考虑由三部分组成:浏览器作为客户端,Java 应用作为服务端,通信协议是 HTTP。我们要实现的效果是:在浏览器地址栏,访问应用地址,在页面中打印出 “Hello, naluduo" 文字,为此我们需要使用 Java 编写一个 HTTP 服务器响应浏览器端的访问请求。

1.1 Servlet 入门

编写 HTTP 服务器其实不难,在 Java 中只需要先编写给予多线程的 TCP 服务器,然后在一个 TCP 连接中读取 HTTP 请求,发送 HTTP 响应即可,可以看到下图中的客户端应用与服务端应用通信结构:

┌───────────┐                                   ┌───────────┐
│Application│                                   │Application│
├───────────┤                                   ├───────────┤
│  Socket   │                                   │  Socket   │
├───────────┤                                   ├───────────┤
│    TCP    │                                   │    TCP    │
├───────────┤      ┌──────┐       ┌──────┐      ├───────────┤
│    IP     │<────>│Router│<─────>│Router│<────>│    IP     │
└───────────┘      └──────┘       └──────┘      └───────────┘
复制代码

现在我们使用 Java 代码编写一个 HTTP 服务器,这块代码理解下传统的开发流程,相对底层。不想看,可以直接跳过。

import java.io.BufferedReader; // 缓冲读取
import java.io.BufferedWriter; // 缓冲写入
import java.io.IOException; // 异常
import java.io.InputStream;  // 输入流
import java.io.InputStreamReader; // 输入流读取
import java.io.OutputStream; // 输出流
import java.io.OutputStreamWriter; // 输出
import java.net.ServerSocket; // socket 包
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Server {
	public static void main(String[] args) throws IOException {
		ServerSocket ss = new ServerSocket(5050); // 监听指定端口
		System.out.println("server is running...");
		for (;;) {
			Socket sock = ss.accept();
			System.out.println("connected from " + sock.getRemoteSocketAddress());
			Thread t = new Handler(sock); // 新建线程
			t.start();
		}
	}
}

class Handler extends Thread {
	Socket sock;

	public Handler(Socket sock) {
		this.sock = sock;
	}

	@Override
	public void run() {
		try (InputStream input = this.sock.getInputStream()) {
			try (OutputStream output = this.sock.getOutputStream()) {
				handle(input, output);
			}
		} catch (Exception e) {
			try {
				this.sock.close();
			} catch (IOException ioe) {
			}
			System.out.println("client disconnected.");
		}
	}

	private void handle(InputStream input, OutputStream output) throws IOException {
		System.out.println("Process new http request...");
		var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
		var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
		// 读取HTTP请求:
		boolean requestOk = false;
		String first = reader.readLine();
		if (first.startsWith("GET / HTTP/1.")) {
			requestOk = true;
		}
		for (;;) {
			String header = reader.readLine();
			if (header.isEmpty()) { // 读取到空行时, HTTP Header读取完毕
				break;
			}
			System.out.println(header);
		}
		System.out.println(requestOk ? "Response OK" : "Response Error");
		if (!requestOk) {
			// 发送错误响应:
			writer.write("404 Not Found\r\n");
			writer.write("Content-Length: 0\r\n");
			writer.write("\r\n");
			writer.flush();
		} else {
			// 发送成功响应:
			String data = "<html><body><h1>Hello, Jecyu!</h1></body></html>";
			int length = data.getBytes(StandardCharsets.UTF_8).length;
			writer.write("HTTP/1.0 200 OK\r\n");
			writer.write("Connection: close\r\n");
			writer.write("Content-Type: text/html\r\n");
			writer.write("Content-Length: " + length + "\r\n");
			writer.write("\r\n"); // 空行标识Header和Body的分隔
			writer.write(data);
			writer.flush();
		}
	}
}
复制代码

由上述代码可见,要编写一个完善的 HTTP 服务器,以 HTTP/1.1 为例,需要考虑的包括:

  • 识别正确和错误的 HTTP 请求
  • 识别正确和错误的 HTTP 头
  • 复用 TCP 连接
  • 复用线程
  • IO 异常处理
  • ......

为了简单起见,我们可以把处理 TCP 连接,解析 HTTP 协议这些底层工作统统交给现成的服务器去做,我们只需要把自己的应用程序跑在 Web 服务器上。为了实现这一目的,JavaEE 提供了 Servlet API,我们只需使用 Servlet API 编写自己的 Servlet 来处理 HTTP 请求,Web 服务器实现 Servlet API 接口(比如 Tomcat 服务器),实现底层功能:

                 ┌───────────┐
                 │My Servlet │
                 ├───────────┤
                 │Servlet API│
┌───────┐  HTTP  ├───────────┤
│Browser│<──────>│Web Server │
└───────┘        └───────────┘
复制代码

综上,我们整体的开发部署步骤如下:

  • 编写 Servlet 代码。
  • 打包为 war 文件,即 Java Web Application Archive。
  • 复制到 Tomcat 的 webapps 目录下。
  • 启动 Tomcat 服务器,进行浏览器输入应用地址访问即可。

当然,在实践上述步骤之前,请确保你已经在电脑上安装了 Java 的运行环境,下载好 IntelliJ IDEA 编辑器,配置好 Java 版本环境。

1.2 使用 Maven 构建开发环境

在编写 Servlet 代码之前,为了提升开发效率,我们可以使用 Maven 构建开发环境。

Maven 是一个优秀的项目构建工具,可以很方便的对项目进行分模块构建,这样在开发和测试打包部署时,效率会提高很多。其次,Maven 进行依赖的管理,可以将不同系统的依赖进行统一管理,并且可以进行依赖之间的传递和继承。

Maven 遵循约定 》 配置 》编码,Maven 要负责项目的自动化构建,以编译为例,Maven 要想自动进行编译,那么它必须知道 Java 的源文件保存在哪里,这样约定之后,不用我们手动指定位置,Maven 能知道位置,从而帮我们完成自动编译。

  • Maven 使用 pom.xml 定义项目内容,并使用预设的目录结构;
  • 使用 Maven 中声明一个依赖项可以自动下载并导入 classpath;
  • Maven使用groupIdartifactIdversion唯一定位一个依赖。
  1. 安装Maven

要安装Maven,可以从Maven官网下载最新的Maven 3.6.x,然后在本地解压,设置几个环境变量:

M2_HOME=/path/to/maven-3.6.x
PATH=$PATH:$M2_HOME/bin
复制代码

然后,打开命令行窗口,输入mvn -version,应该看到Maven的版本信息:

Java version: 1.8.0_251, vendor: Oracle Corporation, runtime: /Library/Java/JavaVirtualMachines/jdk1.8.0_251.jdk/Contents/Home/jre
Default locale: zh_CN, platform encoding: UTF-8
OS name: "mac os x", version: "10.15.7", arch: "x86_64", family: "mac"
复制代码
  1. 按照 Maven 的规约来设置目录:
  • /src/main/java/ :Java 源码。
  • /src/main/resource :Java 配置文件,资源文件。
  • /src/test/java/ :Java 测试代码。
  • /src/test/resource :Java 测试配置文件,资源文件。
  • /target :文件编译过程中生成的 .class 文件、jar、war 等等。
  • pom.xml :配置文件
  1. 使用 IntelliJ IDEA 编译器,在新建项目时选择 maven,可以自动帮我们生成以上目录:

image

  1. 生成目录后,我们要编写 pom.xml 文件,填写项目需要的依赖和编译配置,其中 maven 使用 groupId,artifactId和version唯一定位一个依赖。

本应用中,主要依赖为 javax.servlet 4.0.0 版本的 API,以及依赖的 java 版本为 1.8。注意到<scope>指定为provided,表示编译时使用,但不会打包到.war文件中,因为运行期Web服务器本身已经提供了Servlet API相关的jar包。

<?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>org.example</groupId>
    <artifactId>spring-framework-projects</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.0</version>
            <scope>provided</scope> 
        </dependency>
    </dependencies>
</project>
复制代码

1.3 编写 Servlet 代码

引入对应的 servlet 包,编写代码如下,可以看到代码比前面没使用 Servlet 时简洁不少。

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        // 设置响应类型:
        resp.setContentType("text/html");
        // 获取输出流:
        PrintWriter pw = resp.getWriter();
        // 写入响应:
        pw.write("<h1>Hello, naluduo!</h1>");
        // 最后不要忘记flush强制输出:
        pw.flush();
    }
}
复制代码

一个 Servlet 总是继承自HttpServlet,然后覆写doGet()doPost()方法。注意到doGet()方法传入了HttpServletRequestHttpServletResponse两个对象,分别代表 HTTP 请求和响应。我们使用 Servlet API 时,并不直接与底层 TCP 交互,也不需要解析 HTTP协议,因为 HttpServletRequestHttpServletResponse 就已经封装好了请求和响应。以发送响应为例,我们只需要设置正确的响应类型,然后获取 PrintWriter ,写入响应即可。

2. 怎样构建制品

2.1 Tomcat 部署规范

无论使用哪个服务器,只要它支持 Servlet API 4.0(因为我们引入的Servlet版本是4.0),我们的 war 包都可以在上面运行,这里我们使用开源免费的 Tomcat 服务器。

2.1.1 Tomcat 安装启动

点击 Tomcat 官网,选择对应的安装包进行下载,由于我本机上已经安装了一个 Tomcat 10 的版本,就不再重新安装。

image

安装好 Tomcat 后,目录如下,本指南只需要关注 bin 和 webapps、conf 目录。

  • BUILDING.txt

  • NOTICE

  • RUNNING.txt

  • lib:包含要在类路径上添加的更多资源。

    • 在大多数 servlet 容器中,Tomcat 还支持一种机制来安装库 JAR文件(或解压缩的类)一次,并使它们对所有已安装的 Web 应用程序可见(不必包含在 Web 应用程序本身中)。 “类装入器方法”文档中介绍了有关 Tomcat 如何查找和共享此类的详细信息。 在 Tomcat 安装中,共享代码通常使用的位置是 $CATALINA_HOME / lib。 放置在此处的 JAR 文件对于 Web 应用程序和内部 Tomcat 代码均可见。 这是放置应用程序或内部 Tomcat 使用(例如 JDBCRealm )所需的 JDBC 驱动程序的好地方。

      开箱即用的标准 Tomcat 安装包括各种预安装的共享库文件,其中包括:Servlet 4.0 和 JSP 2.3 API是编写 Servlet 和 JavaServer Pages 的基础 。

  • webapps:Tomcat的主要Web发布目录,默认情况下把Web应用文件放于此目录

  • CONTRIBUTING.md

  • README.md

  • bin:存放 windows 或 Linux 平台上启动和关闭 Tomcat 的脚本文件

  • logs:存放 Tomcat 执行时的日志文件

  • work:存放 JSP 编译后产生的class文件

  • LICENSE

  • RELEASE-NOTES

  • conf:存放 Tomcat 服务器的各种全局配置文件,其中最重要的是 server.xml 和 web.xml。

  • temp:JVM 用于临时文件的目录

首先赋予启动 Catalia 权限,然后启动 Tomcat 服务器。

$ cd bin
  chmod +x catalina.sh
  sh startup.sh
复制代码

如无意外,即可在 http://localhost:8080/ 进行访问如下页面:

image

接下来,我们要了解部署的容器的规范。

一个标准的应用目录如下:

  • *.html*.jsp :HTML和JSP页面,以及应用程序的客户端浏览器必须可见的其他文件(例如JavaScript,样式表文件和图像)。 在较大的应用程序中,您可以选择将这些文件划分为子目录层次结构,但是对于较小的应用程序,通常只为这些文件维护一个目录要简单得多
  • /WEB-INF/web.xml:这是一个XML文件,描述了组成应用程序的servlet和其他组件,以及您希望服务器为您强制执行的所有初始化参数和容器管理的安全性约束。
  • /WEB-INF/classes/:此目录包含应用程序所需的所有Java类文件(和相关资源),包括servlet和非servlet类,这些文件未合并到JAR文件中。 如果将类组织为Java包,则必须在 /WEB-INF/classes/下的目录层次结构中反映出来。 例如,一个名为com.mycompany.mypackage.MyServlet 的 Java 类将需要存储在一个名为 /WEB-INF/classes/com/mycompany/mypackage/MyServlet.class 的文件中。
  • /WEB-INF/lib/:此目录包含 JAR 文件,这些文件包含您的应用程序所需的 Java 类文件(和相关资源),例如第三方类库或 JDBC 驱动程序。

使用 maven 可以把源代码打包输出如上文件。

2.2 打包构建

  1. pom.xml 中 project 标签下添加声明,打包为 war 包
<packaging>war</packaging> <!-- 打包为 war 包 -->
复制代码
  1. src/main/webapp 新建 WEB-INF 文件夹,然后新建文件 web.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>
</web-app>
复制代码
  1. 执行打包命令

    mvn clean package
    复制代码
  2. 获得打包后的文件如下:

image

3. 部署制品的做法

3.1 部署 war 应用

3. 1. 1 使用 GUI 界面
  1. 输入 Tomcat 地址 http://localhost:8080/manager/html,如果出现需要密码和账号登录,则需要进行配置,进入第 2 个步骤。

  2. 导航到按照 Tomcat 的目录文件夹,进入 /conf/tomcat-users.xml 进行配置,添加 manager-gui 角色和账号密码设置。

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">

  <role rolename="manager-gui"/>
     <user username="admin" password="admin" roles="manager-gui"/>
</tomcat-users>
复制代码
  1. 再次进入 manager 页面,并正确输入刚刚配置的账号和密码,导航到部署栏,在这里可以选择要上传的 war 文件,然后点击部署,即可进行访问。

image

  1. 在浏览器中进行访问如下图:

image

3.1.2 手动复制 war 文件到 webapps 内

直接将 war 包放到 webapps下,会自动解压为项目条件 server.xml 中。

<Host name="localhost"  appBase="webapps"
 unpackWARs="true" autoDeploy="true">
复制代码

3.2 解决 tomcat 部署 war 404 问题

按照 3.1 的部署方式,正常来说是没问题的。但是纳撸多还是踩了个坑,在手动复制 war 文件 webapps,并进行访问 http://localhost:8080/spring-framework-projects-1.0-SNAPSHOT/时出现 404 问题,经过多方排查,最终发现是自己安装的 Tomcat 版本 10 问题,不支持 Javax.servlet 4.0 接口规范,导致访问失败。

解决方案:重新安装 Tomcat 9 版本:apache-tomcat-9.0.4 ,重新进行部署即可。

4. 小结

本文通过实现一个 Hello 级别的应用,让你快速了解一个 Java Web 应用的开发、构建、部署的最简单流程,助你敲开服务端开发的大门。

参考资料

文章分类
后端
文章标签